mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2024-12-23 16:40:32 +00:00
Add ExoMedia and remove ExoPlayer
- Remove ExoPlayer files - Added ExoMedia library
This commit is contained in:
parent
a08cbfcef1
commit
abff1f537b
@ -42,10 +42,10 @@ dependencies {
|
|||||||
compile 'de.hdodenhof:circleimageview:2.0.0'
|
compile 'de.hdodenhof:circleimageview:2.0.0'
|
||||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||||
compile 'com.github.nirhart:parallaxscroll:1.0'
|
compile 'com.github.nirhart:parallaxscroll:1.0'
|
||||||
compile 'com.google.android.exoplayer:exoplayer:r1.5.5'
|
|
||||||
compile 'com.google.code.gson:gson:2.4'
|
compile 'com.google.code.gson:gson:2.4'
|
||||||
compile 'com.nononsenseapps:filepicker:3.0.0'
|
compile 'com.nononsenseapps:filepicker:3.0.0'
|
||||||
compile 'ch.acra:acra:4.9.0'
|
compile 'ch.acra:acra:4.9.0'
|
||||||
|
compile 'com.devbrackets.android:exomedia:3.1.1'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||||
testCompile 'org.json:json:20160810'
|
testCompile 'org.json:json:20160810'
|
||||||
|
@ -16,549 +16,240 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended by Christian Schabesberger on 24.12.15.
|
* Extended by Christian Schabesberger on 24.12.15.
|
||||||
*
|
* <p>
|
||||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||||
* ExoPlayerActivity.java is part of NewPipe. all changes are under GPL3
|
* ExoPlayerActivity.java is part of NewPipe. all changes are under GPL3
|
||||||
*
|
* <p>
|
||||||
* NewPipe is free software: you can redistribute it and/or modify
|
* NewPipe is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
* <p>
|
||||||
* NewPipe is distributed in the hope that it will be useful,
|
* NewPipe is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
* <p>
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.schabi.newpipe.player;
|
package org.schabi.newpipe.player;
|
||||||
|
|
||||||
import org.schabi.newpipe.R;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.DashRendererBuilder;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.EventLogger;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.ExtractorRendererBuilder;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.HlsRendererBuilder;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
|
||||||
import org.schabi.newpipe.player.exoplayer.SmoothStreamingRendererBuilder;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.AspectRatioFrameLayout;
|
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
|
||||||
import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException;
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver;
|
|
||||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
|
||||||
import com.google.android.exoplayer.metadata.GeobMetadata;
|
|
||||||
import com.google.android.exoplayer.metadata.PrivMetadata;
|
|
||||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
|
||||||
import com.google.android.exoplayer.text.CaptionStyleCompat;
|
|
||||||
import com.google.android.exoplayer.text.Cue;
|
|
||||||
import com.google.android.exoplayer.text.SubtitleLayout;
|
|
||||||
import com.google.android.exoplayer.util.DebugTextViewHelper;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
|
||||||
|
|
||||||
import android.Manifest.permission;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.SurfaceHolder;
|
|
||||||
import android.view.SurfaceView;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnKeyListener;
|
import android.view.WindowManager;
|
||||||
import android.view.View.OnTouchListener;
|
import android.widget.ImageButton;
|
||||||
import android.view.accessibility.CaptioningManager;
|
import android.widget.SeekBar;
|
||||||
import android.widget.MediaController;
|
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.PopupMenu.OnMenuItemClickListener;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.net.CookieHandler;
|
import com.devbrackets.android.exomedia.listener.OnCompletionListener;
|
||||||
import java.net.CookieManager;
|
import com.devbrackets.android.exomedia.listener.OnPreparedListener;
|
||||||
import java.net.CookiePolicy;
|
import com.devbrackets.android.exomedia.listener.VideoControlsVisibilityListener;
|
||||||
import java.util.List;
|
import com.devbrackets.android.exomedia.ui.widget.EMVideoView;
|
||||||
import java.util.Locale;
|
import com.devbrackets.android.exomedia.ui.widget.VideoControlsMobile;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
import org.schabi.newpipe.R;
|
||||||
* An activity that plays media using {@link NPExoPlayer}.
|
|
||||||
*/
|
|
||||||
public class ExoPlayerActivity extends Activity {
|
|
||||||
|
|
||||||
// For use within demo app code.
|
public class ExoPlayerActivity extends Activity implements OnPreparedListener, OnCompletionListener {
|
||||||
public static final String CONTENT_ID_EXTRA = "content_id";
|
private static final String TAG = "ExoPlayerActivity";
|
||||||
public static final String CONTENT_TYPE_EXTRA = "content_type";
|
private EMVideoView videoView;
|
||||||
public static final String PROVIDER_EXTRA = "provider";
|
private CustomVideoControls videoControls;
|
||||||
|
|
||||||
// For use when launching the demo app using adb.
|
public static final String VIDEO_TITLE = "video_title";
|
||||||
private static final String CONTENT_EXT_EXTRA = "type";
|
public static final String CHANNEL_NAME = "channel_name";
|
||||||
|
private String videoTitle = "";
|
||||||
private static final String TAG = "PlayerActivity";
|
private volatile String channelName = "";
|
||||||
private static final int MENU_GROUP_TRACKS = 1;
|
private int lastPosition;
|
||||||
private static final int ID_OFFSET = 2;
|
private boolean isFinished;
|
||||||
|
|
||||||
private static final CookieManager defaultCookieManager;
|
|
||||||
static {
|
|
||||||
defaultCookieManager = new CookieManager();
|
|
||||||
defaultCookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private EventLogger eventLogger;
|
|
||||||
private MediaController mediaController;
|
|
||||||
private View shutterView;
|
|
||||||
private AspectRatioFrameLayout videoFrame;
|
|
||||||
private SurfaceView surfaceView;
|
|
||||||
private SubtitleLayout subtitleLayout;
|
|
||||||
|
|
||||||
private NPExoPlayer player;
|
|
||||||
private boolean playerNeedsPrepare;
|
|
||||||
|
|
||||||
private long playerPosition;
|
|
||||||
private boolean enableBackgroundAudio = true;
|
|
||||||
|
|
||||||
private Uri contentUri;
|
|
||||||
private int contentType;
|
|
||||||
private String contentId;
|
|
||||||
private String provider;
|
|
||||||
|
|
||||||
private AudioCapabilitiesReceiver audioCapabilitiesReceiver;
|
|
||||||
|
|
||||||
|
|
||||||
NPExoPlayer.Listener exoPlayerListener = new NPExoPlayer.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onStateChanged(boolean playWhenReady, int playbackState) {
|
|
||||||
if (playbackState == ExoPlayer.STATE_ENDED) {
|
|
||||||
showControls();
|
|
||||||
}
|
|
||||||
String text = "playWhenReady=" + playWhenReady + ", playbackState=";
|
|
||||||
switch(playbackState) {
|
|
||||||
case ExoPlayer.STATE_BUFFERING:
|
|
||||||
text += "buffering";
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_ENDED:
|
|
||||||
text += "ended";
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_IDLE:
|
|
||||||
text += "idle";
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_PREPARING:
|
|
||||||
text += "preparing";
|
|
||||||
break;
|
|
||||||
case ExoPlayer.STATE_READY:
|
|
||||||
text += "ready";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
text += "unknown";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//todo: put text in some log
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Exception e) {
|
|
||||||
String errorString = null;
|
|
||||||
if (e instanceof UnsupportedDrmException) {
|
|
||||||
// Special case DRM failures.
|
|
||||||
UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e;
|
|
||||||
errorString = getString(Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
|
||||||
: unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
|
||||||
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
|
||||||
} else if (e instanceof ExoPlaybackException
|
|
||||||
&& e.getCause() instanceof DecoderInitializationException) {
|
|
||||||
// Special case for decoder initialization failures.
|
|
||||||
DecoderInitializationException decoderInitializationException =
|
|
||||||
(DecoderInitializationException) e.getCause();
|
|
||||||
if (decoderInitializationException.decoderName == null) {
|
|
||||||
if (decoderInitializationException.getCause() instanceof DecoderQueryException) {
|
|
||||||
errorString = getString(R.string.error_querying_decoders);
|
|
||||||
} else if (decoderInitializationException.secureDecoderRequired) {
|
|
||||||
errorString = getString(R.string.error_no_secure_decoder,
|
|
||||||
decoderInitializationException.mimeType);
|
|
||||||
} else {
|
|
||||||
errorString = getString(R.string.error_no_decoder,
|
|
||||||
decoderInitializationException.mimeType);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorString = getString(R.string.error_instantiating_decoder,
|
|
||||||
decoderInitializationException.decoderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errorString != null) {
|
|
||||||
Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
playerNeedsPrepare = true;
|
|
||||||
showControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthAspectRatio) {
|
|
||||||
shutterView.setVisibility(View.GONE);
|
|
||||||
videoFrame.setAspectRatio(
|
|
||||||
height == 0 ? 1 : (width * pixelWidthAspectRatio) / height);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
|
|
||||||
@Override
|
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
|
||||||
if (player != null) {
|
|
||||||
player.setSurface(holder.getSurface());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
||||||
if (player != null) {
|
|
||||||
player.blockingClearSurface();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
NPExoPlayer.CaptionListener captionListener = new NPExoPlayer.CaptionListener() {
|
|
||||||
@Override
|
|
||||||
public void onCues(List<Cue> cues) {
|
|
||||||
subtitleLayout.setCues(cues);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
NPExoPlayer.Id3MetadataListener id3MetadataListener = new NPExoPlayer.Id3MetadataListener() {
|
|
||||||
@Override
|
|
||||||
public void onId3Metadata(Map<String, Object> metadata) {
|
|
||||||
for (Map.Entry<String, Object> entry : metadata.entrySet()) {
|
|
||||||
if (TxxxMetadata.TYPE.equals(entry.getKey())) {
|
|
||||||
TxxxMetadata txxxMetadata = (TxxxMetadata) entry.getValue();
|
|
||||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: description=%s, value=%s",
|
|
||||||
TxxxMetadata.TYPE, txxxMetadata.description, txxxMetadata.value));
|
|
||||||
} else if (PrivMetadata.TYPE.equals(entry.getKey())) {
|
|
||||||
PrivMetadata privMetadata = (PrivMetadata) entry.getValue();
|
|
||||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: owner=%s",
|
|
||||||
PrivMetadata.TYPE, privMetadata.owner));
|
|
||||||
} else if (GeobMetadata.TYPE.equals(entry.getKey())) {
|
|
||||||
GeobMetadata geobMetadata = (GeobMetadata) entry.getValue();
|
|
||||||
Log.i(TAG, String.format("ID3 TimedMetadata %s: mimeType=%s, filename=%s, description=%s",
|
|
||||||
GeobMetadata.TYPE, geobMetadata.mimeType, geobMetadata.filename,
|
|
||||||
geobMetadata.description));
|
|
||||||
} else {
|
|
||||||
Log.i(TAG, String.format("ID3 TimedMetadata %s", entry.getKey()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
AudioCapabilitiesReceiver.Listener audioCapabilitiesListener = new AudioCapabilitiesReceiver.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities) {
|
|
||||||
if (player == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean backgrounded = player.getBackgrounded();
|
|
||||||
boolean playWhenReady = player.getPlayWhenReady();
|
|
||||||
releasePlayer();
|
|
||||||
preparePlayer(playWhenReady);
|
|
||||||
player.setBackgrounded(backgrounded);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Activity lifecycle
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_exo_player);
|
||||||
setContentView(R.layout.exo_player_activity);
|
videoView = (EMVideoView) findViewById(R.id.emVideoView);
|
||||||
View root = findViewById(R.id.root);
|
|
||||||
root.setOnTouchListener(new OnTouchListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
|
||||||
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
|
||||||
toggleControlsVisibility();
|
|
||||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
|
||||||
view.performClick();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
root.setOnKeyListener(new OnKeyListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE
|
|
||||||
|| keyCode == KeyEvent.KEYCODE_MENU) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return mediaController.dispatchKeyEvent(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
shutterView = findViewById(R.id.shutter);
|
|
||||||
|
|
||||||
videoFrame = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
|
|
||||||
surfaceView = (SurfaceView) findViewById(R.id.surface_view);
|
|
||||||
surfaceView.getHolder().addCallback(surfaceHolderCallback);
|
|
||||||
subtitleLayout = (SubtitleLayout) findViewById(R.id.subtitles);
|
|
||||||
|
|
||||||
//todo: replace that creapy mediaController
|
|
||||||
mediaController = new KeyCompatibleMediaController(this);
|
|
||||||
mediaController.setAnchorView(root);
|
|
||||||
|
|
||||||
//todo: check what cookie handler does, and if we even need it
|
|
||||||
CookieHandler currentHandler = CookieHandler.getDefault();
|
|
||||||
if (currentHandler != defaultCookieManager) {
|
|
||||||
CookieHandler.setDefault(defaultCookieManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(this, audioCapabilitiesListener);
|
|
||||||
audioCapabilitiesReceiver.register();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
protected void onStart() {
|
||||||
releasePlayer();
|
super.onStart();
|
||||||
playerPosition = 0;
|
|
||||||
setIntent(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
contentUri = intent.getData();
|
videoTitle = intent.getStringExtra(VIDEO_TITLE);
|
||||||
contentType = intent.getIntExtra(CONTENT_TYPE_EXTRA,
|
channelName = intent.getStringExtra(CHANNEL_NAME);
|
||||||
inferContentType(contentUri, intent.getStringExtra(CONTENT_EXT_EXTRA)));
|
videoView.setOnPreparedListener(this);
|
||||||
contentId = intent.getStringExtra(CONTENT_ID_EXTRA);
|
videoView.setOnCompletionListener(this);
|
||||||
provider = intent.getStringExtra(PROVIDER_EXTRA);
|
videoView.setVideoURI(intent.getData());
|
||||||
configureSubtitleView();
|
|
||||||
if (player == null) {
|
videoControls = new CustomVideoControls(this);
|
||||||
if (!maybeRequestPermission()) {
|
videoControls.setTitle(videoTitle);
|
||||||
preparePlayer(true);
|
videoControls.setSubTitle(channelName);
|
||||||
|
|
||||||
|
//We don't need these button until the playlist or queue is implemented
|
||||||
|
videoControls.setNextButtonRemoved(true);
|
||||||
|
videoControls.setPreviousButtonRemoved(true);
|
||||||
|
|
||||||
|
videoControls.setVisibilityListener(new VideoControlsVisibilityListener() {
|
||||||
|
@Override
|
||||||
|
public void onControlsShown() {
|
||||||
|
Log.d(TAG, "------------ onControlsShown() called");
|
||||||
|
showSystemUi();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
player.setBackgrounded(false);
|
@Override
|
||||||
}
|
public void onControlsHidden() {
|
||||||
|
Log.d(TAG, "------------ onControlsHidden() called");
|
||||||
|
hideSystemUi();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
videoView.setControls(videoControls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPrepared() {
|
||||||
|
Log.d(TAG, "onPrepared() called");
|
||||||
|
videoView.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompletion() {
|
||||||
|
Log.d(TAG, "onCompletion() called");
|
||||||
|
// videoView.getVideoControls().setButtonListener();
|
||||||
|
//videoView.restart();
|
||||||
|
videoControls.setRewindButtonRemoved(true);
|
||||||
|
videoControls.setFastForwardButtonRemoved(true);
|
||||||
|
isFinished = true;
|
||||||
|
videoControls.getSeekBar().setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (!enableBackgroundAudio) {
|
videoView.stopPlayback();
|
||||||
releasePlayer();
|
lastPosition = videoView.getCurrentPosition();
|
||||||
} else {
|
|
||||||
player.setBackgrounded(true);
|
|
||||||
}
|
|
||||||
shutterView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (lastPosition > 0) videoView.seekTo(lastPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
audioCapabilitiesReceiver.unregister();
|
videoView.stopPlayback();
|
||||||
releasePlayer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showSystemUi() {
|
||||||
|
Log.d(TAG, "showSystemUi() called");
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Permission request listener method
|
private void hideSystemUi() {
|
||||||
|
Log.d(TAG, "hideSystemUi() called");
|
||||||
@Override
|
if (android.os.Build.VERSION.SDK_INT >= 17) {
|
||||||
public void onRequestPermissionsResult(int requestCode, String[] permissions,
|
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
int[] grantResults) {
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
preparePlayer(true);
|
| View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||||
} else {
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||||
Toast.makeText(getApplicationContext(), R.string.storage_permission_denied,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Permission management methods
|
private class CustomVideoControls extends VideoControlsMobile {
|
||||||
|
protected static final int FAST_FORWARD_REWIND_AMOUNT = 8000;
|
||||||
|
|
||||||
/**
|
protected ImageButton fastForwardButton;
|
||||||
* Checks whether it is necessary to ask for permission to read storage. If necessary, it also
|
protected ImageButton rewindButton;
|
||||||
* requests permission.
|
|
||||||
*
|
|
||||||
* @return true if a permission request is made. False if it is not necessary.
|
|
||||||
*/
|
|
||||||
@TargetApi(23)
|
|
||||||
private boolean maybeRequestPermission() {
|
|
||||||
if (requiresPermission(contentUri)) {
|
|
||||||
requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(23)
|
public CustomVideoControls(Context context) {
|
||||||
private boolean requiresPermission(Uri uri) {
|
|
||||||
return Util.SDK_INT >= 23
|
|
||||||
&& Util.isLocalFileUri(uri)
|
|
||||||
&& checkSelfPermission(permission.READ_EXTERNAL_STORAGE)
|
|
||||||
!= PackageManager.PERMISSION_GRANTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal methods
|
|
||||||
|
|
||||||
private RendererBuilder getRendererBuilder() {
|
|
||||||
String userAgent = Util.getUserAgent(this, "NewPipeExoPlayer");
|
|
||||||
switch (contentType) {
|
|
||||||
case Util.TYPE_SS:
|
|
||||||
// default
|
|
||||||
//return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString());
|
|
||||||
case Util.TYPE_DASH:
|
|
||||||
// if a dash manifest is available
|
|
||||||
//return new DashRendererBuilder(this, userAgent, contentUri.toString());
|
|
||||||
case Util.TYPE_HLS:
|
|
||||||
// for livestreams
|
|
||||||
return new HlsRendererBuilder(this, userAgent, contentUri.toString());
|
|
||||||
case Util.TYPE_OTHER:
|
|
||||||
// video only streaming
|
|
||||||
return new ExtractorRendererBuilder(this, userAgent, contentUri);
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unsupported type: " + contentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void preparePlayer(boolean playWhenReady) {
|
|
||||||
if (player == null) {
|
|
||||||
player = new NPExoPlayer(getRendererBuilder());
|
|
||||||
player.addListener(exoPlayerListener);
|
|
||||||
player.setCaptionListener(captionListener);
|
|
||||||
player.setMetadataListener(id3MetadataListener);
|
|
||||||
player.seekTo(playerPosition);
|
|
||||||
playerNeedsPrepare = true;
|
|
||||||
mediaController.setMediaPlayer(player.getPlayerControl());
|
|
||||||
mediaController.setEnabled(true);
|
|
||||||
eventLogger = new EventLogger();
|
|
||||||
eventLogger.startSession();
|
|
||||||
player.addListener(eventLogger);
|
|
||||||
player.setInfoListener(eventLogger);
|
|
||||||
player.setInternalErrorListener(eventLogger);
|
|
||||||
}
|
|
||||||
if (playerNeedsPrepare) {
|
|
||||||
player.prepare();
|
|
||||||
playerNeedsPrepare = false;
|
|
||||||
}
|
|
||||||
player.setSurface(surfaceView.getHolder().getSurface());
|
|
||||||
player.setPlayWhenReady(playWhenReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void releasePlayer() {
|
|
||||||
if (player != null) {
|
|
||||||
playerPosition = player.getCurrentPosition();
|
|
||||||
player.release();
|
|
||||||
player = null;
|
|
||||||
eventLogger.endSession();
|
|
||||||
eventLogger = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toggleControlsVisibility() {
|
|
||||||
if (mediaController.isShowing()) {
|
|
||||||
mediaController.hide();
|
|
||||||
} else {
|
|
||||||
showControls();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showControls() {
|
|
||||||
mediaController.show(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureSubtitleView() {
|
|
||||||
CaptionStyleCompat style;
|
|
||||||
float fontScale;
|
|
||||||
if (Util.SDK_INT >= 19) {
|
|
||||||
style = getUserCaptionStyleV19();
|
|
||||||
fontScale = getUserCaptionFontScaleV19();
|
|
||||||
} else {
|
|
||||||
style = CaptionStyleCompat.DEFAULT;
|
|
||||||
fontScale = 1.0f;
|
|
||||||
}
|
|
||||||
subtitleLayout.setStyle(style);
|
|
||||||
subtitleLayout.setFractionalTextSize(SubtitleLayout.DEFAULT_TEXT_SIZE_FRACTION * fontScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(19)
|
|
||||||
private float getUserCaptionFontScaleV19() {
|
|
||||||
CaptioningManager captioningManager =
|
|
||||||
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
|
|
||||||
return captioningManager.getFontScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(19)
|
|
||||||
private CaptionStyleCompat getUserCaptionStyleV19() {
|
|
||||||
CaptioningManager captioningManager =
|
|
||||||
(CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
|
|
||||||
return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes a best guess to infer the type from a media {@link Uri} and an optional overriding file
|
|
||||||
* extension.
|
|
||||||
*
|
|
||||||
* @param uri The {@link Uri} of the media.
|
|
||||||
* @param fileExtension An overriding file extension.
|
|
||||||
* @return The inferred type.
|
|
||||||
*/
|
|
||||||
private static int inferContentType(Uri uri, String fileExtension) {
|
|
||||||
String lastPathSegment = !TextUtils.isEmpty(fileExtension) ? "." + fileExtension
|
|
||||||
: uri.getLastPathSegment();
|
|
||||||
return Util.inferContentType(lastPathSegment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class KeyCompatibleMediaController extends MediaController {
|
|
||||||
|
|
||||||
private MediaController.MediaPlayerControl playerControl;
|
|
||||||
|
|
||||||
public KeyCompatibleMediaController(Context context) {
|
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMediaPlayer(MediaController.MediaPlayerControl playerControl) {
|
protected int getLayoutResource() {
|
||||||
super.setMediaPlayer(playerControl);
|
return R.layout.exomedia_custom_controls;
|
||||||
this.playerControl = playerControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
protected void retrieveViews() {
|
||||||
int keyCode = event.getKeyCode();
|
super.retrieveViews();
|
||||||
if (playerControl.canSeekForward() && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
rewindButton = (ImageButton) findViewById(R.id.exomedia_controls_frewind_btn);
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
fastForwardButton = (ImageButton) findViewById(R.id.exomedia_controls_fforward_btn);
|
||||||
playerControl.seekTo(playerControl.getCurrentPosition() + 15000); // milliseconds
|
}
|
||||||
show();
|
|
||||||
|
@Override
|
||||||
|
protected void registerListeners() {
|
||||||
|
super.registerListeners();
|
||||||
|
rewindButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onRewindClicked();
|
||||||
}
|
}
|
||||||
return true;
|
});
|
||||||
} else if (playerControl.canSeekBackward() && keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) {
|
fastForwardButton.setOnClickListener(new OnClickListener() {
|
||||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
@Override
|
||||||
playerControl.seekTo(playerControl.getCurrentPosition() - 5000); // milliseconds
|
public void onClick(View v) {
|
||||||
show();
|
onFastForwardClicked();
|
||||||
}
|
}
|
||||||
return true;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFastForwardClicked() {
|
||||||
|
if (videoView == null) return false;
|
||||||
|
|
||||||
|
int newPosition = videoView.getCurrentPosition() + FAST_FORWARD_REWIND_AMOUNT;
|
||||||
|
if (newPosition > seekBar.getMax()) newPosition = seekBar.getMax();
|
||||||
|
|
||||||
|
performSeek(newPosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onRewindClicked() {
|
||||||
|
if (videoView == null) return false;
|
||||||
|
|
||||||
|
int newPosition = videoView.getCurrentPosition() - FAST_FORWARD_REWIND_AMOUNT;
|
||||||
|
if (newPosition < 0) newPosition = 0;
|
||||||
|
|
||||||
|
performSeek(newPosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFastForwardButtonRemoved(boolean removed) {
|
||||||
|
fastForwardButton.setVisibility(removed ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRewindButtonRemoved(boolean removed) {
|
||||||
|
rewindButton.setVisibility(removed ? View.GONE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPlayPauseClick() {
|
||||||
|
super.onPlayPauseClick();
|
||||||
|
if (videoView == null) return;
|
||||||
|
Log.d(TAG, "onPlayPauseClick() called" + videoView.getDuration()+" position= "+ videoView.getCurrentPosition());
|
||||||
|
if (isFinished) {
|
||||||
|
videoView.restart();
|
||||||
|
setRewindButtonRemoved(false);
|
||||||
|
setFastForwardButtonRemoved(false);
|
||||||
|
isFinished = false;
|
||||||
|
seekBar.setEnabled(true);
|
||||||
}
|
}
|
||||||
return super.dispatchKeyEvent(event);
|
}
|
||||||
|
|
||||||
|
private void performSeek(int newPosition) {
|
||||||
|
internalListener.onSeekEnded(newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeekBar getSeekBar() {
|
||||||
|
return seekBar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
|
||||||
import com.google.android.exoplayer.LoadControl;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
|
||||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
|
||||||
import com.google.android.exoplayer.dash.DefaultDashTrackSelector;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.Period;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElement;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver;
|
|
||||||
import com.google.android.exoplayer.dash.mpd.UtcTimingElementResolver.UtcTimingCallback;
|
|
||||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
|
||||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
|
||||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.UriDataSource;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RendererBuilder} for DASH.
|
|
||||||
*/
|
|
||||||
public class DashRendererBuilder implements RendererBuilder {
|
|
||||||
|
|
||||||
private static final String TAG = "DashRendererBuilder";
|
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
|
||||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
|
||||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
|
||||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
|
||||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
|
||||||
|
|
||||||
private static final int SECURITY_LEVEL_UNKNOWN = -1;
|
|
||||||
private static final int SECURITY_LEVEL_1 = 1;
|
|
||||||
private static final int SECURITY_LEVEL_3 = 3;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final String url;
|
|
||||||
private final MediaDrmCallback drmCallback;
|
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public DashRendererBuilder(Context context, String userAgent, String url,
|
|
||||||
MediaDrmCallback drmCallback) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.url = url;
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildRenderers(NPExoPlayer player) {
|
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
|
||||||
currentAsyncBuilder.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
if (currentAsyncBuilder != null) {
|
|
||||||
currentAsyncBuilder.cancel();
|
|
||||||
currentAsyncBuilder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder
|
|
||||||
implements ManifestFetcher.ManifestCallback<MediaPresentationDescription>, UtcTimingCallback {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final MediaDrmCallback drmCallback;
|
|
||||||
private final NPExoPlayer player;
|
|
||||||
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
|
|
||||||
private final UriDataSource manifestDataSource;
|
|
||||||
|
|
||||||
private boolean canceled;
|
|
||||||
private MediaPresentationDescription manifest;
|
|
||||||
private long elapsedRealtimeOffset;
|
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
|
||||||
MediaDrmCallback drmCallback, NPExoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
this.player = player;
|
|
||||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
|
||||||
manifestDataSource = new DefaultUriDataSource(context, userAgent);
|
|
||||||
manifestFetcher = new ManifestFetcher<>(url, manifestDataSource, parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(MediaPresentationDescription manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.manifest = manifest;
|
|
||||||
if (manifest.dynamic && manifest.utcTiming != null) {
|
|
||||||
UtcTimingElementResolver.resolveTimingElement(manifestDataSource, manifest.utcTiming,
|
|
||||||
manifestFetcher.getManifestLoadCompleteTimestamp(), this);
|
|
||||||
} else {
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onRenderersError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.elapsedRealtimeOffset = elapsedRealtimeOffset;
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimestampError(UtcTimingElement utcTiming, IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.e(TAG, "Failed to resolve UtcTiming element [" + utcTiming + "]", e);
|
|
||||||
// Be optimistic and continue in the hope that the device clock is correct.
|
|
||||||
buildRenderers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildRenderers() {
|
|
||||||
Period period = manifest.getPeriod(0);
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
|
|
||||||
|
|
||||||
boolean hasContentProtection = false;
|
|
||||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
|
||||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
|
||||||
if (adaptationSet.type != AdaptationSet.TYPE_UNKNOWN) {
|
|
||||||
hasContentProtection |= adaptationSet.hasContentProtection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check drm support if necessary.
|
|
||||||
boolean filterHdContent = false;
|
|
||||||
StreamingDrmSessionManager drmSessionManager = null;
|
|
||||||
if (hasContentProtection) {
|
|
||||||
if (Util.SDK_INT < 18) {
|
|
||||||
player.onRenderersError(
|
|
||||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(
|
|
||||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
|
||||||
filterHdContent = getWidevineSecurityLevel(drmSessionManager) != SECURITY_LEVEL_1;
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
player.onRenderersError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the video renderer.
|
|
||||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource videoChunkSource = new DashChunkSource(manifestFetcher,
|
|
||||||
DefaultDashTrackSelector.newVideoInstance(context, true, filterHdContent),
|
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS,
|
|
||||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_VIDEO);
|
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
|
||||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_VIDEO);
|
|
||||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
|
||||||
drmSessionManager, true, mainHandler, player, 50);
|
|
||||||
|
|
||||||
// Build the audio renderer.
|
|
||||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource audioChunkSource = new DashChunkSource(manifestFetcher,
|
|
||||||
DefaultDashTrackSelector.newAudioInstance(), audioDataSource, null, LIVE_EDGE_LATENCY_MS,
|
|
||||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_AUDIO);
|
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_AUDIO);
|
|
||||||
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, player,
|
|
||||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
|
||||||
|
|
||||||
// Build the text renderer.
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource textChunkSource = new DashChunkSource(manifestFetcher,
|
|
||||||
DefaultDashTrackSelector.newTextInstance(), textDataSource, null, LIVE_EDGE_LATENCY_MS,
|
|
||||||
elapsedRealtimeOffset, mainHandler, player, NPExoPlayer.TYPE_TEXT);
|
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_TEXT);
|
|
||||||
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player,
|
|
||||||
mainHandler.getLooper());
|
|
||||||
|
|
||||||
// Invoke the callback.
|
|
||||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
|
||||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
|
||||||
player.onRenderers(renderers, bandwidthMeter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
|
|
||||||
String securityLevelProperty = sessionManager.getPropertyString("securityLevel");
|
|
||||||
return securityLevelProperty.equals("L1") ? SECURITY_LEVEL_1 : securityLevelProperty
|
|
||||||
.equals("L3") ? SECURITY_LEVEL_3 : SECURITY_LEVEL_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.audio.AudioTrack;
|
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
|
||||||
import com.google.android.exoplayer.util.VerboseLogUtil;
|
|
||||||
|
|
||||||
import android.media.MediaCodec.CryptoException;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs player events using {@link Log}.
|
|
||||||
*/
|
|
||||||
public class EventLogger implements NPExoPlayer.Listener, NPExoPlayer.InfoListener,
|
|
||||||
NPExoPlayer.InternalErrorListener {
|
|
||||||
|
|
||||||
private static final String TAG = "EventLogger";
|
|
||||||
private static final NumberFormat TIME_FORMAT;
|
|
||||||
static {
|
|
||||||
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
|
|
||||||
TIME_FORMAT.setMinimumFractionDigits(2);
|
|
||||||
TIME_FORMAT.setMaximumFractionDigits(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long sessionStartTimeMs;
|
|
||||||
private long[] loadStartTimeMs;
|
|
||||||
private long[] availableRangeValuesUs;
|
|
||||||
|
|
||||||
public EventLogger() {
|
|
||||||
loadStartTimeMs = new long[NPExoPlayer.RENDERER_COUNT];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startSession() {
|
|
||||||
sessionStartTimeMs = SystemClock.elapsedRealtime();
|
|
||||||
Log.d(TAG, "start [0]");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void endSession() {
|
|
||||||
Log.d(TAG, "end [" + getSessionTimeString() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// NPExoPlayer.Listener
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStateChanged(boolean playWhenReady, int state) {
|
|
||||||
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", "
|
|
||||||
+ getStateString(state) + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Exception e) {
|
|
||||||
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
|
||||||
float pixelWidthHeightRatio) {
|
|
||||||
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + ", " + unappliedRotationDegrees
|
|
||||||
+ ", " + pixelWidthHeightRatio + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// NPExoPlayer.InfoListener
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
|
|
||||||
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes + ", "
|
|
||||||
+ getTimeString(elapsedMs) + ", " + bitrateEstimate + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDroppedFrames(int count, long elapsed) {
|
|
||||||
Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs) {
|
|
||||||
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
|
|
||||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
|
||||||
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId + ", " + type
|
|
||||||
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
|
|
||||||
if (VerboseLogUtil.isTagEnabled(TAG)) {
|
|
||||||
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
|
|
||||||
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " + downloadTime
|
|
||||||
+ "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs) {
|
|
||||||
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + format.id + ", "
|
|
||||||
+ Integer.toString(trigger) + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs) {
|
|
||||||
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + format.id + ", "
|
|
||||||
+ Integer.toString(trigger) + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// NPExoPlayer.InternalErrorListener
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadError(int sourceId, IOException e) {
|
|
||||||
printInternalError("loadError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRendererInitializationError(Exception e) {
|
|
||||||
printInternalError("rendererInitError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrmSessionManagerError(Exception e) {
|
|
||||||
printInternalError("drmSessionManagerError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDecoderInitializationError(DecoderInitializationException e) {
|
|
||||||
printInternalError("decoderInitializationError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
|
|
||||||
printInternalError("audioTrackInitializationError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
|
|
||||||
printInternalError("audioTrackWriteError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
|
||||||
printInternalError("audioTrackUnderrun [" + bufferSize + ", " + bufferSizeMs + ", "
|
|
||||||
+ elapsedSinceLastFeedMs + "]", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCryptoError(CryptoException e) {
|
|
||||||
printInternalError("cryptoError", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
|
||||||
long initializationDurationMs) {
|
|
||||||
Log.d(TAG, "decoderInitialized [" + getSessionTimeString() + ", " + decoderName + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
|
||||||
availableRangeValuesUs = availableRange.getCurrentBoundsUs(availableRangeValuesUs);
|
|
||||||
Log.d(TAG, "availableRange [" + availableRange.isStatic() + ", " + availableRangeValuesUs[0]
|
|
||||||
+ ", " + availableRangeValuesUs[1] + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printInternalError(String type, Exception e) {
|
|
||||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStateString(int state) {
|
|
||||||
switch (state) {
|
|
||||||
case ExoPlayer.STATE_BUFFERING:
|
|
||||||
return "B";
|
|
||||||
case ExoPlayer.STATE_ENDED:
|
|
||||||
return "E";
|
|
||||||
case ExoPlayer.STATE_IDLE:
|
|
||||||
return "I";
|
|
||||||
case ExoPlayer.STATE_PREPARING:
|
|
||||||
return "P";
|
|
||||||
case ExoPlayer.STATE_READY:
|
|
||||||
return "R";
|
|
||||||
default:
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSessionTimeString() {
|
|
||||||
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTimeString(long timeMs) {
|
|
||||||
return TIME_FORMAT.format((timeMs) / 1000f);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
|
||||||
import com.google.android.exoplayer.extractor.Extractor;
|
|
||||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
|
||||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.upstream.Allocator;
|
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RendererBuilder} for streams that can be read using an {@link Extractor}.
|
|
||||||
*/
|
|
||||||
public class ExtractorRendererBuilder implements RendererBuilder {
|
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
|
||||||
private static final int BUFFER_SEGMENT_COUNT = 256;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final Uri uri;
|
|
||||||
|
|
||||||
public ExtractorRendererBuilder(Context context, String userAgent, Uri uri) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.uri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildRenderers(NPExoPlayer player) {
|
|
||||||
Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
|
|
||||||
|
|
||||||
// Build the video and audio renderers.
|
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(player.getMainHandler(),
|
|
||||||
null);
|
|
||||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator,
|
|
||||||
BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE);
|
|
||||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
|
||||||
sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
|
||||||
player.getMainHandler(), player, 50);
|
|
||||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player,
|
|
||||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
|
||||||
TrackRenderer textRenderer = new TextTrackRenderer(sampleSource, player,
|
|
||||||
player.getMainHandler().getLooper());
|
|
||||||
|
|
||||||
// Invoke the callback.
|
|
||||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
|
||||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
|
||||||
player.onRenderers(renderers, bandwidthMeter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
|
||||||
import com.google.android.exoplayer.LoadControl;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
|
||||||
import com.google.android.exoplayer.hls.DefaultHlsTrackSelector;
|
|
||||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
|
||||||
import com.google.android.exoplayer.hls.HlsMasterPlaylist;
|
|
||||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
|
||||||
import com.google.android.exoplayer.hls.HlsPlaylistParser;
|
|
||||||
import com.google.android.exoplayer.hls.HlsSampleSource;
|
|
||||||
import com.google.android.exoplayer.hls.PtsTimestampAdjusterProvider;
|
|
||||||
import com.google.android.exoplayer.metadata.Id3Parser;
|
|
||||||
import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.text.eia608.Eia608TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.os.Handler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RendererBuilder} for HLS.
|
|
||||||
*/
|
|
||||||
public class HlsRendererBuilder implements RendererBuilder {
|
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
|
||||||
private static final int MAIN_BUFFER_SEGMENTS = 256;
|
|
||||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public HlsRendererBuilder(Context context, String userAgent, String url) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildRenderers(NPExoPlayer player) {
|
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, player);
|
|
||||||
currentAsyncBuilder.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
if (currentAsyncBuilder != null) {
|
|
||||||
currentAsyncBuilder.cancel();
|
|
||||||
currentAsyncBuilder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder implements ManifestCallback<HlsPlaylist> {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final String url;
|
|
||||||
private final NPExoPlayer player;
|
|
||||||
private final ManifestFetcher<HlsPlaylist> playlistFetcher;
|
|
||||||
|
|
||||||
private boolean canceled;
|
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url, NPExoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.url = url;
|
|
||||||
this.player = player;
|
|
||||||
HlsPlaylistParser parser = new HlsPlaylistParser();
|
|
||||||
playlistFetcher = new ManifestFetcher<>(url, new DefaultUriDataSource(context, userAgent),
|
|
||||||
parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
playlistFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException e) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onRenderersError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(HlsPlaylist manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
|
||||||
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
|
||||||
|
|
||||||
// Build the video/audio/metadata renderers.
|
|
||||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
HlsChunkSource chunkSource = new HlsChunkSource(true /* isMaster */, dataSource, url,
|
|
||||||
manifest, DefaultHlsTrackSelector.newDefaultInstance(context), bandwidthMeter,
|
|
||||||
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
|
||||||
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl,
|
|
||||||
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, NPExoPlayer.TYPE_VIDEO);
|
|
||||||
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context,
|
|
||||||
sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT,
|
|
||||||
5000, mainHandler, player, 50);
|
|
||||||
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, null, true, player.getMainHandler(), player,
|
|
||||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
|
||||||
MetadataTrackRenderer<Map<String, Object>> id3Renderer = new MetadataTrackRenderer<>(
|
|
||||||
sampleSource, new Id3Parser(), player, mainHandler.getLooper());
|
|
||||||
|
|
||||||
// Build the text renderer, preferring Webvtt where available.
|
|
||||||
boolean preferWebvtt = false;
|
|
||||||
if (manifest instanceof HlsMasterPlaylist) {
|
|
||||||
preferWebvtt = !((HlsMasterPlaylist) manifest).subtitles.isEmpty();
|
|
||||||
}
|
|
||||||
TrackRenderer textRenderer;
|
|
||||||
if (preferWebvtt) {
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
HlsChunkSource textChunkSource = new HlsChunkSource(false /* isMaster */, textDataSource,
|
|
||||||
url, manifest, DefaultHlsTrackSelector.newVttInstance(), bandwidthMeter,
|
|
||||||
timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
|
||||||
HlsSampleSource textSampleSource = new HlsSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, NPExoPlayer.TYPE_TEXT);
|
|
||||||
textRenderer = new TextTrackRenderer(textSampleSource, player, mainHandler.getLooper());
|
|
||||||
} else {
|
|
||||||
textRenderer = new Eia608TrackRenderer(sampleSource, player, mainHandler.getLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
|
||||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_METADATA] = id3Renderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
|
||||||
player.onRenderers(renderers, bandwidthMeter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,599 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.CodecCounters;
|
|
||||||
import com.google.android.exoplayer.DummyTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaFormat;
|
|
||||||
import com.google.android.exoplayer.TimeRange;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.audio.AudioTrack;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
|
||||||
import com.google.android.exoplayer.chunk.Format;
|
|
||||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
|
||||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
|
||||||
import com.google.android.exoplayer.hls.HlsSampleSource;
|
|
||||||
import com.google.android.exoplayer.metadata.MetadataTrackRenderer.MetadataRenderer;
|
|
||||||
import com.google.android.exoplayer.text.Cue;
|
|
||||||
import com.google.android.exoplayer.text.TextRenderer;
|
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.util.DebugTextViewHelper;
|
|
||||||
import com.google.android.exoplayer.util.PlayerControl;
|
|
||||||
|
|
||||||
import android.media.MediaCodec.CryptoException;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.view.Surface;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper around {@link ExoPlayer} that provides a higher level interface. It can be prepared
|
|
||||||
* with one of a number of {@link RendererBuilder} classes to suit different use cases (e.g. DASH,
|
|
||||||
* SmoothStreaming and so on).
|
|
||||||
*/
|
|
||||||
public class NPExoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventListener,
|
|
||||||
HlsSampleSource.EventListener, DefaultBandwidthMeter.EventListener,
|
|
||||||
MediaCodecVideoTrackRenderer.EventListener, MediaCodecAudioTrackRenderer.EventListener,
|
|
||||||
StreamingDrmSessionManager.EventListener, DashChunkSource.EventListener, TextRenderer,
|
|
||||||
MetadataRenderer<Map<String, Object>>, DebugTextViewHelper.Provider {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds renderers for the player.
|
|
||||||
*/
|
|
||||||
public interface RendererBuilder {
|
|
||||||
/**
|
|
||||||
* Builds renderers for playback.
|
|
||||||
*
|
|
||||||
* @param player The player for which renderers are being built. {@link NPExoPlayer#onRenderers}
|
|
||||||
* should be invoked once the renderers have been built. If building fails,
|
|
||||||
* {@link NPExoPlayer#onRenderersError} should be invoked.
|
|
||||||
*/
|
|
||||||
void buildRenderers(NPExoPlayer player);
|
|
||||||
/**
|
|
||||||
* Cancels the current build operation, if there is one. Else does nothing.
|
|
||||||
* <p>
|
|
||||||
* A canceled build operation must not invoke {@link NPExoPlayer#onRenderers} or
|
|
||||||
* {@link NPExoPlayer#onRenderersError} on the player, which may have been released.
|
|
||||||
*/
|
|
||||||
void cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for core events.
|
|
||||||
*/
|
|
||||||
public interface Listener {
|
|
||||||
void onStateChanged(boolean playWhenReady, int playbackState);
|
|
||||||
void onError(Exception e);
|
|
||||||
void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
|
||||||
float pixelWidthHeightRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for internal errors.
|
|
||||||
* <p>
|
|
||||||
* These errors are not visible to the user, and hence this listener is provided for
|
|
||||||
* informational purposes only. Note however that an internal error may cause a fatal
|
|
||||||
* error if the player fails to recover. If this happens, {@link Listener#onError(Exception)}
|
|
||||||
* will be invoked.
|
|
||||||
*/
|
|
||||||
public interface InternalErrorListener {
|
|
||||||
void onRendererInitializationError(Exception e);
|
|
||||||
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
|
||||||
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
|
||||||
void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
|
|
||||||
void onDecoderInitializationError(DecoderInitializationException e);
|
|
||||||
void onCryptoError(CryptoException e);
|
|
||||||
void onLoadError(int sourceId, IOException e);
|
|
||||||
void onDrmSessionManagerError(Exception e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for debugging information.
|
|
||||||
*/
|
|
||||||
public interface InfoListener {
|
|
||||||
void onVideoFormatEnabled(Format format, int trigger, long mediaTimeMs);
|
|
||||||
void onAudioFormatEnabled(Format format, int trigger, long mediaTimeMs);
|
|
||||||
void onDroppedFrames(int count, long elapsed);
|
|
||||||
void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate);
|
|
||||||
void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs);
|
|
||||||
void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs);
|
|
||||||
void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
|
||||||
long initializationDurationMs);
|
|
||||||
void onAvailableRangeChanged(int sourceId, TimeRange availableRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for receiving notifications of timed text.
|
|
||||||
*/
|
|
||||||
public interface CaptionListener {
|
|
||||||
void onCues(List<Cue> cues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for receiving ID3 metadata parsed from the media stream.
|
|
||||||
*/
|
|
||||||
public interface Id3MetadataListener {
|
|
||||||
void onId3Metadata(Map<String, Object> metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constants pulled into this class for convenience.
|
|
||||||
public static final int STATE_IDLE = ExoPlayer.STATE_IDLE;
|
|
||||||
public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING;
|
|
||||||
public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING;
|
|
||||||
public static final int STATE_READY = ExoPlayer.STATE_READY;
|
|
||||||
public static final int STATE_ENDED = ExoPlayer.STATE_ENDED;
|
|
||||||
public static final int TRACK_DISABLED = ExoPlayer.TRACK_DISABLED;
|
|
||||||
public static final int TRACK_DEFAULT = ExoPlayer.TRACK_DEFAULT;
|
|
||||||
|
|
||||||
public static final int RENDERER_COUNT = 4;
|
|
||||||
public static final int TYPE_VIDEO = 0;
|
|
||||||
public static final int TYPE_AUDIO = 1;
|
|
||||||
public static final int TYPE_TEXT = 2;
|
|
||||||
public static final int TYPE_METADATA = 3;
|
|
||||||
|
|
||||||
private static final int RENDERER_BUILDING_STATE_IDLE = 1;
|
|
||||||
private static final int RENDERER_BUILDING_STATE_BUILDING = 2;
|
|
||||||
private static final int RENDERER_BUILDING_STATE_BUILT = 3;
|
|
||||||
|
|
||||||
private final RendererBuilder rendererBuilder;
|
|
||||||
private final ExoPlayer player;
|
|
||||||
private final PlayerControl playerControl;
|
|
||||||
private final Handler mainHandler;
|
|
||||||
private final CopyOnWriteArrayList<Listener> listeners;
|
|
||||||
|
|
||||||
private int rendererBuildingState;
|
|
||||||
private int lastReportedPlaybackState;
|
|
||||||
private boolean lastReportedPlayWhenReady;
|
|
||||||
|
|
||||||
private Surface surface;
|
|
||||||
private TrackRenderer videoRenderer;
|
|
||||||
private CodecCounters codecCounters;
|
|
||||||
private Format videoFormat;
|
|
||||||
private int videoTrackToRestore;
|
|
||||||
|
|
||||||
private BandwidthMeter bandwidthMeter;
|
|
||||||
private boolean backgrounded;
|
|
||||||
|
|
||||||
private CaptionListener captionListener;
|
|
||||||
private Id3MetadataListener id3MetadataListener;
|
|
||||||
private InternalErrorListener internalErrorListener;
|
|
||||||
private InfoListener infoListener;
|
|
||||||
|
|
||||||
public NPExoPlayer(RendererBuilder rendererBuilder) {
|
|
||||||
this.rendererBuilder = rendererBuilder;
|
|
||||||
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
|
|
||||||
player.addListener(this);
|
|
||||||
playerControl = new PlayerControl(player);
|
|
||||||
mainHandler = new Handler();
|
|
||||||
listeners = new CopyOnWriteArrayList<>();
|
|
||||||
lastReportedPlaybackState = STATE_IDLE;
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
|
||||||
// Disable text initially.
|
|
||||||
player.setSelectedTrack(TYPE_TEXT, TRACK_DISABLED);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerControl getPlayerControl() {
|
|
||||||
return playerControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addListener(Listener listener) {
|
|
||||||
listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeListener(Listener listener) {
|
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInternalErrorListener(InternalErrorListener listener) {
|
|
||||||
internalErrorListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInfoListener(InfoListener listener) {
|
|
||||||
infoListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCaptionListener(CaptionListener listener) {
|
|
||||||
captionListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMetadataListener(Id3MetadataListener listener) {
|
|
||||||
id3MetadataListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSurface(Surface surface) {
|
|
||||||
this.surface = surface;
|
|
||||||
pushSurface(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Surface getSurface() {
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void blockingClearSurface() {
|
|
||||||
surface = null;
|
|
||||||
pushSurface(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTrackCount(int type) {
|
|
||||||
return player.getTrackCount(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaFormat getTrackFormat(int type, int index) {
|
|
||||||
return player.getTrackFormat(type, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSelectedTrack(int type) {
|
|
||||||
return player.getSelectedTrack(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedTrack(int type, int index) {
|
|
||||||
player.setSelectedTrack(type, index);
|
|
||||||
if (type == TYPE_TEXT && index < 0 && captionListener != null) {
|
|
||||||
captionListener.onCues(Collections.<Cue>emptyList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getBackgrounded() {
|
|
||||||
return backgrounded;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBackgrounded(boolean backgrounded) {
|
|
||||||
if (this.backgrounded == backgrounded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.backgrounded = backgrounded;
|
|
||||||
if (backgrounded) {
|
|
||||||
videoTrackToRestore = getSelectedTrack(TYPE_VIDEO);
|
|
||||||
setSelectedTrack(TYPE_VIDEO, TRACK_DISABLED);
|
|
||||||
blockingClearSurface();
|
|
||||||
} else {
|
|
||||||
setSelectedTrack(TYPE_VIDEO, videoTrackToRestore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prepare() {
|
|
||||||
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) {
|
|
||||||
player.stop();
|
|
||||||
}
|
|
||||||
rendererBuilder.cancel();
|
|
||||||
videoFormat = null;
|
|
||||||
videoRenderer = null;
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING;
|
|
||||||
maybeReportPlayerState();
|
|
||||||
rendererBuilder.buildRenderers(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked with the results from a {@link RendererBuilder}.
|
|
||||||
*
|
|
||||||
* @param renderers Renderers indexed by {@link NPExoPlayer} TYPE_* constants. An individual
|
|
||||||
* element may be null if there do not exist tracks of the corresponding type.
|
|
||||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth. May be null.
|
|
||||||
*/
|
|
||||||
/* package */ void onRenderers(TrackRenderer[] renderers, BandwidthMeter bandwidthMeter) {
|
|
||||||
for (int i = 0; i < RENDERER_COUNT; i++) {
|
|
||||||
if (renderers[i] == null) {
|
|
||||||
// Convert a null renderer to a dummy renderer.
|
|
||||||
renderers[i] = new DummyTrackRenderer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Complete preparation.
|
|
||||||
this.videoRenderer = renderers[TYPE_VIDEO];
|
|
||||||
this.codecCounters = videoRenderer instanceof MediaCodecTrackRenderer
|
|
||||||
? ((MediaCodecTrackRenderer) videoRenderer).codecCounters
|
|
||||||
: renderers[TYPE_AUDIO] instanceof MediaCodecTrackRenderer
|
|
||||||
? ((MediaCodecTrackRenderer) renderers[TYPE_AUDIO]).codecCounters : null;
|
|
||||||
this.bandwidthMeter = bandwidthMeter;
|
|
||||||
pushSurface(false);
|
|
||||||
player.prepare(renderers);
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_BUILT;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked if a {@link RendererBuilder} encounters an error.
|
|
||||||
*
|
|
||||||
* @param e Describes the error.
|
|
||||||
*/
|
|
||||||
/* package */ void onRenderersError(Exception e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onRendererInitializationError(e);
|
|
||||||
}
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onError(e);
|
|
||||||
}
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
|
||||||
maybeReportPlayerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPlayWhenReady(boolean playWhenReady) {
|
|
||||||
player.setPlayWhenReady(playWhenReady);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void seekTo(long positionMs) {
|
|
||||||
player.seekTo(positionMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void release() {
|
|
||||||
rendererBuilder.cancel();
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
|
||||||
surface = null;
|
|
||||||
player.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPlaybackState() {
|
|
||||||
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) {
|
|
||||||
return STATE_PREPARING;
|
|
||||||
}
|
|
||||||
int playerState = player.getPlaybackState();
|
|
||||||
if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT && playerState == STATE_IDLE) {
|
|
||||||
// This is an edge case where the renderers are built, but are still being passed to the
|
|
||||||
// player's playback thread.
|
|
||||||
return STATE_PREPARING;
|
|
||||||
}
|
|
||||||
return playerState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Format getFormat() {
|
|
||||||
return videoFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BandwidthMeter getBandwidthMeter() {
|
|
||||||
return bandwidthMeter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CodecCounters getCodecCounters() {
|
|
||||||
return codecCounters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getCurrentPosition() {
|
|
||||||
return player.getCurrentPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getDuration() {
|
|
||||||
return player.getDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBufferedPercentage() {
|
|
||||||
return player.getBufferedPercentage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getPlayWhenReady() {
|
|
||||||
return player.getPlayWhenReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ Looper getPlaybackLooper() {
|
|
||||||
return player.getPlaybackLooper();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* package */ Handler getMainHandler() {
|
|
||||||
return mainHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerStateChanged(boolean playWhenReady, int state) {
|
|
||||||
maybeReportPlayerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerError(ExoPlaybackException exception) {
|
|
||||||
rendererBuildingState = RENDERER_BUILDING_STATE_IDLE;
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onError(exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
|
||||||
float pixelWidthHeightRatio) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDroppedFrames(int count, long elapsed) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onDroppedFrames(count, elapsed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBandwidthSample(int elapsedMs, long bytes, long bitrateEstimate) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onBandwidthSample(elapsedMs, bytes, bitrateEstimate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDownstreamFormatChanged(int sourceId, Format format, int trigger,
|
|
||||||
long mediaTimeMs) {
|
|
||||||
if (infoListener == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sourceId == TYPE_VIDEO) {
|
|
||||||
videoFormat = format;
|
|
||||||
infoListener.onVideoFormatEnabled(format, trigger, mediaTimeMs);
|
|
||||||
} else if (sourceId == TYPE_AUDIO) {
|
|
||||||
infoListener.onAudioFormatEnabled(format, trigger, mediaTimeMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrmKeysLoaded() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrmSessionManagerError(Exception e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onDrmSessionManagerError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDecoderInitializationError(DecoderInitializationException e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onDecoderInitializationError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackInitializationError(AudioTrack.InitializationException e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onAudioTrackInitializationError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackWriteError(AudioTrack.WriteException e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onAudioTrackWriteError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCryptoError(CryptoException e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onCryptoError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs,
|
|
||||||
long initializationDurationMs) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onDecoderInitialized(decoderName, elapsedRealtimeMs, initializationDurationMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadError(int sourceId, IOException e) {
|
|
||||||
if (internalErrorListener != null) {
|
|
||||||
internalErrorListener.onLoadError(sourceId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCues(List<Cue> cues) {
|
|
||||||
if (captionListener != null && getSelectedTrack(TYPE_TEXT) != TRACK_DISABLED) {
|
|
||||||
captionListener.onCues(cues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMetadata(Map<String, Object> metadata) {
|
|
||||||
if (id3MetadataListener != null && getSelectedTrack(TYPE_METADATA) != TRACK_DISABLED) {
|
|
||||||
id3MetadataListener.onId3Metadata(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvailableRangeChanged(int sourceId, TimeRange availableRange) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onAvailableRangeChanged(sourceId, availableRange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayWhenReadyCommitted() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDrawnToSurface(Surface surface) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadStarted(int sourceId, long length, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onLoadStarted(sourceId, length, type, trigger, format, mediaStartTimeMs,
|
|
||||||
mediaEndTimeMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format,
|
|
||||||
long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs) {
|
|
||||||
if (infoListener != null) {
|
|
||||||
infoListener.onLoadCompleted(sourceId, bytesLoaded, type, trigger, format, mediaStartTimeMs,
|
|
||||||
mediaEndTimeMs, elapsedRealtimeMs, loadDurationMs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadCanceled(int sourceId, long bytesLoaded) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeReportPlayerState() {
|
|
||||||
boolean playWhenReady = player.getPlayWhenReady();
|
|
||||||
int playbackState = getPlaybackState();
|
|
||||||
if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) {
|
|
||||||
for (Listener listener : listeners) {
|
|
||||||
listener.onStateChanged(playWhenReady, playbackState);
|
|
||||||
}
|
|
||||||
lastReportedPlayWhenReady = playWhenReady;
|
|
||||||
lastReportedPlaybackState = playbackState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pushSurface(boolean blockForSurfacePush) {
|
|
||||||
if (videoRenderer == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockForSurfacePush) {
|
|
||||||
player.blockingSendMessage(
|
|
||||||
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
|
||||||
} else {
|
|
||||||
player.sendMessage(
|
|
||||||
videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.schabi.newpipe.player.exoplayer;
|
|
||||||
|
|
||||||
|
|
||||||
import org.schabi.newpipe.player.exoplayer.NPExoPlayer.RendererBuilder;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
|
||||||
import com.google.android.exoplayer.LoadControl;
|
|
||||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.MediaCodecSelector;
|
|
||||||
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.TrackRenderer;
|
|
||||||
import com.google.android.exoplayer.audio.AudioCapabilities;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
|
||||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
|
||||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
|
||||||
import com.google.android.exoplayer.drm.DrmSessionManager;
|
|
||||||
import com.google.android.exoplayer.drm.MediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
|
|
||||||
import com.google.android.exoplayer.drm.UnsupportedDrmException;
|
|
||||||
import com.google.android.exoplayer.smoothstreaming.DefaultSmoothStreamingTrackSelector;
|
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
|
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
|
|
||||||
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
|
|
||||||
import com.google.android.exoplayer.text.TextTrackRenderer;
|
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
|
|
||||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
|
||||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.os.Handler;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link RendererBuilder} for SmoothStreaming.
|
|
||||||
*/
|
|
||||||
public class SmoothStreamingRendererBuilder implements RendererBuilder {
|
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
|
||||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
|
||||||
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
|
||||||
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
|
||||||
private static final int LIVE_EDGE_LATENCY_MS = 30000;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final String url;
|
|
||||||
private final MediaDrmCallback drmCallback;
|
|
||||||
|
|
||||||
private AsyncRendererBuilder currentAsyncBuilder;
|
|
||||||
|
|
||||||
public SmoothStreamingRendererBuilder(Context context, String userAgent, String url,
|
|
||||||
MediaDrmCallback drmCallback) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.url = Util.toLowerInvariant(url).endsWith("/manifest") ? url : url + "/Manifest";
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void buildRenderers(NPExoPlayer player) {
|
|
||||||
currentAsyncBuilder = new AsyncRendererBuilder(context, userAgent, url, drmCallback, player);
|
|
||||||
currentAsyncBuilder.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cancel() {
|
|
||||||
if (currentAsyncBuilder != null) {
|
|
||||||
currentAsyncBuilder.cancel();
|
|
||||||
currentAsyncBuilder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AsyncRendererBuilder
|
|
||||||
implements ManifestFetcher.ManifestCallback<SmoothStreamingManifest> {
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final String userAgent;
|
|
||||||
private final MediaDrmCallback drmCallback;
|
|
||||||
private final NPExoPlayer player;
|
|
||||||
private final ManifestFetcher<SmoothStreamingManifest> manifestFetcher;
|
|
||||||
|
|
||||||
private boolean canceled;
|
|
||||||
|
|
||||||
public AsyncRendererBuilder(Context context, String userAgent, String url,
|
|
||||||
MediaDrmCallback drmCallback, NPExoPlayer player) {
|
|
||||||
this.context = context;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.drmCallback = drmCallback;
|
|
||||||
this.player = player;
|
|
||||||
SmoothStreamingManifestParser parser = new SmoothStreamingManifestParser();
|
|
||||||
manifestFetcher = new ManifestFetcher<>(url, new DefaultHttpDataSource(userAgent, null),
|
|
||||||
parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
|
||||||
canceled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifestError(IOException exception) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
player.onRenderersError(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSingleManifest(SmoothStreamingManifest manifest) {
|
|
||||||
if (canceled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Handler mainHandler = player.getMainHandler();
|
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
|
|
||||||
|
|
||||||
// Check drm support if necessary.
|
|
||||||
DrmSessionManager drmSessionManager = null;
|
|
||||||
if (manifest.protectionElement != null) {
|
|
||||||
if (Util.SDK_INT < 18) {
|
|
||||||
player.onRenderersError(
|
|
||||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
drmSessionManager = new StreamingDrmSessionManager(manifest.protectionElement.uuid,
|
|
||||||
player.getPlaybackLooper(), drmCallback, null, player.getMainHandler(), player);
|
|
||||||
} catch (UnsupportedDrmException e) {
|
|
||||||
player.onRenderersError(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the video renderer.
|
|
||||||
DataSource videoDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
DefaultSmoothStreamingTrackSelector.newVideoInstance(context, true, false),
|
|
||||||
videoDataSource, new AdaptiveEvaluator(bandwidthMeter), LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
|
||||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_VIDEO);
|
|
||||||
TrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(context, videoSampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
|
|
||||||
drmSessionManager, true, mainHandler, player, 50);
|
|
||||||
|
|
||||||
// Build the audio renderer.
|
|
||||||
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
DefaultSmoothStreamingTrackSelector.newAudioInstance(),
|
|
||||||
audioDataSource, null, LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
|
||||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_AUDIO);
|
|
||||||
TrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource,
|
|
||||||
MediaCodecSelector.DEFAULT, drmSessionManager, true, mainHandler, player,
|
|
||||||
AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC);
|
|
||||||
|
|
||||||
// Build the text renderer.
|
|
||||||
DataSource textDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
|
||||||
ChunkSource textChunkSource = new SmoothStreamingChunkSource(manifestFetcher,
|
|
||||||
DefaultSmoothStreamingTrackSelector.newTextInstance(),
|
|
||||||
textDataSource, null, LIVE_EDGE_LATENCY_MS);
|
|
||||||
ChunkSampleSource textSampleSource = new ChunkSampleSource(textChunkSource, loadControl,
|
|
||||||
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player,
|
|
||||||
NPExoPlayer.TYPE_TEXT);
|
|
||||||
TrackRenderer textRenderer = new TextTrackRenderer(textSampleSource, player,
|
|
||||||
mainHandler.getLooper());
|
|
||||||
|
|
||||||
// Invoke the callback.
|
|
||||||
TrackRenderer[] renderers = new TrackRenderer[NPExoPlayer.RENDERER_COUNT];
|
|
||||||
renderers[NPExoPlayer.TYPE_VIDEO] = videoRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_AUDIO] = audioRenderer;
|
|
||||||
renderers[NPExoPlayer.TYPE_TEXT] = textRenderer;
|
|
||||||
player.onRenderers(renderers, bandwidthMeter);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
15
app/src/main/res/layout/activity_exo_player.xml
Normal file
15
app/src/main/res/layout/activity_exo_player.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/root"
|
||||||
|
android:focusable="true"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:keepScreenOn="true">
|
||||||
|
|
||||||
|
<com.devbrackets.android.exomedia.ui.widget.EMVideoView
|
||||||
|
android:id="@+id/emVideoView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -1,44 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/root"
|
|
||||||
android:focusable="true"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:keepScreenOn="true">
|
|
||||||
|
|
||||||
<com.google.android.exoplayer.AspectRatioFrameLayout android:id="@+id/video_frame"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center">
|
|
||||||
|
|
||||||
<SurfaceView android:id="@+id/surface_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="center"/>
|
|
||||||
|
|
||||||
<View android:id="@+id/shutter"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:background="@android:color/black"/>
|
|
||||||
|
|
||||||
<com.google.android.exoplayer.text.SubtitleLayout android:id="@+id/subtitles"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"/>
|
|
||||||
|
|
||||||
</com.google.android.exoplayer.AspectRatioFrameLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
180
app/src/main/res/layout/exomedia_custom_controls.xml
Normal file
180
app/src/main/res/layout/exomedia_custom_controls.xml
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.devbrackets.android.exomedia.ui.widget.FitsSystemWindowRelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exomedia_controls_text_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:background="@drawable/exomedia_default_controls_text_background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exomedia_controls_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="The Video Title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exomedia_controls_sub_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="The Video Album"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exomedia_controls_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:text="The Video Artist"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exomedia_controls_interactive_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:background="@drawable/exomedia_default_controls_interactive_background"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:paddingTop="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exomedia_controls_current_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="2dp"
|
||||||
|
android:paddingRight="2dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="1:06:29"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/exomedia_controls_video_seek"
|
||||||
|
style="?android:seekBarStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
tools:ignore="RtlHardcoded"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/exomedia_controls_end_time"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="2dp"
|
||||||
|
android:paddingRight="2dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
tools:ignore="RtlHardcoded"
|
||||||
|
tools:text="1:23:49"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/exomedia_controls_extra_container"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
tools:ignore="RtlHardcoded,UselessLeaf"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exomedia_controls_previous_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
app:srcCompat="@drawable/exomedia_ic_skip_previous_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exomedia_controls_frewind_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginRight="24dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
android:src="@drawable/exomedia_ic_rewind_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exomedia_controls_play_pause_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
app:srcCompat="@drawable/exomedia_ic_play_arrow_white"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exomedia_controls_fforward_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
android:src="@drawable/exomedia_ic_fast_forward_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/exomedia_controls_next_btn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:minHeight="24dp"
|
||||||
|
android:minWidth="24dp"
|
||||||
|
app:srcCompat="@drawable/exomedia_ic_skip_next_white"
|
||||||
|
tools:ignore="ContentDescription,RtlHardcoded"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/exomedia_controls_video_loading"
|
||||||
|
style="?android:progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</com.devbrackets.android.exomedia.ui.widget.FitsSystemWindowRelativeLayout>
|
Loading…
Reference in New Issue
Block a user