mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-20 14:15:14 +00:00
I'm guessing this was a bug as it doesn't make sense that we would set paused to true if the video is playing, and not paused if it wasn't. I believe this is a safe change since we only release the player when the app is closing or we detach (which is going away shortly). We need this since we release the player in order to apply new bufferConfig settings.
951 lines
34 KiB
Java
951 lines
34 KiB
Java
package com.brentvatne.exoplayer;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.app.Activity;
|
|
import android.content.Context;
|
|
import android.media.AudioManager;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.view.Window;
|
|
import android.view.accessibility.CaptioningManager;
|
|
import android.widget.FrameLayout;
|
|
|
|
import com.brentvatne.react.R;
|
|
import com.brentvatne.receiver.AudioBecomingNoisyReceiver;
|
|
import com.brentvatne.receiver.BecomingNoisyListener;
|
|
import com.facebook.react.bridge.Arguments;
|
|
import com.facebook.react.bridge.Dynamic;
|
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
import com.facebook.react.bridge.ReadableArray;
|
|
import com.facebook.react.bridge.ReadableMap;
|
|
import com.facebook.react.bridge.WritableArray;
|
|
import com.facebook.react.bridge.WritableMap;
|
|
import com.facebook.react.uimanager.ThemedReactContext;
|
|
import com.google.android.exoplayer2.C;
|
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
import com.google.android.exoplayer2.ExoPlayer;
|
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
|
import com.google.android.exoplayer2.Format;
|
|
import com.google.android.exoplayer2.PlaybackParameters;
|
|
import com.google.android.exoplayer2.Player;
|
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
|
import com.google.android.exoplayer2.Timeline;
|
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
|
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
|
|
import com.google.android.exoplayer2.metadata.Metadata;
|
|
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
|
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
|
import com.google.android.exoplayer2.source.MediaSource;
|
|
import com.google.android.exoplayer2.source.MergingMediaSource;
|
|
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
|
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
|
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
|
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
|
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
|
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
|
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
|
import com.google.android.exoplayer2.upstream.DataSource;
|
|
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|
import com.google.android.exoplayer2.util.MimeTypes;
|
|
import com.google.android.exoplayer2.util.Util;
|
|
|
|
import java.net.CookieHandler;
|
|
import java.net.CookieManager;
|
|
import java.net.CookiePolicy;
|
|
import java.lang.Math;
|
|
import java.util.Map;
|
|
import java.lang.Object;
|
|
import java.util.ArrayList;
|
|
import java.util.Locale;
|
|
|
|
@SuppressLint("ViewConstructor")
|
|
class ReactExoplayerView extends FrameLayout implements
|
|
LifecycleEventListener,
|
|
ExoPlayer.EventListener,
|
|
BecomingNoisyListener,
|
|
AudioManager.OnAudioFocusChangeListener,
|
|
MetadataRenderer.Output {
|
|
|
|
private static final String TAG = "ReactExoplayerView";
|
|
|
|
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
|
|
private static final CookieManager DEFAULT_COOKIE_MANAGER;
|
|
private static final int SHOW_PROGRESS = 1;
|
|
|
|
static {
|
|
DEFAULT_COOKIE_MANAGER = new CookieManager();
|
|
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
|
}
|
|
|
|
private final VideoEventEmitter eventEmitter;
|
|
|
|
private Handler mainHandler;
|
|
private ExoPlayerView exoPlayerView;
|
|
|
|
private DataSource.Factory mediaDataSourceFactory;
|
|
private SimpleExoPlayer player;
|
|
private MappingTrackSelector trackSelector;
|
|
private boolean playerNeedsSource;
|
|
|
|
private int resumeWindow;
|
|
private long resumePosition;
|
|
private boolean loadVideoStarted;
|
|
private boolean isFullscreen;
|
|
private boolean isInBackground;
|
|
private boolean isPaused;
|
|
private boolean isBuffering;
|
|
private float rate = 1f;
|
|
|
|
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
|
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
|
private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
|
|
private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
|
|
|
|
// Props from React
|
|
private Uri srcUri;
|
|
private String extension;
|
|
private boolean repeat;
|
|
private String audioTrackType;
|
|
private Dynamic audioTrackValue;
|
|
private ReadableArray audioTracks;
|
|
private String textTrackType;
|
|
private Dynamic textTrackValue;
|
|
private ReadableArray textTracks;
|
|
private boolean disableFocus;
|
|
private float mProgressUpdateInterval = 250.0f;
|
|
private boolean playInBackground = false;
|
|
private boolean useTextureView = false;
|
|
private Map<String, String> requestHeaders;
|
|
// \ End props
|
|
|
|
// React
|
|
private final ThemedReactContext themedReactContext;
|
|
private final AudioManager audioManager;
|
|
private final AudioBecomingNoisyReceiver audioBecomingNoisyReceiver;
|
|
|
|
private final Handler progressHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
switch (msg.what) {
|
|
case SHOW_PROGRESS:
|
|
if (player != null
|
|
&& player.getPlaybackState() == ExoPlayer.STATE_READY
|
|
&& player.getPlayWhenReady()
|
|
) {
|
|
long pos = player.getCurrentPosition();
|
|
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
|
|
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
|
|
msg = obtainMessage(SHOW_PROGRESS);
|
|
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
public ReactExoplayerView(ThemedReactContext context) {
|
|
super(context);
|
|
this.themedReactContext = context;
|
|
createViews();
|
|
this.eventEmitter = new VideoEventEmitter(context);
|
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
|
themedReactContext.addLifecycleEventListener(this);
|
|
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
|
|
|
|
initializePlayer();
|
|
}
|
|
|
|
|
|
@Override
|
|
public void setId(int id) {
|
|
super.setId(id);
|
|
eventEmitter.setViewId(id);
|
|
}
|
|
|
|
private void createViews() {
|
|
clearResumePosition();
|
|
mediaDataSourceFactory = buildDataSourceFactory(true);
|
|
mainHandler = new Handler();
|
|
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
|
|
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
|
|
}
|
|
|
|
LayoutParams layoutParams = new LayoutParams(
|
|
LayoutParams.MATCH_PARENT,
|
|
LayoutParams.MATCH_PARENT);
|
|
exoPlayerView = new ExoPlayerView(getContext());
|
|
exoPlayerView.setLayoutParams(layoutParams);
|
|
|
|
addView(exoPlayerView, 0, layoutParams);
|
|
}
|
|
|
|
@Override
|
|
protected void onAttachedToWindow() {
|
|
super.onAttachedToWindow();
|
|
initializePlayer();
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
stopPlayback();
|
|
}
|
|
|
|
// LifecycleEventListener implementation
|
|
|
|
@Override
|
|
public void onHostResume() {
|
|
if (!playInBackground || !isInBackground) {
|
|
setPlayWhenReady(!isPaused);
|
|
}
|
|
isInBackground = false;
|
|
}
|
|
|
|
@Override
|
|
public void onHostPause() {
|
|
isInBackground = true;
|
|
if (playInBackground) {
|
|
return;
|
|
}
|
|
setPlayWhenReady(false);
|
|
}
|
|
|
|
@Override
|
|
public void onHostDestroy() {
|
|
stopPlayback();
|
|
}
|
|
|
|
public void cleanUpResources() {
|
|
stopPlayback();
|
|
}
|
|
|
|
|
|
// Internal methods
|
|
|
|
private void initializePlayer() {
|
|
if (player == null) {
|
|
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
|
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
|
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
|
DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
|
|
player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
|
|
player.addListener(this);
|
|
player.setMetadataOutput(this);
|
|
exoPlayerView.setPlayer(player);
|
|
audioBecomingNoisyReceiver.setListener(this);
|
|
setPlayWhenReady(!isPaused);
|
|
playerNeedsSource = true;
|
|
|
|
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
|
player.setPlaybackParameters(params);
|
|
}
|
|
if (playerNeedsSource && srcUri != null) {
|
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
|
MediaSource videoSource = buildMediaSource(srcUri, extension);
|
|
MediaSource mediaSource;
|
|
if (mediaSourceList.size() == 0) {
|
|
mediaSource = videoSource;
|
|
} else {
|
|
mediaSourceList.add(0, videoSource);
|
|
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
|
new MediaSource[mediaSourceList.size()]
|
|
);
|
|
mediaSource = new MergingMediaSource(textSourceArray);
|
|
}
|
|
|
|
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
|
if (haveResumePosition) {
|
|
player.seekTo(resumeWindow, resumePosition);
|
|
}
|
|
player.prepare(mediaSource, !haveResumePosition, false);
|
|
playerNeedsSource = false;
|
|
|
|
eventEmitter.loadStart();
|
|
loadVideoStarted = true;
|
|
}
|
|
}
|
|
|
|
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
|
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
|
: uri.getLastPathSegment());
|
|
switch (type) {
|
|
case C.TYPE_SS:
|
|
return new SsMediaSource(uri, buildDataSourceFactory(false),
|
|
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
|
|
case C.TYPE_DASH:
|
|
return new DashMediaSource(uri, buildDataSourceFactory(false),
|
|
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null);
|
|
case C.TYPE_HLS:
|
|
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null);
|
|
case C.TYPE_OTHER:
|
|
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
|
|
mainHandler, null);
|
|
default: {
|
|
throw new IllegalStateException("Unsupported type: " + type);
|
|
}
|
|
}
|
|
}
|
|
|
|
private ArrayList<MediaSource> buildTextSources() {
|
|
ArrayList<MediaSource> textSources = new ArrayList<>();
|
|
if (textTracks == null) {
|
|
return textSources;
|
|
}
|
|
|
|
for (int i = 0; i < textTracks.size(); ++i) {
|
|
ReadableMap textTrack = textTracks.getMap(i);
|
|
String language = textTrack.getString("language");
|
|
String title = textTrack.hasKey("title")
|
|
? textTrack.getString("title") : language + " " + i;
|
|
Uri uri = Uri.parse(textTrack.getString("uri"));
|
|
MediaSource textSource = buildTextSource(title, uri, textTrack.getString("type"),
|
|
language);
|
|
if (textSource != null) {
|
|
textSources.add(textSource);
|
|
}
|
|
}
|
|
return textSources;
|
|
}
|
|
|
|
private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) {
|
|
Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language);
|
|
return new SingleSampleMediaSource(uri, mediaDataSourceFactory, textFormat, C.TIME_UNSET);
|
|
}
|
|
|
|
private void releasePlayer() {
|
|
if (player != null) {
|
|
updateResumePosition();
|
|
player.release();
|
|
player.setMetadataOutput(null);
|
|
player = null;
|
|
trackSelector = null;
|
|
}
|
|
progressHandler.removeMessages(SHOW_PROGRESS);
|
|
themedReactContext.removeLifecycleEventListener(this);
|
|
audioBecomingNoisyReceiver.removeListener();
|
|
}
|
|
|
|
private boolean requestAudioFocus() {
|
|
if (disableFocus) {
|
|
return true;
|
|
}
|
|
int result = audioManager.requestAudioFocus(this,
|
|
AudioManager.STREAM_MUSIC,
|
|
AudioManager.AUDIOFOCUS_GAIN);
|
|
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
|
|
}
|
|
|
|
private void setPlayWhenReady(boolean playWhenReady) {
|
|
if (player == null) {
|
|
return;
|
|
}
|
|
|
|
if (playWhenReady) {
|
|
boolean hasAudioFocus = requestAudioFocus();
|
|
if (hasAudioFocus) {
|
|
player.setPlayWhenReady(true);
|
|
}
|
|
} else {
|
|
player.setPlayWhenReady(false);
|
|
}
|
|
}
|
|
|
|
private void startPlayback() {
|
|
if (player != null) {
|
|
switch (player.getPlaybackState()) {
|
|
case ExoPlayer.STATE_IDLE:
|
|
case ExoPlayer.STATE_ENDED:
|
|
initializePlayer();
|
|
break;
|
|
case ExoPlayer.STATE_BUFFERING:
|
|
case ExoPlayer.STATE_READY:
|
|
if (!player.getPlayWhenReady()) {
|
|
setPlayWhenReady(true);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
initializePlayer();
|
|
}
|
|
if (!disableFocus) {
|
|
setKeepScreenOn(true);
|
|
}
|
|
}
|
|
|
|
private void pausePlayback() {
|
|
if (player != null) {
|
|
if (player.getPlayWhenReady()) {
|
|
setPlayWhenReady(false);
|
|
}
|
|
}
|
|
setKeepScreenOn(false);
|
|
}
|
|
|
|
private void stopPlayback() {
|
|
onStopPlayback();
|
|
releasePlayer();
|
|
}
|
|
|
|
private void onStopPlayback() {
|
|
if (isFullscreen) {
|
|
setFullscreen(false);
|
|
}
|
|
setKeepScreenOn(false);
|
|
audioManager.abandonAudioFocus(this);
|
|
}
|
|
|
|
private void updateResumePosition() {
|
|
resumeWindow = player.getCurrentWindowIndex();
|
|
resumePosition = player.isCurrentWindowSeekable() ? Math.max(0, player.getCurrentPosition())
|
|
: C.TIME_UNSET;
|
|
}
|
|
|
|
private void clearResumePosition() {
|
|
resumeWindow = C.INDEX_UNSET;
|
|
resumePosition = C.TIME_UNSET;
|
|
}
|
|
|
|
/**
|
|
* Returns a new DataSource factory.
|
|
*
|
|
* @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new
|
|
* DataSource factory.
|
|
* @return A new DataSource factory.
|
|
*/
|
|
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
|
return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, useBandwidthMeter ? BANDWIDTH_METER : null, requestHeaders);
|
|
}
|
|
|
|
// AudioManager.OnAudioFocusChangeListener implementation
|
|
|
|
@Override
|
|
public void onAudioFocusChange(int focusChange) {
|
|
switch (focusChange) {
|
|
case AudioManager.AUDIOFOCUS_LOSS:
|
|
eventEmitter.audioFocusChanged(false);
|
|
break;
|
|
case AudioManager.AUDIOFOCUS_GAIN:
|
|
eventEmitter.audioFocusChanged(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (player != null) {
|
|
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
|
|
// Lower the volume
|
|
player.setVolume(0.8f);
|
|
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
|
|
// Raise it back to normal
|
|
player.setVolume(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// AudioBecomingNoisyListener implementation
|
|
|
|
@Override
|
|
public void onAudioBecomingNoisy() {
|
|
eventEmitter.audioBecomingNoisy();
|
|
}
|
|
|
|
// ExoPlayer.EventListener implementation
|
|
|
|
@Override
|
|
public void onLoadingChanged(boolean isLoading) {
|
|
// Do nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
|
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
|
|
switch (playbackState) {
|
|
case ExoPlayer.STATE_IDLE:
|
|
text += "idle";
|
|
eventEmitter.idle();
|
|
break;
|
|
case ExoPlayer.STATE_BUFFERING:
|
|
text += "buffering";
|
|
onBuffering(true);
|
|
break;
|
|
case ExoPlayer.STATE_READY:
|
|
text += "ready";
|
|
eventEmitter.ready();
|
|
onBuffering(false);
|
|
startProgressHandler();
|
|
videoLoaded();
|
|
break;
|
|
case ExoPlayer.STATE_ENDED:
|
|
text += "ended";
|
|
eventEmitter.end();
|
|
onStopPlayback();
|
|
break;
|
|
default:
|
|
text += "unknown";
|
|
break;
|
|
}
|
|
Log.d(TAG, text);
|
|
}
|
|
|
|
private void startProgressHandler() {
|
|
progressHandler.sendEmptyMessage(SHOW_PROGRESS);
|
|
}
|
|
|
|
private void videoLoaded() {
|
|
if (loadVideoStarted) {
|
|
loadVideoStarted = false;
|
|
setSelectedAudioTrack(audioTrackType, audioTrackValue);
|
|
setSelectedTextTrack(textTrackType, textTrackValue);
|
|
Format videoFormat = player.getVideoFormat();
|
|
int width = videoFormat != null ? videoFormat.width : 0;
|
|
int height = videoFormat != null ? videoFormat.height : 0;
|
|
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
|
|
getAudioTrackInfo(), getTextTrackInfo());
|
|
}
|
|
}
|
|
|
|
private WritableArray getAudioTrackInfo() {
|
|
WritableArray audioTracks = Arguments.createArray();
|
|
|
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
|
int index = getTrackRendererIndex(C.TRACK_TYPE_AUDIO);
|
|
if (info == null || index == C.INDEX_UNSET) {
|
|
return audioTracks;
|
|
}
|
|
|
|
TrackGroupArray groups = info.getTrackGroups(index);
|
|
for (int i = 0; i < groups.length; ++i) {
|
|
Format format = groups.get(i).getFormat(0);
|
|
WritableMap textTrack = Arguments.createMap();
|
|
textTrack.putInt("index", i);
|
|
textTrack.putString("title", format.id != null ? format.id : "");
|
|
textTrack.putString("type", format.sampleMimeType);
|
|
textTrack.putString("language", format.language != null ? format.language : "");
|
|
audioTracks.pushMap(textTrack);
|
|
}
|
|
return audioTracks;
|
|
}
|
|
|
|
private WritableArray getTextTrackInfo() {
|
|
WritableArray textTracks = Arguments.createArray();
|
|
|
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
|
int index = getTrackRendererIndex(C.TRACK_TYPE_TEXT);
|
|
if (info == null || index == C.INDEX_UNSET) {
|
|
return textTracks;
|
|
}
|
|
|
|
TrackGroupArray groups = info.getTrackGroups(index);
|
|
for (int i = 0; i < groups.length; ++i) {
|
|
Format format = groups.get(i).getFormat(0);
|
|
WritableMap textTrack = Arguments.createMap();
|
|
textTrack.putInt("index", i);
|
|
textTrack.putString("title", format.id != null ? format.id : "");
|
|
textTrack.putString("type", format.sampleMimeType);
|
|
textTrack.putString("language", format.language != null ? format.language : "");
|
|
textTracks.pushMap(textTrack);
|
|
}
|
|
return textTracks;
|
|
}
|
|
|
|
private void onBuffering(boolean buffering) {
|
|
if (isBuffering == buffering) {
|
|
return;
|
|
}
|
|
|
|
isBuffering = buffering;
|
|
if (buffering) {
|
|
eventEmitter.buffering(true);
|
|
} else {
|
|
eventEmitter.buffering(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onPositionDiscontinuity(int reason) {
|
|
if (playerNeedsSource) {
|
|
// This will only occur if the user has performed a seek whilst in the error state. Update the
|
|
// resume position so that if the user then retries, playback will resume from the position to
|
|
// which they seeked.
|
|
updateResumePosition();
|
|
}
|
|
// When repeat is turned on, reaching the end of the video will not cause a state change
|
|
// so we need to explicitly detect it.
|
|
if (reason == ExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
|
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
|
eventEmitter.end();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
|
|
// Do nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onSeekProcessed() {
|
|
// Do nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
|
// Do nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onRepeatModeChanged(int repeatMode) {
|
|
// Do nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
|
// Do Nothing.
|
|
}
|
|
|
|
@Override
|
|
public void onPlaybackParametersChanged(PlaybackParameters params) {
|
|
eventEmitter.playbackRateChange(params.speed);
|
|
}
|
|
|
|
@Override
|
|
public void onPlayerError(ExoPlaybackException e) {
|
|
String errorString = null;
|
|
Exception ex = e;
|
|
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
|
Exception cause = e.getRendererException();
|
|
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
|
|
// Special case for decoder initialization failures.
|
|
MediaCodecRenderer.DecoderInitializationException decoderInitializationException =
|
|
(MediaCodecRenderer.DecoderInitializationException) cause;
|
|
if (decoderInitializationException.decoderName == null) {
|
|
if (decoderInitializationException.getCause() instanceof MediaCodecUtil.DecoderQueryException) {
|
|
errorString = getResources().getString(R.string.error_querying_decoders);
|
|
} else if (decoderInitializationException.secureDecoderRequired) {
|
|
errorString = getResources().getString(R.string.error_no_secure_decoder,
|
|
decoderInitializationException.mimeType);
|
|
} else {
|
|
errorString = getResources().getString(R.string.error_no_decoder,
|
|
decoderInitializationException.mimeType);
|
|
}
|
|
} else {
|
|
errorString = getResources().getString(R.string.error_instantiating_decoder,
|
|
decoderInitializationException.decoderName);
|
|
}
|
|
}
|
|
}
|
|
else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
|
|
ex = e.getSourceException();
|
|
errorString = getResources().getString(R.string.unrecognized_media_format);
|
|
}
|
|
if (errorString != null) {
|
|
eventEmitter.error(errorString, ex);
|
|
}
|
|
playerNeedsSource = true;
|
|
if (isBehindLiveWindow(e)) {
|
|
clearResumePosition();
|
|
initializePlayer();
|
|
} else {
|
|
updateResumePosition();
|
|
}
|
|
}
|
|
|
|
private static boolean isBehindLiveWindow(ExoPlaybackException e) {
|
|
if (e.type != ExoPlaybackException.TYPE_SOURCE) {
|
|
return false;
|
|
}
|
|
Throwable cause = e.getSourceException();
|
|
while (cause != null) {
|
|
if (cause instanceof BehindLiveWindowException) {
|
|
return true;
|
|
}
|
|
cause = cause.getCause();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public int getTrackRendererIndex(int trackType) {
|
|
int rendererCount = player.getRendererCount();
|
|
for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
|
|
if (player.getRendererType(rendererIndex) == trackType) {
|
|
return rendererIndex;
|
|
}
|
|
}
|
|
return C.INDEX_UNSET;
|
|
}
|
|
|
|
@Override
|
|
public void onMetadata(Metadata metadata) {
|
|
eventEmitter.timedMetadata(metadata);
|
|
}
|
|
|
|
// ReactExoplayerViewManager public api
|
|
|
|
public void setSrc(final Uri uri, final String extension, Map<String, String> headers) {
|
|
if (uri != null) {
|
|
boolean isOriginalSourceNull = srcUri == null;
|
|
boolean isSourceEqual = uri.equals(srcUri);
|
|
|
|
this.srcUri = uri;
|
|
this.extension = extension;
|
|
this.requestHeaders = headers;
|
|
this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER, this.requestHeaders);
|
|
|
|
if (!isOriginalSourceNull && !isSourceEqual) {
|
|
reloadSource();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setProgressUpdateInterval(final float progressUpdateInterval) {
|
|
mProgressUpdateInterval = progressUpdateInterval;
|
|
}
|
|
|
|
public void setRawSrc(final Uri uri, final String extension) {
|
|
if (uri != null) {
|
|
boolean isOriginalSourceNull = srcUri == null;
|
|
boolean isSourceEqual = uri.equals(srcUri);
|
|
|
|
this.srcUri = uri;
|
|
this.extension = extension;
|
|
this.mediaDataSourceFactory = DataSourceUtil.getRawDataSourceFactory(this.themedReactContext);
|
|
|
|
if (!isOriginalSourceNull && !isSourceEqual) {
|
|
reloadSource();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setTextTracks(ReadableArray textTracks) {
|
|
this.textTracks = textTracks;
|
|
reloadSource();
|
|
}
|
|
|
|
private void reloadSource() {
|
|
playerNeedsSource = true;
|
|
initializePlayer();
|
|
}
|
|
|
|
public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) {
|
|
exoPlayerView.setResizeMode(resizeMode);
|
|
}
|
|
|
|
public void setRepeatModifier(boolean repeat) {
|
|
if (player != null) {
|
|
if (repeat) {
|
|
player.setRepeatMode(Player.REPEAT_MODE_ONE);
|
|
} else {
|
|
player.setRepeatMode(Player.REPEAT_MODE_OFF);
|
|
}
|
|
}
|
|
this.repeat = repeat;
|
|
}
|
|
|
|
public void setSelectedTrack(int trackType, String type, Dynamic value) {
|
|
int rendererIndex = getTrackRendererIndex(trackType);
|
|
if (rendererIndex == C.INDEX_UNSET) {
|
|
return;
|
|
}
|
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
|
if (info == null) {
|
|
return;
|
|
}
|
|
|
|
TrackGroupArray groups = info.getTrackGroups(rendererIndex);
|
|
int trackIndex = C.INDEX_UNSET;
|
|
|
|
if (TextUtils.isEmpty(type)) {
|
|
type = "default";
|
|
}
|
|
|
|
if (type.equals("disabled")) {
|
|
trackSelector.setSelectionOverride(rendererIndex, groups, null);
|
|
return;
|
|
} else if (type.equals("language")) {
|
|
for (int i = 0; i < groups.length; ++i) {
|
|
Format format = groups.get(i).getFormat(0);
|
|
if (format.language != null && format.language.equals(value.asString())) {
|
|
trackIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if (type.equals("title")) {
|
|
for (int i = 0; i < groups.length; ++i) {
|
|
Format format = groups.get(i).getFormat(0);
|
|
if (format.id != null && format.id.equals(value.asString())) {
|
|
trackIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if (type.equals("index")) {
|
|
if (value.asInt() < groups.length) {
|
|
trackIndex = value.asInt();
|
|
}
|
|
} else { // default
|
|
if (rendererIndex == C.TRACK_TYPE_TEXT) { // Use system settings if possible
|
|
int sdk = android.os.Build.VERSION.SDK_INT;
|
|
if (sdk > 18 && groups.length > 0) {
|
|
CaptioningManager captioningManager
|
|
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
|
|
if (captioningManager != null && captioningManager.isEnabled()) {
|
|
trackIndex = getTrackIndexForDefaultLocale(groups);
|
|
}
|
|
} else {
|
|
trackSelector.setSelectionOverride(rendererIndex, groups, null);
|
|
return;
|
|
}
|
|
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) {
|
|
trackIndex = getTrackIndexForDefaultLocale(groups);
|
|
}
|
|
}
|
|
|
|
if (trackIndex == C.INDEX_UNSET) {
|
|
trackSelector.clearSelectionOverrides(trackIndex);
|
|
return;
|
|
}
|
|
|
|
MappingTrackSelector.SelectionOverride override
|
|
= new MappingTrackSelector.SelectionOverride(
|
|
new FixedTrackSelection.Factory(), trackIndex, 0);
|
|
trackSelector.setSelectionOverride(rendererIndex, groups, override);
|
|
}
|
|
|
|
private int getTrackIndexForDefaultLocale(TrackGroupArray groups) {
|
|
int trackIndex = 0; // default if no match
|
|
String locale2 = Locale.getDefault().getLanguage(); // 2 letter code
|
|
String locale3 = Locale.getDefault().getISO3Language(); // 3 letter code
|
|
for (int i = 0; i < groups.length; ++i) {
|
|
Format format = groups.get(i).getFormat(0);
|
|
String language = format.language;
|
|
if (language != null && (language.equals(locale2) || language.equals(locale3))) {
|
|
trackIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
return trackIndex;
|
|
}
|
|
|
|
public void setSelectedAudioTrack(String type, Dynamic value) {
|
|
audioTrackType = type;
|
|
audioTrackValue = value;
|
|
setSelectedTrack(C.TRACK_TYPE_AUDIO, audioTrackType, audioTrackValue);
|
|
}
|
|
|
|
public void setSelectedTextTrack(String type, Dynamic value) {
|
|
textTrackType = type;
|
|
textTrackValue = value;
|
|
setSelectedTrack(C.TRACK_TYPE_TEXT, textTrackType, textTrackValue);
|
|
}
|
|
|
|
public void setPausedModifier(boolean paused) {
|
|
isPaused = paused;
|
|
if (player != null) {
|
|
if (!paused) {
|
|
startPlayback();
|
|
} else {
|
|
pausePlayback();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setMutedModifier(boolean muted) {
|
|
if (player != null) {
|
|
player.setVolume(muted ? 0 : 1);
|
|
}
|
|
}
|
|
|
|
|
|
public void setVolumeModifier(float volume) {
|
|
if (player != null) {
|
|
player.setVolume(volume);
|
|
}
|
|
}
|
|
|
|
public void seekTo(long positionMs) {
|
|
if (player != null) {
|
|
eventEmitter.seek(player.getCurrentPosition(), positionMs);
|
|
player.seekTo(positionMs);
|
|
}
|
|
}
|
|
|
|
public void setRateModifier(float newRate) {
|
|
rate = newRate;
|
|
|
|
if (player != null) {
|
|
PlaybackParameters params = new PlaybackParameters(rate, 1f);
|
|
player.setPlaybackParameters(params);
|
|
}
|
|
}
|
|
|
|
|
|
public void setPlayInBackground(boolean playInBackground) {
|
|
this.playInBackground = playInBackground;
|
|
}
|
|
|
|
public void setDisableFocus(boolean disableFocus) {
|
|
this.disableFocus = disableFocus;
|
|
}
|
|
|
|
public void setFullscreen(boolean fullscreen) {
|
|
if (fullscreen == isFullscreen) {
|
|
return; // Avoid generating events when nothing is changing
|
|
}
|
|
isFullscreen = fullscreen;
|
|
|
|
Activity activity = themedReactContext.getCurrentActivity();
|
|
if (activity == null) {
|
|
return;
|
|
}
|
|
Window window = activity.getWindow();
|
|
View decorView = window.getDecorView();
|
|
int uiOptions;
|
|
if (isFullscreen) {
|
|
if (Util.SDK_INT >= 19) { // 4.4+
|
|
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
| SYSTEM_UI_FLAG_FULLSCREEN;
|
|
} else {
|
|
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
| SYSTEM_UI_FLAG_FULLSCREEN;
|
|
}
|
|
eventEmitter.fullscreenWillPresent();
|
|
decorView.setSystemUiVisibility(uiOptions);
|
|
eventEmitter.fullscreenDidPresent();
|
|
} else {
|
|
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
|
|
eventEmitter.fullscreenWillDismiss();
|
|
decorView.setSystemUiVisibility(uiOptions);
|
|
eventEmitter.fullscreenDidDismiss();
|
|
}
|
|
}
|
|
|
|
public void setUseTextureView(boolean useTextureView) {
|
|
exoPlayerView.setUseTextureView(useTextureView);
|
|
}
|
|
|
|
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) {
|
|
minBufferMs = newMinBufferMs;
|
|
maxBufferMs = newMaxBufferMs;
|
|
bufferForPlaybackMs = newBufferForPlaybackMs;
|
|
bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs;
|
|
releasePlayer();
|
|
initializePlayer();
|
|
}
|
|
}
|