mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Do not use content timeline to calculate current position
In multi-period live streams, we can't use the content timeline if we want to lookup a period from the public timeline by index or uid because it may be that the content timeline has already been refreshed in the `ImaServerSideAdInsertionMediaSource` but hasn't yet arrived in `ExoPlayerImpl`. This change is taking the current position that needs to be reported to the SDK every 200ms is not relying on the content timeline. PiperOrigin-RevId: 520328126
This commit is contained in:
parent
952edb3f0a
commit
331d5479e6
1 changed files with 51 additions and 29 deletions
|
|
@ -91,6 +91,7 @@ import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||||
import com.google.ads.interactivemedia.v3.api.StreamDisplayContainer;
|
import com.google.ads.interactivemedia.v3.api.StreamDisplayContainer;
|
||||||
import com.google.ads.interactivemedia.v3.api.StreamManager;
|
import com.google.ads.interactivemedia.v3.api.StreamManager;
|
||||||
import com.google.ads.interactivemedia.v3.api.StreamRequest;
|
import com.google.ads.interactivemedia.v3.api.StreamRequest;
|
||||||
|
import com.google.ads.interactivemedia.v3.api.StreamRequest.StreamFormat;
|
||||||
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
|
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
|
||||||
import com.google.ads.interactivemedia.v3.api.player.VideoStreamPlayer;
|
import com.google.ads.interactivemedia.v3.api.player.VideoStreamPlayer;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
@ -102,6 +103,7 @@ import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
|
@ -158,7 +160,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
public MediaSource createMediaSource(MediaItem mediaItem) {
|
public MediaSource createMediaSource(MediaItem mediaItem) {
|
||||||
checkNotNull(mediaItem.localConfiguration);
|
checkNotNull(mediaItem.localConfiguration);
|
||||||
Player player = checkNotNull(adsLoader.player);
|
Player player = checkNotNull(adsLoader.player);
|
||||||
StreamPlayer streamPlayer = new StreamPlayer(player, mediaItem);
|
Uri streamRequestUri = checkNotNull(mediaItem.localConfiguration).uri;
|
||||||
|
StreamRequest streamRequest =
|
||||||
|
ImaServerSideAdInsertionUriBuilder.createStreamRequest(streamRequestUri);
|
||||||
|
StreamPlayer streamPlayer = new StreamPlayer(player, mediaItem, streamRequest);
|
||||||
ImaSdkFactory imaSdkFactory = ImaSdkFactory.getInstance();
|
ImaSdkFactory imaSdkFactory = ImaSdkFactory.getInstance();
|
||||||
StreamDisplayContainer streamDisplayContainer =
|
StreamDisplayContainer streamDisplayContainer =
|
||||||
createStreamDisplayContainer(imaSdkFactory, adsLoader.configuration, streamPlayer);
|
createStreamDisplayContainer(imaSdkFactory, adsLoader.configuration, streamPlayer);
|
||||||
|
|
@ -167,8 +172,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
adsLoader.context, adsLoader.configuration.imaSdkSettings, streamDisplayContainer);
|
adsLoader.context, adsLoader.configuration.imaSdkSettings, streamDisplayContainer);
|
||||||
ImaServerSideAdInsertionMediaSource mediaSource =
|
ImaServerSideAdInsertionMediaSource mediaSource =
|
||||||
new ImaServerSideAdInsertionMediaSource(
|
new ImaServerSideAdInsertionMediaSource(
|
||||||
mediaItem,
|
|
||||||
player,
|
player,
|
||||||
|
mediaItem,
|
||||||
|
streamRequest,
|
||||||
adsLoader,
|
adsLoader,
|
||||||
imaAdsLoader,
|
imaAdsLoader,
|
||||||
streamPlayer,
|
streamPlayer,
|
||||||
|
|
@ -512,16 +518,18 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
private AdPlaybackState adPlaybackState;
|
private AdPlaybackState adPlaybackState;
|
||||||
|
|
||||||
private ImaServerSideAdInsertionMediaSource(
|
private ImaServerSideAdInsertionMediaSource(
|
||||||
MediaItem mediaItem,
|
|
||||||
Player player,
|
Player player,
|
||||||
|
MediaItem mediaItem,
|
||||||
|
StreamRequest streamRequest,
|
||||||
AdsLoader adsLoader,
|
AdsLoader adsLoader,
|
||||||
com.google.ads.interactivemedia.v3.api.AdsLoader sdkAdsLoader,
|
com.google.ads.interactivemedia.v3.api.AdsLoader sdkAdsLoader,
|
||||||
StreamPlayer streamPlayer,
|
StreamPlayer streamPlayer,
|
||||||
MediaSource.Factory contentMediaSourceFactory,
|
MediaSource.Factory contentMediaSourceFactory,
|
||||||
@Nullable AdEventListener applicationAdEventListener,
|
@Nullable AdEventListener applicationAdEventListener,
|
||||||
@Nullable AdErrorEvent.AdErrorListener applicationAdErrorListener) {
|
@Nullable AdErrorListener applicationAdErrorListener) {
|
||||||
this.mediaItem = mediaItem;
|
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
this.mediaItem = mediaItem;
|
||||||
|
this.streamRequest = streamRequest;
|
||||||
this.adsLoader = adsLoader;
|
this.adsLoader = adsLoader;
|
||||||
this.sdkAdsLoader = sdkAdsLoader;
|
this.sdkAdsLoader = sdkAdsLoader;
|
||||||
this.streamPlayer = streamPlayer;
|
this.streamPlayer = streamPlayer;
|
||||||
|
|
@ -535,7 +543,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequestUri);
|
adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequestUri);
|
||||||
loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(streamRequestUri);
|
loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(streamRequestUri);
|
||||||
streamRequest = ImaServerSideAdInsertionUriBuilder.createStreamRequest(streamRequestUri);
|
streamRequest = ImaServerSideAdInsertionUriBuilder.createStreamRequest(streamRequestUri);
|
||||||
boolean isDashStream = streamRequest.getFormat().equals(StreamRequest.StreamFormat.DASH);
|
boolean isDashStream = Objects.equals(streamRequest.getFormat(), StreamFormat.DASH);
|
||||||
componentListener =
|
componentListener =
|
||||||
new ComponentListener(
|
new ComponentListener(
|
||||||
isLiveStream
|
isLiveStream
|
||||||
|
|
@ -679,7 +687,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
private void invalidateServerSideAdInsertionAdPlaybackState() {
|
private void invalidateServerSideAdInsertionAdPlaybackState() {
|
||||||
if (!adPlaybackState.equals(AdPlaybackState.NONE) && contentTimeline != null) {
|
if (!adPlaybackState.equals(AdPlaybackState.NONE) && contentTimeline != null) {
|
||||||
ImmutableMap<Object, AdPlaybackState> splitAdPlaybackStates;
|
ImmutableMap<Object, AdPlaybackState> splitAdPlaybackStates;
|
||||||
if (streamRequest.getFormat() == StreamRequest.StreamFormat.DASH) {
|
if (streamRequest.getFormat() == StreamFormat.DASH) {
|
||||||
// DASH ad groups are always split by period.
|
// DASH ad groups are always split by period.
|
||||||
splitAdPlaybackStates = splitAdPlaybackStateForPeriods(adPlaybackState, contentTimeline);
|
splitAdPlaybackStates = splitAdPlaybackStateForPeriods(adPlaybackState, contentTimeline);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1096,6 +1104,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
private final MediaItem mediaItem;
|
private final MediaItem mediaItem;
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
|
private final boolean isDashStream;
|
||||||
|
|
||||||
private ImmutableMap<Object, AdPlaybackState> adPlaybackStates;
|
private ImmutableMap<Object, AdPlaybackState> adPlaybackStates;
|
||||||
@Nullable private Timeline contentTimeline;
|
@Nullable private Timeline contentTimeline;
|
||||||
|
|
@ -1103,9 +1112,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
@Nullable private StreamLoadListener streamLoadListener;
|
@Nullable private StreamLoadListener streamLoadListener;
|
||||||
|
|
||||||
/** Creates an instance. */
|
/** Creates an instance. */
|
||||||
public StreamPlayer(Player player, MediaItem mediaItem) {
|
public StreamPlayer(Player player, MediaItem mediaItem, StreamRequest streamRequest) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.mediaItem = mediaItem;
|
this.mediaItem = mediaItem;
|
||||||
|
this.isDashStream = streamRequest.getFormat() == StreamFormat.DASH;
|
||||||
callbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
callbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
||||||
adPlaybackStates = ImmutableMap.of();
|
adPlaybackStates = ImmutableMap.of();
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
|
|
@ -1171,30 +1181,42 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
||||||
int currentPeriodIndex = player.getCurrentPeriodIndex();
|
int currentPeriodIndex = player.getCurrentPeriodIndex();
|
||||||
timeline.getPeriod(currentPeriodIndex, period, /* setIds= */ true);
|
timeline.getPeriod(currentPeriodIndex, period, /* setIds= */ true);
|
||||||
timeline.getWindow(player.getCurrentMediaItemIndex(), window);
|
timeline.getWindow(player.getCurrentMediaItemIndex(), window);
|
||||||
|
long streamPositionMs;
|
||||||
// We need the period of the content timeline because its period UIDs are the key used in the
|
if (isDashStream && window.isLive()) {
|
||||||
// ad playback state map. The period UIDs of the public timeline are different (masking).
|
// In multi-period live streams, we can't assume to find the same period in both timelines
|
||||||
Timeline.Period contentPeriod =
|
// with a given period index. Calculate stream position from the period structure instead.
|
||||||
|
streamPositionMs =
|
||||||
|
player.isPlayingAd()
|
||||||
|
? window.windowStartTimeMs
|
||||||
|
+ usToMs(period.positionInWindowUs)
|
||||||
|
+ player.getCurrentPosition()
|
||||||
|
: window.windowStartTimeMs + player.getContentPosition();
|
||||||
|
} else {
|
||||||
|
// The map of ad playback states is keyed with the period UID of the content timeline. In
|
||||||
|
// timelines that do not change the periods (VOD and single period live), we can use the
|
||||||
|
// period index in both timelines.
|
||||||
|
Timeline.Period contentPeriod =
|
||||||
|
checkNotNull(contentTimeline)
|
||||||
|
.getPeriod(
|
||||||
|
currentPeriodIndex - window.firstPeriodIndex,
|
||||||
|
new Timeline.Period(),
|
||||||
|
/* setIds= */ true);
|
||||||
|
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(contentPeriod.uid));
|
||||||
|
// Calculate the stream position from the current position and the playback state.
|
||||||
|
streamPositionMs =
|
||||||
|
usToMs(ServerSideAdInsertionUtil.getStreamPositionUs(player, adPlaybackState));
|
||||||
|
if (window.windowStartTimeMs != C.TIME_UNSET) {
|
||||||
|
// Add the time since epoch at start of the window for live streams.
|
||||||
|
streamPositionMs += window.windowStartTimeMs + period.getPositionInWindowMs();
|
||||||
|
} else if (currentPeriodIndex > window.firstPeriodIndex) {
|
||||||
|
// Add the end position of the previous period in the underlying stream.
|
||||||
checkNotNull(contentTimeline)
|
checkNotNull(contentTimeline)
|
||||||
.getPeriod(
|
.getPeriod(
|
||||||
currentPeriodIndex - window.firstPeriodIndex,
|
currentPeriodIndex - window.firstPeriodIndex - 1,
|
||||||
new Timeline.Period(),
|
contentPeriod,
|
||||||
/* setIds= */ true);
|
/* setIds= */ true);
|
||||||
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(contentPeriod.uid));
|
streamPositionMs += usToMs(contentPeriod.positionInWindowUs + contentPeriod.durationUs);
|
||||||
|
}
|
||||||
long streamPositionMs =
|
|
||||||
usToMs(ServerSideAdInsertionUtil.getStreamPositionUs(player, adPlaybackState));
|
|
||||||
if (window.windowStartTimeMs != C.TIME_UNSET) {
|
|
||||||
// Add the time since epoch at start of the window for live streams.
|
|
||||||
streamPositionMs += window.windowStartTimeMs + period.getPositionInWindowMs();
|
|
||||||
} else if (currentPeriodIndex > window.firstPeriodIndex) {
|
|
||||||
// Add the end position of the previous period in the underlying stream.
|
|
||||||
checkNotNull(contentTimeline)
|
|
||||||
.getPeriod(
|
|
||||||
currentPeriodIndex - window.firstPeriodIndex - 1,
|
|
||||||
contentPeriod,
|
|
||||||
/* setIds= */ true);
|
|
||||||
streamPositionMs += usToMs(contentPeriod.positionInWindowUs + contentPeriod.durationUs);
|
|
||||||
}
|
}
|
||||||
return new VideoProgressUpdate(
|
return new VideoProgressUpdate(
|
||||||
streamPositionMs,
|
streamPositionMs,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue