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 all 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 @@ -106,6 +106,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 @@ -247,6 +253,7 @@ export default class Video extends Component {
onVideoSeek: this._onSeek,
onVideoEnd: this._onEnd,
onVideoBuffer: this._onBuffer,
onVideoBandwidthUpdate: this._onBandwidthUpdate,
onTimedMetadata: this._onTimedMetadata,
onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
onVideoExternalPlaybackChange: this._onExternalPlaybackChange,
Expand Down Expand Up @@ -313,6 +320,7 @@ Video.propTypes = {
onVideoBuffer: PropTypes.func,
onVideoError: PropTypes.func,
onVideoProgress: PropTypes.func,
onVideoBandwidthUpdate: PropTypes.func,
onVideoSeek: PropTypes.func,
onVideoEnd: PropTypes.func,
onTimedMetadata: PropTypes.func,
Expand Down Expand Up @@ -344,6 +352,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 @@ -377,6 +392,7 @@ Video.propTypes = {
playInBackground: PropTypes.bool,
playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
reportBandwidth: PropTypes.bool,
disableFocus: PropTypes.bool,
controls: PropTypes.bool,
audioOnly: PropTypes.bool,
Expand All @@ -391,6 +407,7 @@ Video.propTypes = {
onBuffer: PropTypes.func,
onError: PropTypes.func,
onProgress: PropTypes.func,
onBandwidthUpdate: PropTypes.func,
onSeek: PropTypes.func,
onEnd: PropTypes.func,
onFullscreenPlayerWillPresent: PropTypes.func,
Expand Down
6 changes: 6 additions & 0 deletions android-exoplayer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ dependencies {
implementation('com.google.android.exoplayer:exoplayer:2.9.3') {
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
implementation "com.android.support:support-annotations:${safeExtGet('supportLibVersion', '+')}"
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 All @@ -59,6 +60,7 @@
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.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
Expand All @@ -76,6 +78,7 @@
class ReactExoplayerView extends FrameLayout implements
LifecycleEventListener,
ExoPlayer.EventListener,
BandwidthMeter.EventListener,
BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener,
MetadataRenderer.Output {
Expand All @@ -85,14 +88,15 @@ 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();
DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
}

private final VideoEventEmitter eventEmitter;

private Handler mainHandler;
private ExoPlayerView exoPlayerView;

Expand Down Expand Up @@ -124,6 +128,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 @@ -132,6 +138,7 @@ class ReactExoplayerView extends FrameLayout implements
private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false;
private Map<String, String> requestHeaders;
private boolean mReportBandwidth = false;
// \ End props

// React
Expand Down Expand Up @@ -162,8 +169,11 @@ public void handleMessage(Message msg) {
public ReactExoplayerView(ThemedReactContext context) {
super(context);
this.themedReactContext = context;
createViews();

this.eventEmitter = new VideoEventEmitter(context);

createViews();

audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
themedReactContext.addLifecycleEventListener(this);
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
Expand Down Expand Up @@ -238,9 +248,15 @@ public void cleanUpResources() {
stopPlayback();
}

//BandwidthMeter.EventListener implementation
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) {
eventEmitter.bandwidthReport(bitrate);
}
}

// Internal methods

private void initializePlayer() {
if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
Expand All @@ -255,6 +271,7 @@ private void initializePlayer() {
player.setMetadataOutput(this);
exoPlayerView.setPlayer(player);
audioBecomingNoisyReceiver.setListener(this);
BANDWIDTH_METER.addEventListener(new Handler(), this);
setPlayWhenReady(!isPaused);
playerNeedsSource = true;

Expand Down Expand Up @@ -345,6 +362,7 @@ private void releasePlayer() {
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
BANDWIDTH_METER.removeEventListener(this);
}

private boolean requestAudioFocus() {
Expand Down Expand Up @@ -520,12 +538,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 @@ -546,10 +565,39 @@ 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.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() {
WritableArray textTracks = Arguments.createArray();
Expand Down Expand Up @@ -726,6 +774,10 @@ public void setProgressUpdateInterval(final float progressUpdateInterval) {
mProgressUpdateInterval = progressUpdateInterval;
}

public void setReportBandwidth(boolean reportBandwidth) {
mReportBandwidth = reportBandwidth;
}

public void setRawSrc(final Uri uri, final String extension) {
if (uri != null) {
boolean isOriginalSourceNull = srcUri == null;
Expand Down Expand Up @@ -777,7 +829,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 @@ -795,36 +848,54 @@ 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 { // 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);
} else if (type.equals("resolution")) {
int height = value.asInt();
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
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;
}
}
} 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);
return;
}
Expand All @@ -833,28 +904,34 @@ 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) {
if (groups.length == 0) { // Avoid a crash if we try to select a non-existant group
private int getGroupIndexForDefaultLocale(TrackGroupArray groups) {
if (groups.length == 0){
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 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
Loading