VEX-6011: Align AVOD resolutions with available resolutions on Content (#14)
Add support for content tracks and improve track selection to work even during ads playback.
This commit is contained in:
parent
e27baeb065
commit
f712eecb4f
1
Video.js
1
Video.js
@ -477,6 +477,7 @@ Video.propTypes = {
|
|||||||
playWhenInactive: PropTypes.bool,
|
playWhenInactive: PropTypes.bool,
|
||||||
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
||||||
reportBandwidth: PropTypes.bool,
|
reportBandwidth: PropTypes.bool,
|
||||||
|
contentStartTime: PropTypes.number,
|
||||||
disableFocus: PropTypes.bool,
|
disableFocus: PropTypes.bool,
|
||||||
disableBuffering: PropTypes.bool,
|
disableBuffering: PropTypes.bool,
|
||||||
controls: PropTypes.bool,
|
controls: PropTypes.bool,
|
||||||
|
@ -76,6 +76,13 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
|||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashUtil;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Period;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||||
|
import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
|
||||||
|
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
@ -86,6 +93,13 @@ import java.util.UUID;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
import java.util.List;
|
||||||
|
import java.lang.Thread;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.lang.Integer;
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class ReactExoplayerView extends FrameLayout implements
|
class ReactExoplayerView extends FrameLayout implements
|
||||||
@ -136,6 +150,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private int maxBitRate = 0;
|
private int maxBitRate = 0;
|
||||||
private long seekTime = C.TIME_UNSET;
|
private long seekTime = C.TIME_UNSET;
|
||||||
private boolean hasDrmFailed = false;
|
private boolean hasDrmFailed = false;
|
||||||
|
private boolean isUsingContentResolution = false;
|
||||||
|
private boolean selectTrackWhenReady = false;
|
||||||
|
|
||||||
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||||
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||||
@ -159,6 +175,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private ReadableArray textTracks;
|
private ReadableArray textTracks;
|
||||||
private boolean disableFocus;
|
private boolean disableFocus;
|
||||||
private boolean disableBuffering;
|
private boolean disableBuffering;
|
||||||
|
private long contentStartTime;
|
||||||
private boolean disableDisconnectError;
|
private boolean disableDisconnectError;
|
||||||
private boolean preventsDisplaySleepDuringVideoPlayback = true;
|
private boolean preventsDisplaySleepDuringVideoPlayback = true;
|
||||||
private float mProgressUpdateInterval = 250.0f;
|
private float mProgressUpdateInterval = 250.0f;
|
||||||
@ -850,6 +867,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
onBuffering(false);
|
onBuffering(false);
|
||||||
startProgressHandler();
|
startProgressHandler();
|
||||||
videoLoaded();
|
videoLoaded();
|
||||||
|
if (selectTrackWhenReady && isUsingContentResolution) {
|
||||||
|
selectTrackWhenReady = false;
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
}
|
||||||
// Setting the visibility for the playerControlView
|
// Setting the visibility for the playerControlView
|
||||||
if (playerControlView != null) {
|
if (playerControlView != null) {
|
||||||
playerControlView.show();
|
playerControlView.show();
|
||||||
@ -921,6 +942,13 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
return audioTracks;
|
return audioTracks;
|
||||||
}
|
}
|
||||||
private WritableArray getVideoTrackInfo() {
|
private WritableArray getVideoTrackInfo() {
|
||||||
|
|
||||||
|
WritableArray contentVideoTracks = this.getVideoTrackInfoFromManifest();
|
||||||
|
if (contentVideoTracks != null) {
|
||||||
|
isUsingContentResolution = true;
|
||||||
|
return contentVideoTracks;
|
||||||
|
}
|
||||||
|
|
||||||
WritableArray videoTracks = Arguments.createArray();
|
WritableArray videoTracks = Arguments.createArray();
|
||||||
|
|
||||||
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
|
||||||
@ -947,9 +975,73 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return videoTracks;
|
return videoTracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WritableArray getVideoTrackInfoFromManifest() {
|
||||||
|
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||||
|
final DataSource dataSource = this.mediaDataSourceFactory.createDataSource();
|
||||||
|
final Uri sourceUri = this.srcUri;
|
||||||
|
final Timeline timelineRef = this.player.getCurrentTimeline();
|
||||||
|
final long startTime = this.contentStartTime * 1000 - 100; // s -> ms with 100ms offset
|
||||||
|
|
||||||
|
Future<WritableArray> result = es.submit(new Callable<WritableArray>() {
|
||||||
|
DataSource ds = dataSource;
|
||||||
|
Uri uri = sourceUri;
|
||||||
|
Timeline timeline = timelineRef;
|
||||||
|
long startTimeUs = startTime * 1000; // ms -> us
|
||||||
|
|
||||||
|
public WritableArray call() throws Exception {
|
||||||
|
WritableArray videoTracks = Arguments.createArray();
|
||||||
|
try {
|
||||||
|
DashManifest manifest = DashUtil.loadManifest(this.ds, this.uri);
|
||||||
|
int periodCount = manifest.getPeriodCount();
|
||||||
|
for (int i = 0; i < periodCount; i++) {
|
||||||
|
Period period = manifest.getPeriod(i);
|
||||||
|
for (int adaptationIndex = 0; adaptationIndex < period.adaptationSets.size(); adaptationIndex++) {
|
||||||
|
AdaptationSet adaptation = period.adaptationSets.get(adaptationIndex);
|
||||||
|
if (adaptation.type != C.TRACK_TYPE_VIDEO) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boolean hasFoundContentPeriod = false;
|
||||||
|
for (int representationIndex = 0; representationIndex < adaptation.representations.size(); representationIndex++) {
|
||||||
|
Representation representation = adaptation.representations.get(representationIndex);
|
||||||
|
Format format = representation.format;
|
||||||
|
if (representation.presentationTimeOffsetUs <= startTimeUs) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hasFoundContentPeriod = true;
|
||||||
|
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(representationIndex) : format.id);
|
||||||
|
if (isFormatSupported(format)) {
|
||||||
|
videoTracks.pushMap(videoTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasFoundContentPeriod) {
|
||||||
|
return videoTracks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
WritableArray results = result.get();
|
||||||
|
es.shutdown();
|
||||||
|
return results;
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private WritableArray getTextTrackInfo() {
|
private WritableArray getTextTrackInfo() {
|
||||||
WritableArray textTracks = Arguments.createArray();
|
WritableArray textTracks = Arguments.createArray();
|
||||||
|
|
||||||
@ -993,6 +1085,11 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
// which they seeked.
|
// which they seeked.
|
||||||
updateResumePosition();
|
updateResumePosition();
|
||||||
}
|
}
|
||||||
|
if (isUsingContentResolution) {
|
||||||
|
// Discontinuity events might have a different track list so we update the selected track
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
selectTrackWhenReady = true;
|
||||||
|
}
|
||||||
// When repeat is turned on, reaching the end of the video will not cause a state change
|
// When repeat is turned on, reaching the end of the video will not cause a state change
|
||||||
// so we need to explicitly detect it.
|
// so we need to explicitly detect it.
|
||||||
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
|
||||||
@ -1010,6 +1107,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
public void onSeekProcessed() {
|
public void onSeekProcessed() {
|
||||||
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
||||||
seekTime = C.TIME_UNSET;
|
seekTime = C.TIME_UNSET;
|
||||||
|
if (isUsingContentResolution) {
|
||||||
|
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period
|
||||||
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1278,14 +1379,51 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
int height = value.asInt();
|
int height = value.asInt();
|
||||||
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
||||||
TrackGroup group = groups.get(i);
|
TrackGroup group = groups.get(i);
|
||||||
|
Format closestFormat = null;
|
||||||
|
int closestTrackIndex = -1;
|
||||||
|
boolean usingExactMatch = false;
|
||||||
for (int j = 0; j < group.length; j++) {
|
for (int j = 0; j < group.length; j++) {
|
||||||
Format format = group.getFormat(j);
|
Format format = group.getFormat(j);
|
||||||
if (format.height == height) {
|
if (format.height == height) {
|
||||||
groupIndex = i;
|
groupIndex = i;
|
||||||
tracks[0] = j;
|
tracks[0] = j;
|
||||||
|
closestFormat = null;
|
||||||
|
closestTrackIndex = -1;
|
||||||
|
usingExactMatch = true;
|
||||||
break;
|
break;
|
||||||
|
} else if (isUsingContentResolution) {
|
||||||
|
// When using content resolution rather than ads, we need to try and find the closest match if there is no exact match
|
||||||
|
if (closestFormat != null) {
|
||||||
|
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) {
|
||||||
|
// Higher quality match
|
||||||
|
closestFormat = format;
|
||||||
|
closestTrackIndex = j;
|
||||||
|
}
|
||||||
|
} else if(format.height < height) {
|
||||||
|
closestFormat = format;
|
||||||
|
closestTrackIndex = j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// This is a fallback if the new period contains only higher resolutions than the user has selected
|
||||||
|
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
||||||
|
// No close match found - so we pick the lowest quality
|
||||||
|
int minHeight = Integer.MAX_VALUE;
|
||||||
|
for (int j = 0; j < group.length; j++) {
|
||||||
|
Format format = group.getFormat(j);
|
||||||
|
if (format.height < minHeight) {
|
||||||
|
minHeight = format.height;
|
||||||
|
groupIndex = i;
|
||||||
|
tracks[0] = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Selecting the closest match found
|
||||||
|
if (closestFormat != null && closestTrackIndex != -1) {
|
||||||
|
// We found the closest match instead of an exact one
|
||||||
|
groupIndex = i;
|
||||||
|
tracks[0] = closestTrackIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
|
} else if (rendererIndex == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
|
||||||
// Use system settings if possible
|
// Use system settings if possible
|
||||||
@ -1468,6 +1606,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.backBufferDurationMs = backBufferDurationMs;
|
this.backBufferDurationMs = backBufferDurationMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContentStartTime(int contentStartTime) {
|
||||||
|
this.contentStartTime = (long)contentStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDisableBuffering(boolean disableBuffering) {
|
public void setDisableBuffering(boolean disableBuffering) {
|
||||||
this.disableBuffering = disableBuffering;
|
this.disableBuffering = disableBuffering;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
|
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
|
||||||
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
|
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
|
||||||
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
||||||
|
private static final String PROP_CONTENT_START_TIME = "contentStartTime";
|
||||||
private static final String PROP_DISABLE_FOCUS = "disableFocus";
|
private static final String PROP_DISABLE_FOCUS = "disableFocus";
|
||||||
private static final String PROP_DISABLE_BUFFERING = "disableBuffering";
|
private static final String PROP_DISABLE_BUFFERING = "disableBuffering";
|
||||||
private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError";
|
private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError";
|
||||||
@ -302,6 +303,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
videoView.setBackBufferDurationMs(backBufferDurationMs);
|
videoView.setBackBufferDurationMs(backBufferDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_CONTENT_START_TIME, defaultInt = 0)
|
||||||
|
public void setContentStartTime(final ReactExoplayerView videoView, final int contentStartTime) {
|
||||||
|
videoView.setContentStartTime(contentStartTime);
|
||||||
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_DISABLE_BUFFERING, defaultBoolean = false)
|
@ReactProp(name = PROP_DISABLE_BUFFERING, defaultBoolean = false)
|
||||||
public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) {
|
public void setDisableBuffering(final ReactExoplayerView videoView, final boolean disableBuffering) {
|
||||||
videoView.setDisableBuffering(disableBuffering);
|
videoView.setDisableBuffering(disableBuffering);
|
||||||
|
Loading…
Reference in New Issue
Block a user