mirror of
https://github.com/samsonjs/media.git
synced 2026-04-04 11:05:47 +00:00
Calculate SSAI window duration for live periods with unset duration.
We currently skip this calculation entirely, but it can be added by calculating the window duration using the wrapped window's duration and the provided AdPlaybackState. Issue: google/ExoPlayer#10764 PiperOrigin-RevId: 488614767
This commit is contained in:
parent
66dc6b3242
commit
7a7d08343a
3 changed files with 101 additions and 8 deletions
|
|
@ -119,6 +119,9 @@ Release notes
|
|||
([#10510](https://github.com/google/ExoPlayer/issues/10510)).
|
||||
* Prevent skipping mid-roll ads when seeking to the end of the content
|
||||
([#10685](https://github.com/google/ExoPlayer/issues/10685)).
|
||||
* Correctly calculate window duration for live streams with server-side
|
||||
inserted ads, for example IMA DAI
|
||||
([#10764](https://github.com/google/ExoPlayer/issues/10764)).
|
||||
* FFmpeg extension:
|
||||
* Add newly required flags to link FFmpeg libraries with NDK 23.1.7779620
|
||||
and above ([#9933](https://github.com/google/ExoPlayer/issues/9933)).
|
||||
|
|
|
|||
|
|
@ -1019,8 +1019,9 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
|
||||
Period period = new Period();
|
||||
Object firstPeriodUid =
|
||||
checkNotNull(getPeriod(window.firstPeriodIndex, new Period(), /* setIds= */ true).uid);
|
||||
checkNotNull(getPeriod(window.firstPeriodIndex, period, /* setIds= */ true).uid);
|
||||
AdPlaybackState firstAdPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid));
|
||||
long positionInPeriodUs =
|
||||
getMediaPeriodPositionUsForContent(
|
||||
|
|
@ -1032,11 +1033,21 @@ public final class ServerSideAdInsertionMediaSource extends BaseMediaSource
|
|||
window.durationUs = firstAdPlaybackState.contentDurationUs - positionInPeriodUs;
|
||||
}
|
||||
} else {
|
||||
Period lastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, new Period());
|
||||
Period originalLastPeriod =
|
||||
super.getPeriod(/* periodIndex= */ window.lastPeriodIndex, period, /* setIds= */ true);
|
||||
long originalLastPeriodPositionInWindowUs = originalLastPeriod.positionInWindowUs;
|
||||
AdPlaybackState lastAdPlaybackState =
|
||||
checkNotNull(adPlaybackStates.get(originalLastPeriod.uid));
|
||||
Period adjustedLastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, period);
|
||||
long originalWindowDurationInLastPeriodUs =
|
||||
window.durationUs - originalLastPeriodPositionInWindowUs;
|
||||
long adjustedWindowDurationInLastPeriodUs =
|
||||
getMediaPeriodPositionUsForContent(
|
||||
originalWindowDurationInLastPeriodUs,
|
||||
/* nextAdGroupIndex= */ C.INDEX_UNSET,
|
||||
lastAdPlaybackState);
|
||||
window.durationUs =
|
||||
lastPeriod.durationUs == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: lastPeriod.positionInWindowUs + lastPeriod.durationUs;
|
||||
adjustedLastPeriod.positionInWindowUs + adjustedWindowDurationInLastPeriodUs;
|
||||
}
|
||||
window.positionInFirstPeriodUs = positionInPeriodUs;
|
||||
return window;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import android.graphics.SurfaceTexture;
|
|||
import android.util.Pair;
|
||||
import android.view.Surface;
|
||||
import androidx.media3.common.AdPlaybackState;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
|
|
@ -43,6 +44,7 @@ import androidx.media3.exoplayer.ExoPlayer;
|
|||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||
import androidx.media3.test.utils.DumpFileAsserts;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
|
|
@ -71,15 +73,15 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||
private static final String TEST_ASSET_DUMP = "playbackdumps/mp4/ssai-sample.mp4.dump";
|
||||
|
||||
@Test
|
||||
public void timeline_containsAdsDefinedInAdPlaybackState() throws Exception {
|
||||
public void timeline_vodSinglePeriod_containsAdsDefinedInAdPlaybackState() throws Exception {
|
||||
FakeTimeline wrappedTimeline =
|
||||
new FakeTimeline(
|
||||
new FakeTimeline.TimelineWindowDefinition(
|
||||
/* periodCount= */ 1,
|
||||
/* id= */ 0,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* isLive= */ true,
|
||||
/* isDynamic= */ false,
|
||||
/* isLive= */ false,
|
||||
/* isPlaceholder= */ false,
|
||||
/* durationUs= */ 10_000_000,
|
||||
/* defaultPositionUs= */ 3_000_000,
|
||||
|
|
@ -144,6 +146,83 @@ public final class ServerSideAdInsertionMediaSourceTest {
|
|||
assertThat(window.durationUs).isEqualTo(9_800_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeline_liveSinglePeriodWithUnsetPeriodDuration_containsAdsDefinedInAdPlaybackState()
|
||||
throws Exception {
|
||||
Timeline wrappedTimeline =
|
||||
new SinglePeriodTimeline(
|
||||
/* periodDurationUs= */ C.TIME_UNSET,
|
||||
/* windowDurationUs= */ 10_000_000,
|
||||
/* windowPositionInPeriodUs= */ 42_000_000L,
|
||||
/* windowDefaultStartPositionUs= */ 3_000_000,
|
||||
/* isSeekable= */ true,
|
||||
/* isDynamic= */ true,
|
||||
/* useLiveConfiguration= */ true,
|
||||
/* manifest= */ null,
|
||||
/* mediaItem= */ MediaItem.EMPTY);
|
||||
ServerSideAdInsertionMediaSource mediaSource =
|
||||
new ServerSideAdInsertionMediaSource(
|
||||
new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null);
|
||||
// Test with one ad group before the window, and the window starting within the second ad group.
|
||||
AdPlaybackState adPlaybackState =
|
||||
new AdPlaybackState(
|
||||
/* adsId= */ new Object(), /* adGroupTimesUs= */ 15_000_000, 41_500_000, 42_200_000)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
|
||||
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
|
||||
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs= */ 500_000)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs= */ 300_000, 100_000)
|
||||
.withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs= */ 400_000)
|
||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100_000)
|
||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000)
|
||||
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000);
|
||||
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
|
||||
mediaSource.setAdPlaybackStates(
|
||||
ImmutableMap.of(
|
||||
wrappedTimeline.getPeriod(
|
||||
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
|
||||
.uid,
|
||||
adPlaybackState));
|
||||
|
||||
mediaSource.prepareSource(
|
||||
(source, timeline) -> timelineReference.set(timeline),
|
||||
/* mediaTransferListener= */ null,
|
||||
PlayerId.UNSET);
|
||||
runMainLooperUntil(() -> timelineReference.get() != null);
|
||||
|
||||
Timeline timeline = timelineReference.get();
|
||||
assertThat(timeline.getPeriodCount()).isEqualTo(1);
|
||||
Timeline.Period period = timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period());
|
||||
assertThat(period.getAdGroupCount()).isEqualTo(3);
|
||||
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 0)).isEqualTo(1);
|
||||
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 1)).isEqualTo(2);
|
||||
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 2)).isEqualTo(1);
|
||||
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 0)).isEqualTo(15_000_000);
|
||||
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 1)).isEqualTo(41_500_000);
|
||||
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 2)).isEqualTo(42_200_000);
|
||||
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(500_000);
|
||||
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(300_000);
|
||||
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1))
|
||||
.isEqualTo(100_000);
|
||||
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0))
|
||||
.isEqualTo(400_000);
|
||||
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 0)).isEqualTo(100_000);
|
||||
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 1)).isEqualTo(400_000);
|
||||
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 2)).isEqualTo(200_000);
|
||||
assertThat(period.getDurationUs()).isEqualTo(C.TIME_UNSET);
|
||||
// positionInWindowUs + sum(adDurationsBeforeWindow) - sum(contentResumeOffsetsBeforeWindow)
|
||||
assertThat(period.getPositionInWindowUs()).isEqualTo(-41_600_000);
|
||||
Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());
|
||||
assertThat(window.positionInFirstPeriodUs).isEqualTo(41_600_000);
|
||||
// windowDurationUs - sum(adDurationsInWindow) + sum(applicableContentResumeOffsetUs)
|
||||
assertThat(window.durationUs).isEqualTo(9_800_000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows() {
|
||||
ServerSideAdInsertionMediaSource mediaSource =
|
||||
|
|
|
|||
Loading…
Reference in a new issue