Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Youtube like video track selection #1199

Merged
merged 18 commits into from
Jan 1, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ export default class Video extends Component {
}
};

_onBandwidthUpdate = (event) => {
if (this.props.onBandwidthUpdate) {
this.props.onBandwidthUpdate(event.nativeEvent);
}
};

_onSeek = (event) => {
if (this.state.showPoster && !this.props.audioOnly) {
this.setState({showPoster: false});
Expand Down Expand Up @@ -234,6 +240,7 @@ export default class Video extends Component {
onVideoSeek: this._onSeek,
onVideoEnd: this._onEnd,
onVideoBuffer: this._onBuffer,
onBandwidthUpdate: this._onBandwidthUpdate,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change this to onVideoBandwidthUpdate. The outward facing prop can still be called onBandwidthUpdate. Event names are global across the app, so if another module (e.g. audio) also declares onBandwidthUpdate, it will conflict.

You can copy the pattern for other events such as onVideoProgress -> onProgress

onTimedMetadata: this._onTimedMetadata,
onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
Expand Down Expand Up @@ -294,6 +301,7 @@ Video.propTypes = {
onVideoBuffer: PropTypes.func,
onVideoError: PropTypes.func,
onVideoProgress: PropTypes.func,
onBandwidthUpdate: PropTypes.func,
onVideoSeek: PropTypes.func,
onVideoEnd: PropTypes.func,
onTimedMetadata: PropTypes.func,
Expand Down Expand Up @@ -323,6 +331,13 @@ Video.propTypes = {
PropTypes.number
])
}),
selectedVideoTrack: PropTypes.shape({
type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}),
selectedTextTrack: PropTypes.shape({
type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
Expand Down Expand Up @@ -356,6 +371,8 @@ Video.propTypes = {
playInBackground: PropTypes.bool,
playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
reportBandwidth: PropTypes.bool,
bandwidthUpdateInterval: PropTypes.number,
disableFocus: PropTypes.bool,
controls: PropTypes.bool,
audioOnly: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
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.TrackGroup;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
Expand Down Expand Up @@ -85,6 +86,7 @@ class ReactExoplayerView extends FrameLayout implements
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1;
private static final int REPORT_BANDWIDTH = 1;

static {
DEFAULT_COOKIE_MANAGER = new CookieManager();
Expand Down Expand Up @@ -121,6 +123,8 @@ class ReactExoplayerView extends FrameLayout implements
private boolean repeat;
private String audioTrackType;
private Dynamic audioTrackValue;
private String videoTrackType;
private Dynamic videoTrackValue;
private ReadableArray audioTracks;
private String textTrackType;
private Dynamic textTrackValue;
Expand All @@ -130,6 +134,8 @@ class ReactExoplayerView extends FrameLayout implements
private boolean playInBackground = false;
private boolean useTextureView = false;
private Map<String, String> requestHeaders;
private float mBandwidthUpdateInterval = 250.0f;
private boolean mReportBandwidth = false;
// \ End props

// React
Expand Down Expand Up @@ -157,6 +163,23 @@ public void handleMessage(Message msg) {
}
};

private final Handler bandwidthReporter = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_BANDWIDTH:
if (player != null) {
long bitRateEstimate = BANDWIDTH_METER.getBitrateEstimate();

eventEmitter.bandwidthReport(bitRateEstimate);
msg = obtainMessage(REPORT_BANDWIDTH);
sendMessageDelayed(msg, Math.round(mBandwidthUpdateInterval));
}
break;
}
}
};

public ReactExoplayerView(ThemedReactContext context) {
super(context);
this.themedReactContext = context;
Expand Down Expand Up @@ -334,6 +357,7 @@ private void releasePlayer() {
player = null;
trackSelector = null;
}
bandwidthReporter.removeMessages(REPORT_BANDWIDTH);
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
Expand Down Expand Up @@ -512,12 +536,13 @@ private void videoLoaded() {
if (loadVideoStarted) {
loadVideoStarted = false;
setSelectedAudioTrack(audioTrackType, audioTrackValue);
setSelectedVideoTrack(videoTrackType, videoTrackValue);
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());
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo());
}
}

Expand All @@ -538,11 +563,46 @@ private WritableArray getAudioTrackInfo() {
audioTrack.putString("title", format.id != null ? format.id : "");
audioTrack.putString("type", format.sampleMimeType);
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);
}
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.putString("width",
format.width == Format.NO_VALUE ? "" : format.width + "");
videoTrack.putString("height",
format.height == Format.NO_VALUE ? "" : format.height + "");
videoTrack.putString("bitrate",
format.bitrate == Format.NO_VALUE ? ""
: String.format(Locale.US, "%.2fMbps", format.bitrate / 1000000f));
videoTrack.putString("trackid", format.id == null ? "" : "" + format.id + "");

videoTracks.pushMap(videoTrack);
}
}
return videoTracks;
}

private WritableArray getTextTrackInfo() {
WritableArray textTracks = Arguments.createArray();

Expand Down Expand Up @@ -717,6 +777,20 @@ public void setProgressUpdateInterval(final float progressUpdateInterval) {
mProgressUpdateInterval = progressUpdateInterval;
}

public void setBandwidthUpdateInterval(final float bandwidthUpdateInterval) {
mBandwidthUpdateInterval = bandwidthUpdateInterval;
}

public void setReportBandwidthModifier(boolean reportBandwidth) {
mReportBandwidth = reportBandwidth;
if (mReportBandwidth) {
bandwidthReporter.removeMessages(REPORT_BANDWIDTH);
bandwidthReporter.sendEmptyMessage(REPORT_BANDWIDTH);
} else {
bandwidthReporter.removeMessages(REPORT_BANDWIDTH);
}
}

public void setRawSrc(final Uri uri, final String extension) {
if (uri != null) {
boolean isOriginalSourceNull = srcUri == null;
Expand Down Expand Up @@ -758,6 +832,7 @@ public void setRepeatModifier(boolean repeat) {
}

public void setSelectedTrack(int trackType, String type, Dynamic value) {

int rendererIndex = getTrackRendererIndex(trackType);
if (rendererIndex == C.INDEX_UNSET) {
return;
Expand All @@ -768,7 +843,8 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) {
}

TrackGroupArray groups = info.getTrackGroups(rendererIndex);
int trackIndex = C.INDEX_UNSET;
int groupIndex = C.INDEX_UNSET;
int[] tracks = {0} ;

if (TextUtils.isEmpty(type)) {
type = "default";
Expand All @@ -786,36 +862,79 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) {
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;
groupIndex = 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;
groupIndex = i;
break;
}
}
} else if (type.equals("index")) {
if (value.asInt() < groups.length) {
trackIndex = value.asInt();
groupIndex = value.asInt();
}
} else if (type.equals("videoHeight")) {
groupIndex = C.INDEX_UNSET;

/*0 is auto mode. Add all tracks of group 0*/
if (0 == value.asInt()) {
TrackGroup group = groups.get(0);

tracks = new int[group.length];

groupIndex = 0;

for (int j = 0; j < group.length; j++) {
tracks[j] = j;
}
} else {
/*Search for the exact video Height*/
for (int i = 0; i < groups.length; ++i) {
TrackGroup group = groups.get(i);

for (int j = 0; j < group.length; j++) {
Format format = group.getFormat(j);

if (format.height == value.asInt()) {
groupIndex = i;
tracks[0] = j;
break;
}
}
}

/*If exact height not found then make it auto. Add all tracks of group 0*/
if (groupIndex == C.INDEX_UNSET) {
TrackGroup group = groups.get(0);

tracks = new int[group.length];

groupIndex = 0;

for (int j = 0; j < group.length; j++) {
tracks[j] = j;
}
}
}
} else { // default
if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18 && groups.length > 0) {
// Use system settings if possible
CaptioningManager captioningManager
= (CaptioningManager)themedReactContext.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager != null && captioningManager.isEnabled()) {
trackIndex = getTrackIndexForDefaultLocale(groups);
groupIndex = getGroupIndexForDefaultLocale(groups);
}
} else if (rendererIndex == C.TRACK_TYPE_AUDIO) {
trackIndex = getTrackIndexForDefaultLocale(groups);
groupIndex = getGroupIndexForDefaultLocale(groups);
}
}

if (trackIndex == C.INDEX_UNSET) {
if (groupIndex == C.INDEX_UNSET) {
trackSelector.setParameters(disableParameters);
return;
}
Expand All @@ -824,24 +943,30 @@ public void setSelectedTrack(int trackType, String type, Dynamic value) {
.buildUpon()
.setRendererDisabled(rendererIndex, false)
.setSelectionOverride(rendererIndex, groups,
new DefaultTrackSelector.SelectionOverride(trackIndex, 0))
new DefaultTrackSelector.SelectionOverride(groupIndex, tracks))
.build();
trackSelector.setParameters(selectionParameters);
}

private int getTrackIndexForDefaultLocale(TrackGroupArray groups) {
int trackIndex = 0; // default if no match
private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
int groupIndex = 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;
groupIndex = i;
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,17 @@ 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_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs";
private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval";
private static final String PROP_BANDWIDTH_UPDATE_INTERVAL = "bandwidthUpdateInterval";
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
private static final String PROP_SEEK = "seek";
private static final String PROP_RATE = "rate";
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
private static final String PROP_DISABLE_FOCUS = "disableFocus";
private static final String PROP_FULLSCREEN = "fullscreen";
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";

@Override
public String getName() {
Expand Down Expand Up @@ -136,6 +141,20 @@ public void setRepeat(final ReactExoplayerView videoView, final boolean 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)
public void setSelectedAudioTrack(final ReactExoplayerView videoView,
@Nullable ReadableMap selectedAudioTrack) {
Expand Down Expand Up @@ -190,6 +209,16 @@ public void setProgressUpdateInterval(final ReactExoplayerView videoView, final
videoView.setProgressUpdateInterval(progressUpdateInterval);
}

@ReactProp(name = PROP_BANDWIDTH_UPDATE_INTERVAL, defaultFloat = 250.0f)
public void setBandwidthUpdateInterval(final ReactExoplayerView videoView, final float bandwidthUpdateInterval) {
videoView.setBandwidthUpdateInterval(bandwidthUpdateInterval);
}

@ReactProp(name = PROP_REPORT_BANDWIDTH, defaultBoolean = false)
public void setReportBandwidth(final ReactExoplayerView videoView, final boolean reportBandwidth) {
videoView.setReportBandwidthModifier(reportBandwidth);
}

@ReactProp(name = PROP_SEEK)
public void setSeek(final ReactExoplayerView videoView, final float seek) {
videoView.seekTo(Math.round(seek * 1000f));
Expand Down
Loading