Merge pull request #1199 from sridhard/master

Youtube like video track selection
This commit is contained in:
Hampton Maxwell 2018-12-31 21:39:25 -08:00 committed by GitHub
commit 04f3f40278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 187 additions and 30 deletions

View File

@ -106,6 +106,12 @@ export default class Video extends Component {
} }
}; };
_onBandwidthUpdate = (event) => {
if (this.props.onBandwidthUpdate) {
this.props.onBandwidthUpdate(event.nativeEvent);
}
};
_onSeek = (event) => { _onSeek = (event) => {
if (this.state.showPoster && !this.props.audioOnly) { if (this.state.showPoster && !this.props.audioOnly) {
this.setState({showPoster: false}); this.setState({showPoster: false});
@ -247,6 +253,7 @@ export default class Video extends Component {
onVideoSeek: this._onSeek, onVideoSeek: this._onSeek,
onVideoEnd: this._onEnd, onVideoEnd: this._onEnd,
onVideoBuffer: this._onBuffer, onVideoBuffer: this._onBuffer,
onVideoBandwidthUpdate: this._onBandwidthUpdate,
onTimedMetadata: this._onTimedMetadata, onTimedMetadata: this._onTimedMetadata,
onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy, onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
onVideoExternalPlaybackChange: this._onExternalPlaybackChange, onVideoExternalPlaybackChange: this._onExternalPlaybackChange,
@ -313,6 +320,7 @@ Video.propTypes = {
onVideoBuffer: PropTypes.func, onVideoBuffer: PropTypes.func,
onVideoError: PropTypes.func, onVideoError: PropTypes.func,
onVideoProgress: PropTypes.func, onVideoProgress: PropTypes.func,
onVideoBandwidthUpdate: PropTypes.func,
onVideoSeek: PropTypes.func, onVideoSeek: PropTypes.func,
onVideoEnd: PropTypes.func, onVideoEnd: PropTypes.func,
onTimedMetadata: PropTypes.func, onTimedMetadata: PropTypes.func,
@ -344,6 +352,13 @@ Video.propTypes = {
PropTypes.number PropTypes.number
]) ])
}), }),
selectedVideoTrack: PropTypes.shape({
type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}),
selectedTextTrack: PropTypes.shape({ selectedTextTrack: PropTypes.shape({
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([ value: PropTypes.oneOfType([
@ -377,6 +392,7 @@ Video.propTypes = {
playInBackground: PropTypes.bool, playInBackground: PropTypes.bool,
playWhenInactive: PropTypes.bool, playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']), ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
reportBandwidth: PropTypes.bool,
disableFocus: PropTypes.bool, disableFocus: PropTypes.bool,
controls: PropTypes.bool, controls: PropTypes.bool,
audioOnly: PropTypes.bool, audioOnly: PropTypes.bool,
@ -391,6 +407,7 @@ Video.propTypes = {
onBuffer: PropTypes.func, onBuffer: PropTypes.func,
onError: PropTypes.func, onError: PropTypes.func,
onProgress: PropTypes.func, onProgress: PropTypes.func,
onBandwidthUpdate: PropTypes.func,
onSeek: PropTypes.func, onSeek: PropTypes.func,
onEnd: PropTypes.func, onEnd: PropTypes.func,
onFullscreenPlayerWillPresent: PropTypes.func, onFullscreenPlayerWillPresent: PropTypes.func,

View File

@ -21,6 +21,12 @@ dependencies {
implementation('com.google.android.exoplayer:exoplayer:2.9.3') { implementation('com.google.android.exoplayer:exoplayer:2.9.3') {
exclude group: 'com.android.support' exclude group: 'com.android.support'
} }
implementation project(':exoplayer-library-core')
implementation project(':exoplayer-library-dash')
implementation project(':exoplayer-library-ui')
implementation project(':exoplayer-library-smoothstreaming')
implementation project(':exoplayer-library-hls')
implementation project(':exoplayer-extension-okhttp')
// All support libs must use the same version // All support libs must use the same version
implementation "com.android.support:support-annotations:${safeExtGet('supportLibVersion', '+')}" implementation "com.android.support:support-annotations:${safeExtGet('supportLibVersion', '+')}"

View File

@ -46,6 +46,7 @@ import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource; import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
@ -59,6 +60,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -76,6 +78,7 @@ import java.util.Locale;
class ReactExoplayerView extends FrameLayout implements class ReactExoplayerView extends FrameLayout implements
LifecycleEventListener, LifecycleEventListener,
ExoPlayer.EventListener, ExoPlayer.EventListener,
BandwidthMeter.EventListener,
BecomingNoisyListener, BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener, AudioManager.OnAudioFocusChangeListener,
MetadataRenderer.Output { MetadataRenderer.Output {
@ -85,6 +88,7 @@ class ReactExoplayerView extends FrameLayout implements
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1; private static final int SHOW_PROGRESS = 1;
private static final int REPORT_BANDWIDTH = 1;
static { static {
DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER = new CookieManager();
@ -124,6 +128,8 @@ class ReactExoplayerView extends FrameLayout implements
private boolean repeat; private boolean repeat;
private String audioTrackType; private String audioTrackType;
private Dynamic audioTrackValue; private Dynamic audioTrackValue;
private String videoTrackType;
private Dynamic videoTrackValue;
private ReadableArray audioTracks; private ReadableArray audioTracks;
private String textTrackType; private String textTrackType;
private Dynamic textTrackValue; private Dynamic textTrackValue;
@ -132,6 +138,7 @@ class ReactExoplayerView extends FrameLayout implements
private float mProgressUpdateInterval = 250.0f; private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false; private boolean playInBackground = false;
private Map<String, String> requestHeaders; private Map<String, String> requestHeaders;
private boolean mReportBandwidth = false;
// \ End props // \ End props
// React // React
@ -162,8 +169,11 @@ class ReactExoplayerView extends FrameLayout implements
public ReactExoplayerView(ThemedReactContext context) { public ReactExoplayerView(ThemedReactContext context) {
super(context); super(context);
this.themedReactContext = context; this.themedReactContext = context;
createViews();
this.eventEmitter = new VideoEventEmitter(context); this.eventEmitter = new VideoEventEmitter(context);
createViews();
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
themedReactContext.addLifecycleEventListener(this); themedReactContext.addLifecycleEventListener(this);
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
@ -238,9 +248,15 @@ class ReactExoplayerView extends FrameLayout implements
stopPlayback(); stopPlayback();
} }
//BandwidthMeter.EventListener implementation
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) {
eventEmitter.bandwidthReport(bitrate);
}
}
// Internal methods // Internal methods
private void initializePlayer() { private void initializePlayer() {
if (player == null) { if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
@ -255,6 +271,7 @@ class ReactExoplayerView extends FrameLayout implements
player.setMetadataOutput(this); player.setMetadataOutput(this);
exoPlayerView.setPlayer(player); exoPlayerView.setPlayer(player);
audioBecomingNoisyReceiver.setListener(this); audioBecomingNoisyReceiver.setListener(this);
BANDWIDTH_METER.addEventListener(new Handler(), this);
setPlayWhenReady(!isPaused); setPlayWhenReady(!isPaused);
playerNeedsSource = true; playerNeedsSource = true;
@ -345,6 +362,7 @@ class ReactExoplayerView extends FrameLayout implements
progressHandler.removeMessages(SHOW_PROGRESS); progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this); themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener(); audioBecomingNoisyReceiver.removeListener();
BANDWIDTH_METER.removeEventListener(this);
} }
private boolean requestAudioFocus() { private boolean requestAudioFocus() {
@ -520,12 +538,13 @@ class ReactExoplayerView extends FrameLayout implements
if (loadVideoStarted) { if (loadVideoStarted) {
loadVideoStarted = false; loadVideoStarted = false;
setSelectedAudioTrack(audioTrackType, audioTrackValue); setSelectedAudioTrack(audioTrackType, audioTrackValue);
setSelectedVideoTrack(videoTrackType, videoTrackValue);
setSelectedTextTrack(textTrackType, textTrackValue); setSelectedTextTrack(textTrackType, textTrackValue);
Format videoFormat = player.getVideoFormat(); Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0; int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0; int height = videoFormat != null ? videoFormat.height : 0;
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height, eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
getAudioTrackInfo(), getTextTrackInfo()); getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo());
} }
} }
@ -546,10 +565,39 @@ class ReactExoplayerView extends FrameLayout implements
audioTrack.putString("title", format.id != null ? format.id : ""); audioTrack.putString("title", format.id != null ? format.id : "");
audioTrack.putString("type", format.sampleMimeType); audioTrack.putString("type", format.sampleMimeType);
audioTrack.putString("language", format.language != null ? format.language : ""); audioTrack.putString("language", format.language != null ? format.language : "");
audioTrack.putString("bitrate", format.bitrate == Format.NO_VALUE ? ""
: String.format(Locale.US, "%.2fMbps", format.bitrate / 1000000f));
audioTracks.pushMap(audioTrack); audioTracks.pushMap(audioTrack);
} }
return audioTracks; return audioTracks;
} }
private WritableArray getVideoTrackInfo() {
WritableArray videoTracks = Arguments.createArray();
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
int index = getTrackRendererIndex(C.TRACK_TYPE_VIDEO);
if (info == null || index == C.INDEX_UNSET) {
return videoTracks;
}
TrackGroupArray groups = info.getTrackGroups(index);
for (int i = 0; i < groups.length; ++i) {
TrackGroup group = groups.get(i);
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
Format format = group.getFormat(trackIndex);
WritableMap videoTrack = Arguments.createMap();
videoTrack.putInt("width", format.width == Format.NO_VALUE ? 0 : format.width);
videoTrack.putInt("height",format.height == Format.NO_VALUE ? 0 : format.height);
videoTrack.putInt("bitrate", format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
videoTrack.putString("codecs", format.codecs != null ? format.codecs : "");
videoTrack.putString("trackId",
format.id == null ? String.valueOf(trackIndex) : format.id);
videoTracks.pushMap(videoTrack);
}
}
return videoTracks;
}
private WritableArray getTextTrackInfo() { private WritableArray getTextTrackInfo() {
WritableArray textTracks = Arguments.createArray(); WritableArray textTracks = Arguments.createArray();
@ -726,6 +774,10 @@ class ReactExoplayerView extends FrameLayout implements
mProgressUpdateInterval = progressUpdateInterval; mProgressUpdateInterval = progressUpdateInterval;
} }
public void setReportBandwidth(boolean reportBandwidth) {
mReportBandwidth = reportBandwidth;
}
public void setRawSrc(final Uri uri, final String extension) { public void setRawSrc(final Uri uri, final String extension) {
if (uri != null) { if (uri != null) {
boolean isOriginalSourceNull = srcUri == null; boolean isOriginalSourceNull = srcUri == null;
@ -777,7 +829,8 @@ class ReactExoplayerView extends FrameLayout implements
} }
TrackGroupArray groups = info.getTrackGroups(rendererIndex); TrackGroupArray groups = info.getTrackGroups(rendererIndex);
int trackIndex = C.INDEX_UNSET; int groupIndex = C.INDEX_UNSET;
int[] tracks = {0} ;
if (TextUtils.isEmpty(type)) { if (TextUtils.isEmpty(type)) {
type = "default"; type = "default";
@ -795,7 +848,7 @@ class ReactExoplayerView extends FrameLayout implements
for (int i = 0; i < groups.length; ++i) { for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0); Format format = groups.get(i).getFormat(0);
if (format.language != null && format.language.equals(value.asString())) { if (format.language != null && format.language.equals(value.asString())) {
trackIndex = i; groupIndex = i;
break; break;
} }
} }
@ -803,28 +856,46 @@ class ReactExoplayerView extends FrameLayout implements
for (int i = 0; i < groups.length; ++i) { for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0); Format format = groups.get(i).getFormat(0);
if (format.id != null && format.id.equals(value.asString())) { if (format.id != null && format.id.equals(value.asString())) {
trackIndex = i; groupIndex = i;
break; break;
} }
} }
} else if (type.equals("index")) { } else if (type.equals("index")) {
if (value.asInt() < groups.length) { if (value.asInt() < groups.length) {
trackIndex = value.asInt(); groupIndex = value.asInt();
} }
} else { // default } else if (type.equals("resolution")) {
if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18 && groups.length > 0) { int height = value.asInt();
// Use system settings if possible for (int i = 0; i < groups.length; ++i) { // Search for the exact height
CaptioningManager captioningManager TrackGroup group = groups.get(i);
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE); for (int j = 0; j < group.length; j++) {
if (captioningManager != null && captioningManager.isEnabled()) { Format format = group.getFormat(j);
trackIndex = getTrackIndexForDefaultLocale(groups); if (format.height == value.asInt()) {
groupIndex = i;
tracks[0] = j;
break;
}
} }
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) {
trackIndex = getTrackIndexForDefaultLocale(groups);
} }
} else if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
// Use system settings if possible
CaptioningManager captioningManager
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager != null && captioningManager.isEnabled()) {
groupIndex = getGroupIndexForDefaultLocale(groups);
}
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) { // Audio default
groupIndex = getGroupIndexForDefaultLocale(groups);
} }
if (trackIndex == C.INDEX_UNSET) { if (groupIndex == C.INDEX_UNSET && trackType == C.TRACK_TYPE_VIDEO) { // Video auto
TrackGroup group = groups.get(0);
tracks = new int[group.length];
groupIndex = 0;
for (int j = 0; j < group.length; j++) {
tracks[j] = j;
}
} else if (groupIndex == C.INDEX_UNSET) {
trackSelector.setParameters(disableParameters); trackSelector.setParameters(disableParameters);
return; return;
} }
@ -833,28 +904,34 @@ class ReactExoplayerView extends FrameLayout implements
.buildUpon() .buildUpon()
.setRendererDisabled(rendererIndex, false) .setRendererDisabled(rendererIndex, false)
.setSelectionOverride(rendererIndex, groups, .setSelectionOverride(rendererIndex, groups,
new DefaultTrackSelector.SelectionOverride(trackIndex, 0)) new DefaultTrackSelector.SelectionOverride(groupIndex, tracks))
.build(); .build();
trackSelector.setParameters(selectionParameters); trackSelector.setParameters(selectionParameters);
} }
private int getTrackIndexForDefaultLocale(TrackGroupArray groups) { private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
if (groups.length == 0) { // Avoid a crash if we try to select a non-existant group if (groups.length == 0){
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
int trackIndex = 0; // default if no match int groupIndex = 0; // default if no match
String locale2 = Locale.getDefault().getLanguage(); // 2 letter code String locale2 = Locale.getDefault().getLanguage(); // 2 letter code
String locale3 = Locale.getDefault().getISO3Language(); // 3 letter code String locale3 = Locale.getDefault().getISO3Language(); // 3 letter code
for (int i = 0; i < groups.length; ++i) { for (int i = 0; i < groups.length; ++i) {
Format format = groups.get(i).getFormat(0); Format format = groups.get(i).getFormat(0);
String language = format.language; String language = format.language;
if (language != null && (language.equals(locale2) || language.equals(locale3))) { if (language != null && (language.equals(locale2) || language.equals(locale3))) {
trackIndex = i; groupIndex = i;
break; break;
} }
} }
return trackIndex; return groupIndex;
}
public void setSelectedVideoTrack(String type, Dynamic value) {
videoTrackType = type;
videoTrackValue = value;
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
} }
public void setSelectedAudioTrack(String type, Dynamic value) { public void setSelectedAudioTrack(String type, Dynamic value) {

View File

@ -45,6 +45,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs"; private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs";
private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs"; private static final String PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval"; private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
private static final String PROP_SEEK = "seek"; private static final String PROP_SEEK = "seek";
private static final String PROP_RATE = "rate"; private static final String PROP_RATE = "rate";
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate"; private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
@ -52,6 +53,9 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_DISABLE_FOCUS = "disableFocus"; private static final String PROP_DISABLE_FOCUS = "disableFocus";
private static final String PROP_FULLSCREEN = "fullscreen"; private static final String PROP_FULLSCREEN = "fullscreen";
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView"; private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack";
private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type";
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView"; private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
@Override @Override
@ -138,6 +142,20 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setRepeatModifier(repeat); videoView.setRepeatModifier(repeat);
} }
@ReactProp(name = PROP_SELECTED_VIDEO_TRACK)
public void setSelectedVideoTrack(final ReactExoplayerView videoView,
@Nullable ReadableMap selectedVideoTrack) {
String typeString = null;
Dynamic value = null;
if (selectedVideoTrack != null) {
typeString = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_TYPE)
? selectedVideoTrack.getString(PROP_SELECTED_VIDEO_TRACK_TYPE) : null;
value = selectedVideoTrack.hasKey(PROP_SELECTED_VIDEO_TRACK_VALUE)
? selectedVideoTrack.getDynamic(PROP_SELECTED_VIDEO_TRACK_VALUE) : null;
}
videoView.setSelectedVideoTrack(typeString, value);
}
@ReactProp(name = PROP_SELECTED_AUDIO_TRACK) @ReactProp(name = PROP_SELECTED_AUDIO_TRACK)
public void setSelectedAudioTrack(final ReactExoplayerView videoView, public void setSelectedAudioTrack(final ReactExoplayerView videoView,
@Nullable ReadableMap selectedAudioTrack) { @Nullable ReadableMap selectedAudioTrack) {
@ -192,6 +210,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setProgressUpdateInterval(progressUpdateInterval); videoView.setProgressUpdateInterval(progressUpdateInterval);
} }
@ReactProp(name = PROP_REPORT_BANDWIDTH, defaultBoolean = false)
public void setReportBandwidth(final ReactExoplayerView videoView, final boolean reportBandwidth) {
videoView.setReportBandwidth(reportBandwidth);
}
@ReactProp(name = PROP_SEEK) @ReactProp(name = PROP_SEEK)
public void setSeek(final ReactExoplayerView videoView, final float seek) { public void setSeek(final ReactExoplayerView videoView, final float seek) {
videoView.seekTo(Math.round(seek * 1000f)); videoView.seekTo(Math.round(seek * 1000f));

View File

@ -29,6 +29,7 @@ class VideoEventEmitter {
private static final String EVENT_LOAD = "onVideoLoad"; private static final String EVENT_LOAD = "onVideoLoad";
private static final String EVENT_ERROR = "onVideoError"; private static final String EVENT_ERROR = "onVideoError";
private static final String EVENT_PROGRESS = "onVideoProgress"; private static final String EVENT_PROGRESS = "onVideoProgress";
private static final String EVENT_BANDWIDTH = "onVideoBandwidthUpdate";
private static final String EVENT_SEEK = "onVideoSeek"; private static final String EVENT_SEEK = "onVideoSeek";
private static final String EVENT_END = "onVideoEnd"; private static final String EVENT_END = "onVideoEnd";
private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent"; private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent";
@ -66,6 +67,7 @@ class VideoEventEmitter {
EVENT_AUDIO_BECOMING_NOISY, EVENT_AUDIO_BECOMING_NOISY,
EVENT_AUDIO_FOCUS_CHANGE, EVENT_AUDIO_FOCUS_CHANGE,
EVENT_PLAYBACK_RATE_CHANGE, EVENT_PLAYBACK_RATE_CHANGE,
EVENT_BANDWIDTH,
}; };
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -89,6 +91,7 @@ class VideoEventEmitter {
EVENT_AUDIO_BECOMING_NOISY, EVENT_AUDIO_BECOMING_NOISY,
EVENT_AUDIO_FOCUS_CHANGE, EVENT_AUDIO_FOCUS_CHANGE,
EVENT_PLAYBACK_RATE_CHANGE, EVENT_PLAYBACK_RATE_CHANGE,
EVENT_BANDWIDTH,
}) })
@interface VideoEvents { @interface VideoEvents {
} }
@ -109,6 +112,7 @@ class VideoEventEmitter {
private static final String EVENT_PROP_WIDTH = "width"; private static final String EVENT_PROP_WIDTH = "width";
private static final String EVENT_PROP_HEIGHT = "height"; private static final String EVENT_PROP_HEIGHT = "height";
private static final String EVENT_PROP_ORIENTATION = "orientation"; private static final String EVENT_PROP_ORIENTATION = "orientation";
private static final String EVENT_PROP_VIDEO_TRACKS = "videoTracks";
private static final String EVENT_PROP_AUDIO_TRACKS = "audioTracks"; private static final String EVENT_PROP_AUDIO_TRACKS = "audioTracks";
private static final String EVENT_PROP_TEXT_TRACKS = "textTracks"; private static final String EVENT_PROP_TEXT_TRACKS = "textTracks";
private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus"; private static final String EVENT_PROP_HAS_AUDIO_FOCUS = "hasAudioFocus";
@ -117,10 +121,12 @@ class VideoEventEmitter {
private static final String EVENT_PROP_ERROR = "error"; private static final String EVENT_PROP_ERROR = "error";
private static final String EVENT_PROP_ERROR_STRING = "errorString"; private static final String EVENT_PROP_ERROR_STRING = "errorString";
private static final String EVENT_PROP_ERROR_EXCEPTION = ""; private static final String EVENT_PROP_ERROR_EXCEPTION = "errorException";
private static final String EVENT_PROP_TIMED_METADATA = "metadata"; private static final String EVENT_PROP_TIMED_METADATA = "metadata";
private static final String EVENT_PROP_BITRATE = "bitrate";
void setViewId(int viewId) { void setViewId(int viewId) {
this.viewId = viewId; this.viewId = viewId;
@ -131,7 +137,7 @@ class VideoEventEmitter {
} }
void load(double duration, double currentPosition, int videoWidth, int videoHeight, void load(double duration, double currentPosition, int videoWidth, int videoHeight,
WritableArray audioTracks, WritableArray textTracks) { WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, duration / 1000D); event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
@ -146,6 +152,7 @@ class VideoEventEmitter {
} }
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize); event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);
event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTracks);
event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks); event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks); event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
@ -169,6 +176,12 @@ class VideoEventEmitter {
receiveEvent(EVENT_PROGRESS, event); receiveEvent(EVENT_PROGRESS, event);
} }
void bandwidthReport(double bitRateEstimate) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_BITRATE, bitRate);
receiveEvent(EVENT_BANDWIDTH, event);
}
void seek(long currentPosition, long seekTime) { void seek(long currentPosition, long seekTime) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);

View File

@ -22,6 +22,7 @@ public class ReactVideoPackage implements ReactPackage {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override @Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new ReactExoplayerViewManager()); return Collections.<ViewManager>singletonList(new ReactExoplayerViewManager());

View File

@ -24,6 +24,7 @@
@property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer; @property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoError; @property (nonatomic, copy) RCTBubblingEventBlock onVideoError;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress; @property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onBandwidthUpdate;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek; @property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd; @property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd;
@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata; @property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata;

View File

@ -704,6 +704,24 @@ static int const RCTVideoUnset = -1;
selector:@selector(playbackStalled:) selector:@selector(playbackStalled:)
name:AVPlayerItemPlaybackStalledNotification name:AVPlayerItemPlaybackStalledNotification
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:AVPlayerItemNewAccessLogEntryNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAVPlayerAccess:)
name:AVPlayerItemNewAccessLogEntryNotification
object:nil];
}
- (void)handleAVPlayerAccess:(NSNotification *)notification {
AVPlayerItemAccessLog *accessLog = [((AVPlayerItem *)notification.object) accessLog];
AVPlayerItemAccessLogEvent *lastEvent = accessLog.events.lastObject;
if (self.onBandwidthUpdate) {
self.onBandwidthUpdate(@{@"bitrate": [NSNumber numberWithFloat:lastEvent.observedBitrate]});
}
} }
- (void)playbackStalled:(NSNotification *)notification - (void)playbackStalled:(NSNotification *)notification

View File

@ -48,6 +48,7 @@ RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock);