Split ad playback state and recalculate window and period duriations

This change enables the ImaServerSideAdInsertionMediaSource for multi-period content. The global ad playback state is split into pieces for each period and the window and period durations are calculated accordingly in the ServerSideAdInsertionTimeline.

For multi-period live content (DASH), the ad playback state is not set with this change. This is deferred to a follow up CL. Splitting is very tricky. For each timeline update the windowStartTimeUs may vary for some milliseconds relative to the start of the period.positionInWindowUs. This requires to either introduce some fuzzy logic or to choose a different approach than for multi-period VOD. Because mistakes within the playback states of subsequent moving live windows produces crashes, it seems sensible to defer this for now and keep this change in a separate future CL (unblock further work, easy to rollback).

In this state, live DASH stream are working and the ad overlay is placed over the player correctly bu the SDK. However, ads are not reported by the position discontinuity event. Similarly, the player.isPlayingAd() does never returns true when a ad period is playing.

PiperOrigin-RevId: 422539770
This commit is contained in:
bachinger 2022-01-18 14:16:05 +00:00 committed by Ian Baker
parent 05d5937855
commit 05e23996fa
2 changed files with 30 additions and 41 deletions

View File

@ -994,7 +994,6 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
public ServerSideAdInsertionTimeline(
Timeline contentTimeline, ImmutableMap<Object, AdPlaybackState> adPlaybackStates) {
super(contentTimeline);
checkState(contentTimeline.getPeriodCount() == 1);
checkState(contentTimeline.getWindowCount() == 1);
Period period = new Period();
for (int i = 0; i < contentTimeline.getPeriodCount(); i++) {
@ -1008,25 +1007,23 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
Object firstPeriodUid =
checkNotNull(getPeriod(/* periodIndex= */ 0, new Period(), /* setIds= */ true).uid);
AdPlaybackState adPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid));
checkNotNull(getPeriod(window.firstPeriodIndex, new Period(), /* setIds= */ true).uid);
AdPlaybackState firstAdPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid));
long positionInPeriodUs =
getMediaPeriodPositionUsForContent(
window.positionInFirstPeriodUs,
/* nextAdGroupIndex= */ C.INDEX_UNSET,
adPlaybackState);
firstAdPlaybackState);
if (window.durationUs == C.TIME_UNSET) {
if (adPlaybackState.contentDurationUs != C.TIME_UNSET) {
window.durationUs = adPlaybackState.contentDurationUs - positionInPeriodUs;
if (firstAdPlaybackState.contentDurationUs != C.TIME_UNSET) {
window.durationUs = firstAdPlaybackState.contentDurationUs - positionInPeriodUs;
}
} else {
long actualWindowEndPositionInPeriodUs = window.positionInFirstPeriodUs + window.durationUs;
long windowEndPositionInPeriodUs =
getMediaPeriodPositionUsForContent(
actualWindowEndPositionInPeriodUs,
/* nextAdGroupIndex= */ C.INDEX_UNSET,
adPlaybackState);
window.durationUs = windowEndPositionInPeriodUs - positionInPeriodUs;
Period lastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, new Period());
window.durationUs =
lastPeriod.durationUs == C.TIME_UNSET
? C.TIME_UNSET
: lastPeriod.positionInWindowUs + lastPeriod.durationUs;
}
window.positionInFirstPeriodUs = positionInPeriodUs;
return window;
@ -1044,11 +1041,26 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
getMediaPeriodPositionUsForContent(
durationUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState);
}
long positionInWindowUs =
-getMediaPeriodPositionUsForContent(
-period.getPositionInWindowUs(),
/* nextAdGroupIndex= */ C.INDEX_UNSET,
adPlaybackState);
long positionInWindowUs = 0;
Period innerPeriod = new Period();
for (int i = 0; i < periodIndex + 1; i++) {
timeline.getPeriod(/* periodIndex= */ i, innerPeriod, /* setIds= */ true);
AdPlaybackState innerAdPlaybackState = checkNotNull(adPlaybackStates.get(innerPeriod.uid));
if (i == 0) {
positionInWindowUs =
-getMediaPeriodPositionUsForContent(
-innerPeriod.getPositionInWindowUs(),
/* nextAdGroupIndex= */ C.INDEX_UNSET,
innerAdPlaybackState);
}
if (i != periodIndex) {
positionInWindowUs +=
getMediaPeriodPositionUsForContent(
innerPeriod.durationUs,
/* nextAdGroupIndex= */ C.INDEX_UNSET,
innerAdPlaybackState);
}
}
period.set(
period.id,
period.uid,

View File

@ -74,29 +74,6 @@ public final class ServerSideAdInsertionUtil {
adPlaybackState, insertionIndex, adDurationUs, contentResumeOffsetUs);
}
/**
* Returns the duration of the underlying server-side inserted ads stream for the current {@link
* Timeline.Period} in the {@link Player}.
*
* @param player The {@link Player}.
* @param adPlaybackState The {@link AdPlaybackState} defining the ad groups.
* @return The duration of the underlying server-side inserted ads stream, in microseconds, or
* {@link C#TIME_UNSET} if it can't be determined.
*/
public static long getStreamDurationUs(Player player, AdPlaybackState adPlaybackState) {
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return C.TIME_UNSET;
}
Timeline.Period period =
timeline.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period());
if (period.durationUs == C.TIME_UNSET) {
return C.TIME_UNSET;
}
return getStreamPositionUsForContent(
period.durationUs, /* nextAdGroupIndex= */ C.INDEX_UNSET, adPlaybackState);
}
/**
* Returns the position in the underlying server-side inserted ads stream for the current playback
* position in the {@link Player}.