diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 14d3f9af17..99896816ae 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -15,6 +15,7 @@ */ package androidx.media3.exoplayer; +import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Util.castNonNull; import static java.lang.Math.max; import static java.lang.Math.min; @@ -2153,14 +2154,20 @@ import java.util.concurrent.atomic.AtomicBoolean; // If we advance more than one period at a time, notify listeners after each update. maybeNotifyPlaybackInfoChanged(); } - MediaPeriodHolder newPlayingPeriodHolder = queue.advancePlayingPeriod(); + MediaPeriodHolder newPlayingPeriodHolder = checkNotNull(queue.advancePlayingPeriod()); + boolean isCancelledSSAIAdTransition = + playbackInfo.periodId.periodUid.equals(newPlayingPeriodHolder.info.id.periodUid) + && playbackInfo.periodId.adGroupIndex == C.INDEX_UNSET + && newPlayingPeriodHolder.info.id.adGroupIndex == C.INDEX_UNSET + && playbackInfo.periodId.nextAdGroupIndex + != newPlayingPeriodHolder.info.id.nextAdGroupIndex; playbackInfo = handlePositionDiscontinuity( newPlayingPeriodHolder.info.id, newPlayingPeriodHolder.info.startPositionUs, newPlayingPeriodHolder.info.requestedContentPositionUs, /* discontinuityStartPositionUs= */ newPlayingPeriodHolder.info.startPositionUs, - /* reportDiscontinuity= */ true, + /* reportDiscontinuity= */ !isCancelledSSAIAdTransition, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); resetPendingPauseAtEndOfPeriod(); updatePlaybackPositions(); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodInfo.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodInfo.java index 520fd63606..cd50c7e739 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodInfo.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodInfo.java @@ -39,9 +39,10 @@ import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; public final long requestedContentPositionUs; /** * The end position to which the media period's content is clipped in order to play a following ad - * group, in microseconds, or {@link C#TIME_UNSET} if there is no following ad group or if this - * media period is an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll ad - * follows at the end of this content media period. + * group or to terminate a server side ad inserted stream before a played postroll, in + * microseconds, or {@link C#TIME_UNSET} if the content is not clipped or if this media period is + * an ad. The value {@link C#TIME_END_OF_SOURCE} indicates that a postroll ad follows at the end + * of this content media period. */ public final long endPositionUs; /** diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java index 12edf0605c..a0731462bb 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/MediaPeriodQueue.java @@ -21,6 +21,7 @@ import static java.lang.Math.max; import android.os.Handler; import android.util.Pair; import androidx.annotation.Nullable; +import androidx.media3.common.AdPlaybackState; import androidx.media3.common.C; import androidx.media3.common.Player.RepeatMode; import androidx.media3.common.Timeline; @@ -801,8 +802,14 @@ import com.google.common.collect.ImmutableList; } else { // Play the next ad group if it's still available. int adIndexInAdGroup = period.getFirstAdIndexToPlay(currentPeriodId.nextAdGroupIndex); - if (adIndexInAdGroup == period.getAdCountInAdGroup(currentPeriodId.nextAdGroupIndex)) { - // The next ad group has no ads left to play. Play content from the end position instead. + boolean isPlayedServerSideInsertedAd = + period.isServerSideInsertedAdGroup(currentPeriodId.nextAdGroupIndex) + && period.getAdState(currentPeriodId.nextAdGroupIndex, adIndexInAdGroup) + == AdPlaybackState.AD_STATE_PLAYED; + if (adIndexInAdGroup == period.getAdCountInAdGroup(currentPeriodId.nextAdGroupIndex) + || isPlayedServerSideInsertedAd) { + // The next ad group has no ads left to play or is a played SSAI ad group. Play content from + // the end position instead. long startPositionUs = getMinStartPositionAfterAdGroupUs( timeline, currentPeriodId.periodUid, currentPeriodId.nextAdGroupIndex); @@ -888,6 +895,20 @@ import com.google.common.collect.ImmutableList; long windowSequenceNumber) { timeline.getPeriodByUid(periodUid, period); int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(startPositionUs); + boolean clipPeriodAtContentDuration = false; + if (nextAdGroupIndex == C.INDEX_UNSET) { + // Clip SSAI streams when at the end of the period. + clipPeriodAtContentDuration = + period.getAdGroupCount() > 0 + && period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount()); + } else if (period.isServerSideInsertedAdGroup(nextAdGroupIndex) + && period.getAdGroupTimeUs(nextAdGroupIndex) == period.durationUs) { + if (period.hasPlayedAdGroup(nextAdGroupIndex)) { + // Clip period before played SSAI post-rolls. + nextAdGroupIndex = C.INDEX_UNSET; + clipPeriodAtContentDuration = true; + } + } MediaPeriodId id = new MediaPeriodId(periodUid, windowSequenceNumber, nextAdGroupIndex); boolean isLastInPeriod = isLastInPeriod(id); boolean isLastInWindow = isLastInWindow(timeline, id); @@ -897,7 +918,7 @@ import com.google.common.collect.ImmutableList; long endPositionUs = nextAdGroupIndex != C.INDEX_UNSET ? period.getAdGroupTimeUs(nextAdGroupIndex) - : C.TIME_UNSET; + : clipPeriodAtContentDuration ? period.durationUs : C.TIME_UNSET; long durationUs = endPositionUs == C.TIME_UNSET || endPositionUs == C.TIME_END_OF_SOURCE ? period.durationUs diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java index 1ae8722199..67300997f5 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/ExoPlayerTest.java @@ -46,8 +46,11 @@ import static androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE; import static androidx.media3.common.Player.COMMAND_SET_VOLUME; import static androidx.media3.common.Player.COMMAND_STOP; import static androidx.media3.common.Player.STATE_ENDED; +import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; +import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US; +import static androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US; import static androidx.media3.test.utils.TestUtil.assertTimelinesSame; import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil; import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition; @@ -789,21 +792,8 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true); player.release(); - // There is still one discontinuity from content to content for the failed ad insertion. - PositionInfo positionInfo = - new PositionInfo( - window.uid, - /* mediaItemIndex= */ 0, - window.mediaItem, - period.uid, - /* periodIndex= */ 0, - /* positionMs= */ 5_000, - /* contentPositionMs= */ 5_000, - /* adGroupIndex= */ C.INDEX_UNSET, - /* adIndexInAdGroup= */ C.INDEX_UNSET); - verify(mockListener) - .onPositionDiscontinuity( - positionInfo, positionInfo, Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + // Content to content transition is ignored. + verify(mockListener, never()).onPositionDiscontinuity(any(), any(), anyInt()); } @Test @@ -863,24 +853,7 @@ public final class ExoPlayerTest { .getPeriod(/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true); player.release(); - // There is still one discontinuity from content to content for the failed ad insertion and the - // normal ad transition for the successful ad insertion. - PositionInfo positionInfoFailedAd = - new PositionInfo( - window.uid, - /* mediaItemIndex= */ 0, - window.mediaItem, - period.uid, - /* periodIndex= */ 0, - /* positionMs= */ 5_000, - /* contentPositionMs= */ 5_000, - /* adGroupIndex= */ C.INDEX_UNSET, - /* adIndexInAdGroup= */ C.INDEX_UNSET); - verify(mockListener) - .onPositionDiscontinuity( - positionInfoFailedAd, - positionInfoFailedAd, - Player.DISCONTINUITY_REASON_AUTO_TRANSITION); + // There content to content discontinuity after the failed ad is suppressed. PositionInfo positionInfoContentAtSuccessfulAd = new PositionInfo( window.uid, @@ -5029,10 +5002,9 @@ public final class ExoPlayerTest { oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1, 2, 0, 0, 0, 0).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(1, 2, 0, 0, 0, 0).inOrder(); // seek discontinuities assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); @@ -5121,10 +5093,9 @@ public final class ExoPlayerTest { oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1, 2, 0, 0, 0).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(1, 2, 0, 0, 0).inOrder(); // seek assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); @@ -5184,10 +5155,9 @@ public final class ExoPlayerTest { oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1, 0, 0, 0, 0, 0).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(1, 0, 0, 0, 0, 0).inOrder(); // seek discontinuity assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(newPositions.get(0).periodIndex).isEqualTo(0); @@ -5252,10 +5222,9 @@ public final class ExoPlayerTest { oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1, 2, 0, 0, 0, 0).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(1, 2, 0, 0, 0, 0).inOrder(); // seek discontinuity assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); @@ -5334,10 +5303,9 @@ public final class ExoPlayerTest { oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(1).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(1).inOrder(); // seek discontinuity assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); @@ -5347,7 +5315,8 @@ public final class ExoPlayerTest { } @Test - public void play_playedSSAIPreMidPostRolls_skipsAllAds() throws Exception { + public void play_playedSSAIPreMidPostRollsMultiPeriodWindow_contentPeriodTransitionsOnly() + throws Exception { ArgumentCaptor oldPositionArgumentCaptor = ArgumentCaptor.forClass(PositionInfo.class); ArgumentCaptor newPositionArgumentCaptor = @@ -5371,7 +5340,7 @@ public final class ExoPlayerTest { AtomicReference sourceReference = new AtomicReference<>(); sourceReference.set( new ServerSideAdInsertionMediaSource( - new FakeMediaSource(adTimeline), + new FakeMediaSource(adTimeline, ExoPlayerTestRunner.AUDIO_FORMAT), contentTimeline -> { sourceReference .get() @@ -5385,16 +5354,18 @@ public final class ExoPlayerTest { runUntilPlaybackState(player, Player.STATE_ENDED); player.release(); + ArgumentCaptor playbackStateCaptor = ArgumentCaptor.forClass(Integer.class); + verify(listener, times(3)).onPlaybackStateChanged(playbackStateCaptor.capture()); + assertThat(playbackStateCaptor.getAllValues()).containsExactly(2, 3, 4).inOrder(); verify(listener, times(3)) .onPositionDiscontinuity( oldPositionArgumentCaptor.capture(), newPositionArgumentCaptor.capture(), reasonArgumentCaptor.capture()); + assertThat(reasonArgumentCaptor.getAllValues()).containsExactly(0, 0, 0).inOrder(); List oldPositions = oldPositionArgumentCaptor.getAllValues(); List newPositions = newPositionArgumentCaptor.getAllValues(); - List reasons = reasonArgumentCaptor.getAllValues(); - assertThat(reasons).containsExactly(0, 0, 0).inOrder(); - // Auto discontinuity from the empty ad period to the first content period. + // Auto discontinuity from the empty pre-roll period to the first content period. assertThat(oldPositions.get(0).periodIndex).isEqualTo(0); assertThat(oldPositions.get(0).adGroupIndex).isEqualTo(-1); assertThat(oldPositions.get(0).positionMs).isEqualTo(0); @@ -5407,7 +5378,7 @@ public final class ExoPlayerTest { assertThat(newPositions.get(1).periodIndex).isEqualTo(4); assertThat(newPositions.get(1).adGroupIndex).isEqualTo(-1); assertThat(newPositions.get(1).positionMs).isEqualTo(1250); - // Auto discontinuity from the second content period to the last frame of the last postroll. + // Auto discontinuity from the second content period to the last frame of the last ad period. assertThat(oldPositions.get(2).periodIndex).isEqualTo(4); assertThat(oldPositions.get(2).adGroupIndex).isEqualTo(-1); assertThat(newPositions.get(2).periodIndex).isEqualTo(7); @@ -5415,6 +5386,86 @@ public final class ExoPlayerTest { assertThat(newPositions.get(2).positionMs).isEqualTo(2500); } + @Test + public void play_playedSSAIPreMidPostRollsSinglePeriodWindow_noDiscontinuities() + throws Exception { + AdPlaybackState adPlaybackState = + addAdGroupToAdPlaybackState( + new AdPlaybackState("adsId"), + /* fromPositionUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + /* contentResumeOffsetUs= */ 0, + /* adDurationsUs...= */ C.MICROS_PER_SECOND); + adPlaybackState = + addAdGroupToAdPlaybackState( + adPlaybackState, + /* fromPositionUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + + (3 * C.MICROS_PER_SECOND), + /* contentResumeOffsetUs= */ 0, + /* adDurationsUs...= */ C.MICROS_PER_SECOND); + adPlaybackState = + addAdGroupToAdPlaybackState( + adPlaybackState, + /* fromPositionUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + + (5 * C.MICROS_PER_SECOND), + /* contentResumeOffsetUs= */ 0, + /* adDurationsUs...= */ C.MICROS_PER_SECOND); + adPlaybackState = + addAdGroupToAdPlaybackState( + adPlaybackState, + /* fromPositionUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + + (9 * C.MICROS_PER_SECOND), + /* contentResumeOffsetUs= */ 0, + /* adDurationsUs...= */ C.MICROS_PER_SECOND); + adPlaybackState = + adPlaybackState.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup+ */ 0); + adPlaybackState = + adPlaybackState.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup+ */ 0); + adPlaybackState = + adPlaybackState.withPlayedAd(/* adGroupIndex= */ 2, /* adIndexInAdGroup+ */ 0); + adPlaybackState = + adPlaybackState.withPlayedAd(/* adGroupIndex= */ 3, /* adIndexInAdGroup+ */ 0); + FakeTimeline adTimeline = + new FakeTimeline( + new TimelineWindowDefinition( + /* periodCount= */ 1, + "windowId", + /* isSeekable= */ true, + /* isDynamic= */ false, + /* isLive= */ false, + /* isPlaceholder= */ false, + /* durationUs= */ DEFAULT_WINDOW_DURATION_US, + /* defaultPositionUs= */ 0, + /* windowOffsetInFirstPeriodUs= */ DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US, + /* adPlaybackStates= */ ImmutableList.of(adPlaybackState), + MediaItem.EMPTY)); + + Listener listener = mock(Listener.class); + ExoPlayer player = new TestExoPlayerBuilder(context).build(); + player.addListener(listener); + AtomicReference sourceReference = new AtomicReference<>(); + sourceReference.set( + new ServerSideAdInsertionMediaSource( + new FakeMediaSource(adTimeline, ExoPlayerTestRunner.AUDIO_FORMAT), + contentTimeline -> { + sourceReference + .get() + .setAdPlaybackStates(adTimeline.getAdPlaybackStates(/* windowIndex= */ 0)); + return true; + })); + player.setMediaSource(sourceReference.get()); + player.prepare(); + player.play(); + runUntilPlaybackState(player, Player.STATE_ENDED); + long finalPositionMs = player.getCurrentPosition(); + player.release(); + + assertThat(finalPositionMs).isEqualTo(6000); + verify(listener, never()).onPositionDiscontinuity(any(), any(), anyInt()); + ArgumentCaptor playbackStateCaptor = ArgumentCaptor.forClass(Integer.class); + verify(listener, times(3)).onPlaybackStateChanged(playbackStateCaptor.capture()); + assertThat(playbackStateCaptor.getAllValues()).containsExactly(2, 3, 4).inOrder(); + } + @Test public void becomingNoisyIgnoredIfBecomingNoisyHandlingIsDisabled() throws Exception { ExoPlayer player = new TestExoPlayerBuilder(context).build(); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java index f0c0b42e9d..c649b6a680 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/MediaPeriodQueueTest.java @@ -388,7 +388,7 @@ public final class MediaPeriodQueueTest { /* periodUid= */ firstPeriodUid, /* startPositionUs= */ SECOND_AD_START_TIME_US, /* requestedContentPositionUs= */ SECOND_AD_START_TIME_US, - /* endPositionUs= */ C.TIME_UNSET, + /* endPositionUs= */ CONTENT_DURATION_US, /* durationUs= */ CONTENT_DURATION_US, /* isFollowedByTransitionToSameStream= */ false, /* isLastInPeriod= */ true, diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java index ef8d30a56c..82a5941365 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ads/ServerSideAdInsertionMediaSourceTest.java @@ -68,7 +68,7 @@ public final class ServerSideAdInsertionMediaSourceTest { ShadowMediaCodecConfig.forAllSupportedMimeTypes(); private static final String TEST_ASSET = "asset:///media/mp4/sample.mp4"; - private static final String TEST_ASSET_DUMP = "playbackdumps/mp4/sample.mp4.dump"; + private static final String TEST_ASSET_DUMP = "playbackdumps/mp4/ssai-sample.mp4.dump"; @Test public void timeline_containsAdsDefinedInAdPlaybackState() throws Exception { diff --git a/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-sample.mp4.dump b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-sample.mp4.dump new file mode 100644 index 0000000000..5417858cbd --- /dev/null +++ b/libraries/test_data/src/test/assets/playbackdumps/mp4/ssai-sample.mp4.dump @@ -0,0 +1,79 @@ +MediaCodecAdapter (exotest.audio.aac): + buffers.length = 44 + buffers[0] = length 23, hash 47DE9131 + buffers[1] = length 6, hash 31EC5206 + buffers[2] = length 148, hash 894A176B + buffers[3] = length 189, hash CEF235A1 + buffers[4] = length 205, hash BBF5F7B0 + buffers[5] = length 210, hash F278B193 + buffers[6] = length 210, hash 82DA1589 + buffers[7] = length 207, hash 5BE231DF + buffers[8] = length 225, hash 18819EE1 + buffers[9] = length 215, hash CA7FA67B + buffers[10] = length 211, hash 581A1C18 + buffers[11] = length 216, hash ADB88187 + buffers[12] = length 229, hash 2E8BA4DC + buffers[13] = length 232, hash 22F0C510 + buffers[14] = length 235, hash 867AD0DC + buffers[15] = length 231, hash 84E823A8 + buffers[16] = length 226, hash 1BEF3A95 + buffers[17] = length 216, hash EAA345AE + buffers[18] = length 229, hash 6957411F + buffers[19] = length 219, hash 41275022 + buffers[20] = length 241, hash 6495DF96 + buffers[21] = length 228, hash 63D95906 + buffers[22] = length 238, hash 34F676F9 + buffers[23] = length 234, hash E5CBC045 + buffers[24] = length 231, hash 5FC43661 + buffers[25] = length 217, hash 682708ED + buffers[26] = length 239, hash D43780FC + buffers[27] = length 243, hash C5E17980 + buffers[28] = length 231, hash AC5837BA + buffers[29] = length 230, hash 169EE895 + buffers[30] = length 238, hash C48FF3F1 + buffers[31] = length 225, hash 531E4599 + buffers[32] = length 232, hash CB3C6B8D + buffers[33] = length 243, hash F8C94C7 + buffers[34] = length 232, hash A646A7D0 + buffers[35] = length 237, hash E8B787A5 + buffers[36] = length 228, hash 3FA7A29F + buffers[37] = length 235, hash B9B33B0A + buffers[38] = length 264, hash 71A4869E + buffers[39] = length 257, hash D049B54C + buffers[40] = length 227, hash 66757231 + buffers[41] = length 227, hash BD374F1B + buffers[42] = length 235, hash 999477F6 + buffers[43] = length 0, hash 1 +MediaCodecAdapter (exotest.video.avc): + buffers.length = 31 + buffers[0] = length 36692, hash D216076E + buffers[1] = length 5312, hash D45D3CA0 + buffers[2] = length 599, hash 1BE7812D + buffers[3] = length 7735, hash 4490F110 + buffers[4] = length 987, hash 560B5036 + buffers[5] = length 673, hash ED7CD8C7 + buffers[6] = length 523, hash 3020DF50 + buffers[7] = length 6061, hash 736C72B2 + buffers[8] = length 992, hash FE132F23 + buffers[9] = length 623, hash 5B2C1816 + buffers[10] = length 421, hash 742E69C1 + buffers[11] = length 4899, hash F72F86A1 + buffers[12] = length 568, hash 519A8E50 + buffers[13] = length 620, hash 3990AA39 + buffers[14] = length 5450, hash F06EC4AA + buffers[15] = length 1051, hash 92DFA63A + buffers[16] = length 874, hash 69587FB4 + buffers[17] = length 781, hash 36BE495B + buffers[18] = length 4725, hash AC0C8CD3 + buffers[19] = length 1022, hash 5D8BFF34 + buffers[20] = length 790, hash 99413A99 + buffers[21] = length 610, hash 5E129290 + buffers[22] = length 2751, hash 769974CB + buffers[23] = length 745, hash B78A477A + buffers[24] = length 621, hash CF741E7A + buffers[25] = length 505, hash 1DB4894E + buffers[26] = length 1268, hash C15348DC + buffers[27] = length 880, hash C2DE85D0 + buffers[28] = length 530, hash C98BC6A8 + buffers[29] = length 568, hash 4FE5C8EA + buffers[30] = length 0, hash 1