From 74d61bbffb834cdb50d003a8de66af0e094ef702 Mon Sep 17 00:00:00 2001 From: rohks Date: Wed, 22 Jun 2022 17:19:55 +0100 Subject: [PATCH] Add timestamp to `CueGroup` `TextRenderer` is updated to output `CueGroup`, which contains the presentation time of the cues, in microseconds. PiperOrigin-RevId: 456531399 --- .../java/androidx/media3/cast/CastPlayer.java | 4 +- .../androidx/media3/common/text/CueGroup.java | 21 +- .../media3/common/text/CueGroupTest.java | 2 +- .../media3/exoplayer/ExoPlayerImpl.java | 6 +- .../media3/exoplayer/text/TextRenderer.java | 57 +- .../e2etest/PlaylistPlaybackTest.java | 47 ++ .../media3/session/MediaController.java | 6 +- .../session/MediaControllerImplLegacy.java | 6 +- .../androidx/media3/session/PlayerInfo.java | 8 +- .../playlists/playlist_with_subtitles.dump | 723 ++++++++++++++++++ .../assets/playbackdumps/webvtt/typical.dump | 5 + .../session/MediaControllerListenerTest.java | 10 +- .../session/MediaSessionProviderService.java | 4 +- .../androidx/media3/session/MockPlayer.java | 4 +- .../utils/robolectric/PlaybackOutput.java | 24 +- 15 files changed, 885 insertions(+), 42 deletions(-) create mode 100644 libraries/test_data/src/test/assets/playbackdumps/playlists/playlist_with_subtitles.dump diff --git a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java index b01ff7345f..4ec40f6846 100644 --- a/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java +++ b/libraries/cast/src/main/java/androidx/media3/cast/CastPlayer.java @@ -113,6 +113,8 @@ public final class CastPlayer extends BasePlayer { private static final long PROGRESS_REPORT_PERIOD_MS = 1000; private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0]; + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); private final CastContext castContext; private final MediaItemConverter mediaItemConverter; @@ -714,7 +716,7 @@ public final class CastPlayer extends BasePlayer { /** This method is not supported and returns an empty {@link CueGroup}. */ @Override public CueGroup getCurrentCues() { - return CueGroup.EMPTY; + return EMPTY_CUE_GROUP; } /** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */ diff --git a/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java b/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java index 9a26d0ec26..3d1f1bec20 100644 --- a/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java +++ b/libraries/common/src/main/java/androidx/media3/common/text/CueGroup.java @@ -22,6 +22,7 @@ import android.os.Bundle; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.Bundleable; +import androidx.media3.common.Timeline; import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.UnstableApi; import com.google.common.collect.ImmutableList; @@ -34,10 +35,6 @@ import java.util.List; /** Class to represent the state of active {@link Cue Cues} at a particular time. */ public final class CueGroup implements Bundleable { - - /** Empty {@link CueGroup}. */ - @UnstableApi public static final CueGroup EMPTY = new CueGroup(ImmutableList.of()); - /** * The cues in this group. * @@ -47,11 +44,18 @@ public final class CueGroup implements Bundleable { *

This list may be empty if the group represents a state with no cues. */ public final ImmutableList cues; + /** + * The presentation time of the {@link #cues}, in microseconds. + * + *

This time is an offset from the start of the current {@link Timeline.Period} + */ + @UnstableApi public final long presentationTimeUs; /** Creates a CueGroup. */ @UnstableApi - public CueGroup(List cues) { + public CueGroup(List cues, long presentationTimeUs) { this.cues = ImmutableList.copyOf(cues); + this.presentationTimeUs = presentationTimeUs; } // Bundleable implementation. @@ -59,10 +63,11 @@ public final class CueGroup implements Bundleable { @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) - @IntDef({FIELD_CUES}) + @IntDef({FIELD_CUES, FIELD_PRESENTATION_TIME_US}) private @interface FieldNumber {} private static final int FIELD_CUES = 0; + private static final int FIELD_PRESENTATION_TIME_US = 1; @UnstableApi @Override @@ -70,6 +75,7 @@ public final class CueGroup implements Bundleable { Bundle bundle = new Bundle(); bundle.putParcelableArrayList( keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues))); + bundle.putLong(keyForField(FIELD_PRESENTATION_TIME_US), presentationTimeUs); return bundle; } @@ -81,7 +87,8 @@ public final class CueGroup implements Bundleable { cueBundles == null ? ImmutableList.of() : BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles); - return new CueGroup(cues); + long presentationTimeUs = bundle.getLong(keyForField(FIELD_PRESENTATION_TIME_US)); + return new CueGroup(cues, presentationTimeUs); } private static String keyForField(@FieldNumber int field) { diff --git a/libraries/common/src/test/java/androidx/media3/common/text/CueGroupTest.java b/libraries/common/src/test/java/androidx/media3/common/text/CueGroupTest.java index 6a8f4d6e9c..03d898f3fa 100644 --- a/libraries/common/src/test/java/androidx/media3/common/text/CueGroupTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/text/CueGroupTest.java @@ -37,7 +37,7 @@ public class CueGroupTest { Cue bitmapCue = new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build(); ImmutableList cues = ImmutableList.of(textCue, bitmapCue); - CueGroup cueGroup = new CueGroup(cues); + CueGroup cueGroup = new CueGroup(cues, /* presentationTimeUs= */ 1_230_000); Parcel parcel = Parcel.obtain(); try { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index 389112484a..a8587da565 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -355,7 +355,7 @@ import java.util.concurrent.TimeoutException; } else { audioSessionId = Util.generateAudioSessionIdV21(applicationContext); } - currentCueGroup = CueGroup.EMPTY; + currentCueGroup = new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); throwsWhenUsingWrongThread = true; addListener(analyticsCollector); @@ -939,7 +939,7 @@ import java.util.concurrent.TimeoutException; verifyApplicationThread(); audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE); stopInternal(reset, /* error= */ null); - currentCueGroup = CueGroup.EMPTY; + currentCueGroup = new CueGroup(ImmutableList.of(), playbackInfo.positionUs); } @Override @@ -993,7 +993,7 @@ import java.util.concurrent.TimeoutException; checkNotNull(priorityTaskManager).remove(C.PRIORITY_PLAYBACK); isPriorityTaskManagerRegistered = false; } - currentCueGroup = CueGroup.EMPTY; + currentCueGroup = new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); playerReleased = true; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java index 506a69a842..2ddbd5908b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java @@ -42,12 +42,13 @@ import androidx.media3.extractor.text.SubtitleDecoder; import androidx.media3.extractor.text.SubtitleDecoderException; import androidx.media3.extractor.text.SubtitleInputBuffer; import androidx.media3.extractor.text.SubtitleOutputBuffer; +import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Collections; -import java.util.List; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; +import org.checkerframework.dataflow.qual.SideEffectFree; /** * A renderer for text. @@ -103,6 +104,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Nullable private SubtitleOutputBuffer nextSubtitle; private int nextSubtitleEventIndex; private long finalStreamEndPositionUs; + private long outputStreamOffsetUs; + private long lastRendererPositionUs; /** * @param output The output. @@ -134,6 +137,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { this.decoderFactory = decoderFactory; formatHolder = new FormatHolder(); finalStreamEndPositionUs = C.TIME_UNSET; + outputStreamOffsetUs = C.TIME_UNSET; + lastRendererPositionUs = C.TIME_UNSET; } @Override @@ -170,6 +175,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { + outputStreamOffsetUs = offsetUs; streamFormat = formats[0]; if (decoder != null) { decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; @@ -180,6 +186,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override protected void onPositionReset(long positionUs, boolean joining) { + lastRendererPositionUs = positionUs; clearOutput(); inputStreamEnded = false; outputStreamEnded = false; @@ -194,6 +201,7 @@ public final class TextRenderer extends BaseRenderer implements Callback { @Override public void render(long positionUs, long elapsedRealtimeUs) { + lastRendererPositionUs = positionUs; if (isCurrentStreamFinal() && finalStreamEndPositionUs != C.TIME_UNSET && positionUs >= finalStreamEndPositionUs) { @@ -257,7 +265,9 @@ public final class TextRenderer extends BaseRenderer implements Callback { // If textRendererNeedsUpdate then subtitle must be non-null. checkNotNull(subtitle); // textRendererNeedsUpdate is set and we're playing. Update the renderer. - updateOutput(subtitle.getCues(positionUs)); + long presentationTimeUs = getPresentationTimeUs(getCurrentEventTimeUs(positionUs)); + CueGroup cueGroup = new CueGroup(subtitle.getCues(positionUs), presentationTimeUs); + updateOutput(cueGroup); } if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { @@ -315,6 +325,8 @@ public final class TextRenderer extends BaseRenderer implements Callback { streamFormat = null; finalStreamEndPositionUs = C.TIME_UNSET; clearOutput(); + outputStreamOffsetUs = C.TIME_UNSET; + lastRendererPositionUs = C.TIME_UNSET; releaseDecoder(); } @@ -370,33 +382,33 @@ public final class TextRenderer extends BaseRenderer implements Callback { : subtitle.getEventTime(nextSubtitleEventIndex); } - private void updateOutput(List cues) { + private void updateOutput(CueGroup cueGroup) { if (outputHandler != null) { - outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); + outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cueGroup).sendToTarget(); } else { - invokeUpdateOutputInternal(cues); + invokeUpdateOutputInternal(cueGroup); } } private void clearOutput() { - updateOutput(Collections.emptyList()); + updateOutput(new CueGroup(ImmutableList.of(), getPresentationTimeUs(lastRendererPositionUs))); } - @SuppressWarnings("unchecked") @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_OUTPUT: - invokeUpdateOutputInternal((List) msg.obj); + invokeUpdateOutputInternal((CueGroup) msg.obj); return true; default: throw new IllegalStateException(); } } - private void invokeUpdateOutputInternal(List cues) { - output.onCues(cues); - output.onCues(new CueGroup(cues)); + @SuppressWarnings("deprecation") // We need to call both onCues method for backward compatibility. + private void invokeUpdateOutputInternal(CueGroup cueGroup) { + output.onCues(cueGroup.cues); + output.onCues(cueGroup); } /** @@ -410,4 +422,25 @@ public final class TextRenderer extends BaseRenderer implements Callback { clearOutput(); replaceDecoder(); } + + @RequiresNonNull("subtitle") + @SideEffectFree + private long getCurrentEventTimeUs(long positionUs) { + int nextEventTimeIndex = subtitle.getNextEventTimeIndex(positionUs); + if (nextEventTimeIndex == 0) { + return subtitle.timeUs; + } + + return nextEventTimeIndex == C.INDEX_UNSET + ? subtitle.getEventTime(subtitle.getEventTimeCount() - 1) + : subtitle.getEventTime(nextEventTimeIndex - 1); + } + + @SideEffectFree + private long getPresentationTimeUs(long positionUs) { + checkState(positionUs != C.TIME_UNSET); + checkState(outputStreamOffsetUs != C.TIME_UNSET); + + return positionUs - outputStreamOffsetUs; + } } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java index 1987a556dc..d4de66ad9e 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/PlaylistPlaybackTest.java @@ -16,9 +16,16 @@ package androidx.media3.exoplayer.e2etest; import android.content.Context; +import android.graphics.SurfaceTexture; +import android.net.Uri; +import android.view.Surface; +import androidx.media3.common.C; import androidx.media3.common.MediaItem; +import androidx.media3.common.MimeTypes; import androidx.media3.common.Player; import androidx.media3.exoplayer.ExoPlayer; +import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; +import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.test.utils.CapturingRenderersFactory; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.FakeClock; @@ -27,6 +34,7 @@ import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig; import androidx.media3.test.utils.robolectric.TestPlayerRunHelper; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableList; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,4 +90,43 @@ public final class PlaylistPlaybackTest { DumpFileAsserts.assertOutput( applicationContext, playbackOutput, "playbackdumps/playlists/bypass-off-then-on.dump"); } + + @Test + public void test_subtitle() throws Exception { + Context applicationContext = ApplicationProvider.getApplicationContext(); + CapturingRenderersFactory capturingRenderersFactory = + new CapturingRenderersFactory(applicationContext); + MediaSource.Factory mediaSourceFactory = + new DefaultMediaSourceFactory(applicationContext) + .experimentalUseProgressiveMediaSourceForSubtitles(true); + ExoPlayer player = + new ExoPlayer.Builder(applicationContext, capturingRenderersFactory) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + .setMediaSourceFactory(mediaSourceFactory) + .build(); + player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1))); + PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory); + + player.addMediaItem(MediaItem.fromUri("asset:///media/mp4/preroll-5s.mp4")); + MediaItem mediaItemWithSubtitle = + new MediaItem.Builder() + .setUri("asset:///media/mp4/preroll-5s.mp4") + .setSubtitleConfigurations( + ImmutableList.of( + new MediaItem.SubtitleConfiguration.Builder( + Uri.parse("asset:///media/webvtt/typical")) + .setMimeType(MimeTypes.TEXT_VTT) + .setLanguage("en") + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) + .build())) + .build(); + player.addMediaItem(mediaItemWithSubtitle); + player.prepare(); + player.play(); + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + player.release(); + + DumpFileAsserts.assertOutput( + applicationContext, playbackOutput, "playbackdumps/playlists/playlist_with_subtitles.dump"); + } } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaController.java b/libraries/session/src/main/java/androidx/media3/session/MediaController.java index e47fc02253..a3a140c772 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaController.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaController.java @@ -56,6 +56,7 @@ import androidx.media3.common.util.Consumer; import androidx.media3.common.util.Log; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; @@ -164,6 +165,9 @@ public class MediaController implements Player { "MediaController method is called from a wrong thread." + " See javadoc of MediaController for details."; + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); + /** A builder for {@link MediaController}. */ public static final class Builder { @@ -1576,7 +1580,7 @@ public class MediaController implements Player { @Override public CueGroup getCurrentCues() { verifyApplicationThread(); - return isConnected() ? impl.getCurrentCues() : CueGroup.EMPTY; + return isConnected() ? impl.getCurrentCues() : EMPTY_CUE_GROUP; } @Override diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java index 6a2dc56c1f..04006c7e53 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaControllerImplLegacy.java @@ -105,6 +105,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; private static final long AGGREGATES_CALLBACKS_WITHIN_TIMEOUT_MS = 500L; private static final int VOLUME_FLAGS = AudioManager.FLAG_SHOW_UI; + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); /* package */ final Context context; /* package */ final MediaController instance; @@ -1016,7 +1018,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Override public CueGroup getCurrentCues() { Log.w(TAG, "Session doesn't support getting Cue"); - return CueGroup.EMPTY; + return EMPTY_CUE_GROUP; } @Override @@ -2086,7 +2088,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; /* playlistMetadata= */ playlistMetadata, /* volume= */ 1.0f, /* audioAttributes= */ audioAttributes, - /* cueGroup= */ CueGroup.EMPTY, + /* cueGroup= */ EMPTY_CUE_GROUP, /* deviceInfo= */ deviceInfo, /* deviceVolume= */ deviceVolume, /* deviceMuted= */ deviceMuted, diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java b/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java index f6bd408b7a..6b886bb568 100644 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java +++ b/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java @@ -45,6 +45,7 @@ import androidx.media3.common.TrackSelectionParameters; import androidx.media3.common.VideoSize; import androidx.media3.common.text.CueGroup; import androidx.media3.common.util.Assertions; +import com.google.common.collect.ImmutableList; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -56,6 +57,9 @@ import java.lang.annotation.Target; */ /* package */ class PlayerInfo implements Bundleable { + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); + public static class Builder { @Nullable private PlaybackException playerError; @@ -332,7 +336,7 @@ import java.lang.annotation.Target; MediaMetadata.EMPTY, /* volume= */ 1f, AudioAttributes.DEFAULT, - /* cueGroup = */ CueGroup.EMPTY, + /* cueGroup = */ EMPTY_CUE_GROUP, DeviceInfo.UNKNOWN, /* deviceVolume= */ 0, /* deviceMuted= */ false, @@ -846,7 +850,7 @@ import java.lang.annotation.Target; : AudioAttributes.CREATOR.fromBundle(audioAttributesBundle); @Nullable Bundle cueGroupBundle = bundle.getBundle(keyForField(FIELD_CUE_GROUP)); CueGroup cueGroup = - cueGroupBundle == null ? CueGroup.EMPTY : CueGroup.CREATOR.fromBundle(cueGroupBundle); + cueGroupBundle == null ? EMPTY_CUE_GROUP : CueGroup.CREATOR.fromBundle(cueGroupBundle); @Nullable Bundle deviceInfoBundle = bundle.getBundle(keyForField(FIELD_DEVICE_INFO)); DeviceInfo deviceInfo = deviceInfoBundle == null diff --git a/libraries/test_data/src/test/assets/playbackdumps/playlists/playlist_with_subtitles.dump b/libraries/test_data/src/test/assets/playbackdumps/playlists/playlist_with_subtitles.dump new file mode 100644 index 0000000000..718fd4bd43 --- /dev/null +++ b/libraries/test_data/src/test/assets/playbackdumps/playlists/playlist_with_subtitles.dump @@ -0,0 +1,723 @@ +MediaCodecAdapter (exotest.audio.aac): + buffers.length = 436 + buffers[0] = length 21, hash D57A2CCC + buffers[1] = length 4, hash EE9DF + buffers[2] = length 4, hash EE9DF + buffers[3] = length 4, hash EE9DF + buffers[4] = length 4, hash EE9DF + buffers[5] = length 4, hash EE9DF + buffers[6] = length 4, hash EE9DF + buffers[7] = length 4, hash EE9DF + buffers[8] = length 4, hash EE9DF + buffers[9] = length 4, hash EE9DF + buffers[10] = length 4, hash EE9DF + buffers[11] = length 4, hash EE9DF + buffers[12] = length 4, hash EE9DF + buffers[13] = length 4, hash EE9DF + buffers[14] = length 4, hash EE9DF + buffers[15] = length 4, hash EE9DF + buffers[16] = length 4, hash EE9DF + buffers[17] = length 4, hash EE9DF + buffers[18] = length 4, hash EE9DF + buffers[19] = length 4, hash EE9DF + buffers[20] = length 4, hash EE9DF + buffers[21] = length 4, hash EE9DF + buffers[22] = length 4, hash EE9DF + buffers[23] = length 4, hash EE9DF + buffers[24] = length 4, hash EE9DF + buffers[25] = length 4, hash EE9DF + buffers[26] = length 4, hash EE9DF + buffers[27] = length 4, hash EE9DF + buffers[28] = length 4, hash EE9DF + buffers[29] = length 4, hash EE9DF + buffers[30] = length 4, hash EE9DF + buffers[31] = length 4, hash EE9DF + buffers[32] = length 4, hash EE9DF + buffers[33] = length 4, hash EE9DF + buffers[34] = length 4, hash EE9DF + buffers[35] = length 4, hash EE9DF + buffers[36] = length 4, hash EE9DF + buffers[37] = length 4, hash EE9DF + buffers[38] = length 4, hash EE9DF + buffers[39] = length 4, hash EE9DF + buffers[40] = length 4, hash EE9DF + buffers[41] = length 4, hash EE9DF + buffers[42] = length 4, hash EE9DF + buffers[43] = length 4, hash EE9DF + buffers[44] = length 4, hash EE9DF + buffers[45] = length 4, hash EE9DF + buffers[46] = length 4, hash EE9DF + buffers[47] = length 4, hash EE9DF + buffers[48] = length 4, hash EE9DF + buffers[49] = length 4, hash EE9DF + buffers[50] = length 4, hash EE9DF + buffers[51] = length 4, hash EE9DF + buffers[52] = length 4, hash EE9DF + buffers[53] = length 4, hash EE9DF + buffers[54] = length 4, hash EE9DF + buffers[55] = length 4, hash EE9DF + buffers[56] = length 4, hash EE9DF + buffers[57] = length 4, hash EE9DF + buffers[58] = length 4, hash EE9DF + buffers[59] = length 4, hash EE9DF + buffers[60] = length 4, hash EE9DF + buffers[61] = length 4, hash EE9DF + buffers[62] = length 4, hash EE9DF + buffers[63] = length 4, hash EE9DF + buffers[64] = length 4, hash EE9DF + buffers[65] = length 4, hash EE9DF + buffers[66] = length 4, hash EE9DF + buffers[67] = length 4, hash EE9DF + buffers[68] = length 4, hash EE9DF + buffers[69] = length 4, hash EE9DF + buffers[70] = length 4, hash EE9DF + buffers[71] = length 4, hash EE9DF + buffers[72] = length 4, hash EE9DF + buffers[73] = length 4, hash EE9DF + buffers[74] = length 4, hash EE9DF + buffers[75] = length 4, hash EE9DF + buffers[76] = length 4, hash EE9DF + buffers[77] = length 4, hash EE9DF + buffers[78] = length 4, hash EE9DF + buffers[79] = length 4, hash EE9DF + buffers[80] = length 4, hash EE9DF + buffers[81] = length 4, hash EE9DF + buffers[82] = length 4, hash EE9DF + buffers[83] = length 4, hash EE9DF + buffers[84] = length 4, hash EE9DF + buffers[85] = length 4, hash EE9DF + buffers[86] = length 4, hash EE9DF + buffers[87] = length 4, hash EE9DF + buffers[88] = length 4, hash EE9DF + buffers[89] = length 4, hash EE9DF + buffers[90] = length 4, hash EE9DF + buffers[91] = length 4, hash EE9DF + buffers[92] = length 4, hash EE9DF + buffers[93] = length 4, hash EE9DF + buffers[94] = length 4, hash EE9DF + buffers[95] = length 4, hash EE9DF + buffers[96] = length 4, hash EE9DF + buffers[97] = length 4, hash EE9DF + buffers[98] = length 4, hash EE9DF + buffers[99] = length 4, hash EE9DF + buffers[100] = length 4, hash EE9DF + buffers[101] = length 4, hash EE9DF + buffers[102] = length 4, hash EE9DF + buffers[103] = length 4, hash EE9DF + buffers[104] = length 4, hash EE9DF + buffers[105] = length 4, hash EE9DF + buffers[106] = length 4, hash EE9DF + buffers[107] = length 4, hash EE9DF + buffers[108] = length 4, hash EE9DF + buffers[109] = length 4, hash EE9DF + buffers[110] = length 4, hash EE9DF + buffers[111] = length 4, hash EE9DF + buffers[112] = length 4, hash EE9DF + buffers[113] = length 4, hash EE9DF + buffers[114] = length 4, hash EE9DF + buffers[115] = length 4, hash EE9DF + buffers[116] = length 4, hash EE9DF + buffers[117] = length 4, hash EE9DF + buffers[118] = length 4, hash EE9DF + buffers[119] = length 4, hash EE9DF + buffers[120] = length 4, hash EE9DF + buffers[121] = length 4, hash EE9DF + buffers[122] = length 4, hash EE9DF + buffers[123] = length 4, hash EE9DF + buffers[124] = length 4, hash EE9DF + buffers[125] = length 4, hash EE9DF + buffers[126] = length 4, hash EE9DF + buffers[127] = length 4, hash EE9DF + buffers[128] = length 4, hash EE9DF + buffers[129] = length 4, hash EE9DF + buffers[130] = length 4, hash EE9DF + buffers[131] = length 4, hash EE9DF + buffers[132] = length 4, hash EE9DF + buffers[133] = length 4, hash EE9DF + buffers[134] = length 4, hash EE9DF + buffers[135] = length 4, hash EE9DF + buffers[136] = length 4, hash EE9DF + buffers[137] = length 4, hash EE9DF + buffers[138] = length 4, hash EE9DF + buffers[139] = length 4, hash EE9DF + buffers[140] = length 4, hash EE9DF + buffers[141] = length 4, hash EE9DF + buffers[142] = length 4, hash EE9DF + buffers[143] = length 4, hash EE9DF + buffers[144] = length 4, hash EE9DF + buffers[145] = length 4, hash EE9DF + buffers[146] = length 4, hash EE9DF + buffers[147] = length 4, hash EE9DF + buffers[148] = length 4, hash EE9DF + buffers[149] = length 4, hash EE9DF + buffers[150] = length 4, hash EE9DF + buffers[151] = length 4, hash EE9DF + buffers[152] = length 4, hash EE9DF + buffers[153] = length 4, hash EE9DF + buffers[154] = length 4, hash EE9DF + buffers[155] = length 4, hash EE9DF + buffers[156] = length 4, hash EE9DF + buffers[157] = length 4, hash EE9DF + buffers[158] = length 4, hash EE9DF + buffers[159] = length 4, hash EE9DF + buffers[160] = length 4, hash EE9DF + buffers[161] = length 4, hash EE9DF + buffers[162] = length 4, hash EE9DF + buffers[163] = length 4, hash EE9DF + buffers[164] = length 4, hash EE9DF + buffers[165] = length 4, hash EE9DF + buffers[166] = length 4, hash EE9DF + buffers[167] = length 4, hash EE9DF + buffers[168] = length 4, hash EE9DF + buffers[169] = length 4, hash EE9DF + buffers[170] = length 4, hash EE9DF + buffers[171] = length 4, hash EE9DF + buffers[172] = length 4, hash EE9DF + buffers[173] = length 4, hash EE9DF + buffers[174] = length 4, hash EE9DF + buffers[175] = length 4, hash EE9DF + buffers[176] = length 4, hash EE9DF + buffers[177] = length 4, hash EE9DF + buffers[178] = length 4, hash EE9DF + buffers[179] = length 4, hash EE9DF + buffers[180] = length 4, hash EE9DF + buffers[181] = length 4, hash EE9DF + buffers[182] = length 4, hash EE9DF + buffers[183] = length 4, hash EE9DF + buffers[184] = length 4, hash EE9DF + buffers[185] = length 4, hash EE9DF + buffers[186] = length 4, hash EE9DF + buffers[187] = length 4, hash EE9DF + buffers[188] = length 4, hash EE9DF + buffers[189] = length 4, hash EE9DF + buffers[190] = length 4, hash EE9DF + buffers[191] = length 4, hash EE9DF + buffers[192] = length 4, hash EE9DF + buffers[193] = length 4, hash EE9DF + buffers[194] = length 4, hash EE9DF + buffers[195] = length 4, hash EE9DF + buffers[196] = length 4, hash EE9DF + buffers[197] = length 4, hash EE9DF + buffers[198] = length 4, hash EE9DF + buffers[199] = length 4, hash EE9DF + buffers[200] = length 4, hash EE9DF + buffers[201] = length 4, hash EE9DF + buffers[202] = length 4, hash EE9DF + buffers[203] = length 4, hash EE9DF + buffers[204] = length 4, hash EE9DF + buffers[205] = length 4, hash EE9DF + buffers[206] = length 4, hash EE9DF + buffers[207] = length 4, hash EE9DF + buffers[208] = length 4, hash EE9DF + buffers[209] = length 4, hash EE9DF + buffers[210] = length 4, hash EE9DF + buffers[211] = length 4, hash EE9DF + buffers[212] = length 4, hash EE9DF + buffers[213] = length 4, hash EE9DF + buffers[214] = length 4, hash EE9DF + buffers[215] = length 4, hash EE9DF + buffers[216] = length 4, hash EE9DF + buffers[217] = length 0, hash 1 + buffers[218] = length 21, hash D57A2CCC + buffers[219] = length 4, hash EE9DF + buffers[220] = length 4, hash EE9DF + buffers[221] = length 4, hash EE9DF + buffers[222] = length 4, hash EE9DF + buffers[223] = length 4, hash EE9DF + buffers[224] = length 4, hash EE9DF + buffers[225] = length 4, hash EE9DF + buffers[226] = length 4, hash EE9DF + buffers[227] = length 4, hash EE9DF + buffers[228] = length 4, hash EE9DF + buffers[229] = length 4, hash EE9DF + buffers[230] = length 4, hash EE9DF + buffers[231] = length 4, hash EE9DF + buffers[232] = length 4, hash EE9DF + buffers[233] = length 4, hash EE9DF + buffers[234] = length 4, hash EE9DF + buffers[235] = length 4, hash EE9DF + buffers[236] = length 4, hash EE9DF + buffers[237] = length 4, hash EE9DF + buffers[238] = length 4, hash EE9DF + buffers[239] = length 4, hash EE9DF + buffers[240] = length 4, hash EE9DF + buffers[241] = length 4, hash EE9DF + buffers[242] = length 4, hash EE9DF + buffers[243] = length 4, hash EE9DF + buffers[244] = length 4, hash EE9DF + buffers[245] = length 4, hash EE9DF + buffers[246] = length 4, hash EE9DF + buffers[247] = length 4, hash EE9DF + buffers[248] = length 4, hash EE9DF + buffers[249] = length 4, hash EE9DF + buffers[250] = length 4, hash EE9DF + buffers[251] = length 4, hash EE9DF + buffers[252] = length 4, hash EE9DF + buffers[253] = length 4, hash EE9DF + buffers[254] = length 4, hash EE9DF + buffers[255] = length 4, hash EE9DF + buffers[256] = length 4, hash EE9DF + buffers[257] = length 4, hash EE9DF + buffers[258] = length 4, hash EE9DF + buffers[259] = length 4, hash EE9DF + buffers[260] = length 4, hash EE9DF + buffers[261] = length 4, hash EE9DF + buffers[262] = length 4, hash EE9DF + buffers[263] = length 4, hash EE9DF + buffers[264] = length 4, hash EE9DF + buffers[265] = length 4, hash EE9DF + buffers[266] = length 4, hash EE9DF + buffers[267] = length 4, hash EE9DF + buffers[268] = length 4, hash EE9DF + buffers[269] = length 4, hash EE9DF + buffers[270] = length 4, hash EE9DF + buffers[271] = length 4, hash EE9DF + buffers[272] = length 4, hash EE9DF + buffers[273] = length 4, hash EE9DF + buffers[274] = length 4, hash EE9DF + buffers[275] = length 4, hash EE9DF + buffers[276] = length 4, hash EE9DF + buffers[277] = length 4, hash EE9DF + buffers[278] = length 4, hash EE9DF + buffers[279] = length 4, hash EE9DF + buffers[280] = length 4, hash EE9DF + buffers[281] = length 4, hash EE9DF + buffers[282] = length 4, hash EE9DF + buffers[283] = length 4, hash EE9DF + buffers[284] = length 4, hash EE9DF + buffers[285] = length 4, hash EE9DF + buffers[286] = length 4, hash EE9DF + buffers[287] = length 4, hash EE9DF + buffers[288] = length 4, hash EE9DF + buffers[289] = length 4, hash EE9DF + buffers[290] = length 4, hash EE9DF + buffers[291] = length 4, hash EE9DF + buffers[292] = length 4, hash EE9DF + buffers[293] = length 4, hash EE9DF + buffers[294] = length 4, hash EE9DF + buffers[295] = length 4, hash EE9DF + buffers[296] = length 4, hash EE9DF + buffers[297] = length 4, hash EE9DF + buffers[298] = length 4, hash EE9DF + buffers[299] = length 4, hash EE9DF + buffers[300] = length 4, hash EE9DF + buffers[301] = length 4, hash EE9DF + buffers[302] = length 4, hash EE9DF + buffers[303] = length 4, hash EE9DF + buffers[304] = length 4, hash EE9DF + buffers[305] = length 4, hash EE9DF + buffers[306] = length 4, hash EE9DF + buffers[307] = length 4, hash EE9DF + buffers[308] = length 4, hash EE9DF + buffers[309] = length 4, hash EE9DF + buffers[310] = length 4, hash EE9DF + buffers[311] = length 4, hash EE9DF + buffers[312] = length 4, hash EE9DF + buffers[313] = length 4, hash EE9DF + buffers[314] = length 4, hash EE9DF + buffers[315] = length 4, hash EE9DF + buffers[316] = length 4, hash EE9DF + buffers[317] = length 4, hash EE9DF + buffers[318] = length 4, hash EE9DF + buffers[319] = length 4, hash EE9DF + buffers[320] = length 4, hash EE9DF + buffers[321] = length 4, hash EE9DF + buffers[322] = length 4, hash EE9DF + buffers[323] = length 4, hash EE9DF + buffers[324] = length 4, hash EE9DF + buffers[325] = length 4, hash EE9DF + buffers[326] = length 4, hash EE9DF + buffers[327] = length 4, hash EE9DF + buffers[328] = length 4, hash EE9DF + buffers[329] = length 4, hash EE9DF + buffers[330] = length 4, hash EE9DF + buffers[331] = length 4, hash EE9DF + buffers[332] = length 4, hash EE9DF + buffers[333] = length 4, hash EE9DF + buffers[334] = length 4, hash EE9DF + buffers[335] = length 4, hash EE9DF + buffers[336] = length 4, hash EE9DF + buffers[337] = length 4, hash EE9DF + buffers[338] = length 4, hash EE9DF + buffers[339] = length 4, hash EE9DF + buffers[340] = length 4, hash EE9DF + buffers[341] = length 4, hash EE9DF + buffers[342] = length 4, hash EE9DF + buffers[343] = length 4, hash EE9DF + buffers[344] = length 4, hash EE9DF + buffers[345] = length 4, hash EE9DF + buffers[346] = length 4, hash EE9DF + buffers[347] = length 4, hash EE9DF + buffers[348] = length 4, hash EE9DF + buffers[349] = length 4, hash EE9DF + buffers[350] = length 4, hash EE9DF + buffers[351] = length 4, hash EE9DF + buffers[352] = length 4, hash EE9DF + buffers[353] = length 4, hash EE9DF + buffers[354] = length 4, hash EE9DF + buffers[355] = length 4, hash EE9DF + buffers[356] = length 4, hash EE9DF + buffers[357] = length 4, hash EE9DF + buffers[358] = length 4, hash EE9DF + buffers[359] = length 4, hash EE9DF + buffers[360] = length 4, hash EE9DF + buffers[361] = length 4, hash EE9DF + buffers[362] = length 4, hash EE9DF + buffers[363] = length 4, hash EE9DF + buffers[364] = length 4, hash EE9DF + buffers[365] = length 4, hash EE9DF + buffers[366] = length 4, hash EE9DF + buffers[367] = length 4, hash EE9DF + buffers[368] = length 4, hash EE9DF + buffers[369] = length 4, hash EE9DF + buffers[370] = length 4, hash EE9DF + buffers[371] = length 4, hash EE9DF + buffers[372] = length 4, hash EE9DF + buffers[373] = length 4, hash EE9DF + buffers[374] = length 4, hash EE9DF + buffers[375] = length 4, hash EE9DF + buffers[376] = length 4, hash EE9DF + buffers[377] = length 4, hash EE9DF + buffers[378] = length 4, hash EE9DF + buffers[379] = length 4, hash EE9DF + buffers[380] = length 4, hash EE9DF + buffers[381] = length 4, hash EE9DF + buffers[382] = length 4, hash EE9DF + buffers[383] = length 4, hash EE9DF + buffers[384] = length 4, hash EE9DF + buffers[385] = length 4, hash EE9DF + buffers[386] = length 4, hash EE9DF + buffers[387] = length 4, hash EE9DF + buffers[388] = length 4, hash EE9DF + buffers[389] = length 4, hash EE9DF + buffers[390] = length 4, hash EE9DF + buffers[391] = length 4, hash EE9DF + buffers[392] = length 4, hash EE9DF + buffers[393] = length 4, hash EE9DF + buffers[394] = length 4, hash EE9DF + buffers[395] = length 4, hash EE9DF + buffers[396] = length 4, hash EE9DF + buffers[397] = length 4, hash EE9DF + buffers[398] = length 4, hash EE9DF + buffers[399] = length 4, hash EE9DF + buffers[400] = length 4, hash EE9DF + buffers[401] = length 4, hash EE9DF + buffers[402] = length 4, hash EE9DF + buffers[403] = length 4, hash EE9DF + buffers[404] = length 4, hash EE9DF + buffers[405] = length 4, hash EE9DF + buffers[406] = length 4, hash EE9DF + buffers[407] = length 4, hash EE9DF + buffers[408] = length 4, hash EE9DF + buffers[409] = length 4, hash EE9DF + buffers[410] = length 4, hash EE9DF + buffers[411] = length 4, hash EE9DF + buffers[412] = length 4, hash EE9DF + buffers[413] = length 4, hash EE9DF + buffers[414] = length 4, hash EE9DF + buffers[415] = length 4, hash EE9DF + buffers[416] = length 4, hash EE9DF + buffers[417] = length 4, hash EE9DF + buffers[418] = length 4, hash EE9DF + buffers[419] = length 4, hash EE9DF + buffers[420] = length 4, hash EE9DF + buffers[421] = length 4, hash EE9DF + buffers[422] = length 4, hash EE9DF + buffers[423] = length 4, hash EE9DF + buffers[424] = length 4, hash EE9DF + buffers[425] = length 4, hash EE9DF + buffers[426] = length 4, hash EE9DF + buffers[427] = length 4, hash EE9DF + buffers[428] = length 4, hash EE9DF + buffers[429] = length 4, hash EE9DF + buffers[430] = length 4, hash EE9DF + buffers[431] = length 4, hash EE9DF + buffers[432] = length 4, hash EE9DF + buffers[433] = length 4, hash EE9DF + buffers[434] = length 4, hash EE9DF + buffers[435] = length 0, hash 1 +MediaCodecAdapter (exotest.video.avc): + buffers.length = 251 + buffers[0] = length 5245, hash C090A41E + buffers[1] = length 63, hash 5141C80D + buffers[2] = length 22, hash A32E59A1 + buffers[3] = length 20, hash A09DEAB8 + buffers[4] = length 18, hash B64DA059 + buffers[5] = length 28, hash FC8EF2BB + buffers[6] = length 22, hash BF8A4A9F + buffers[7] = length 18, hash D163DF61 + buffers[8] = length 18, hash FD82E95 + buffers[9] = length 28, hash 44A16E72 + buffers[10] = length 22, hash 31C06057 + buffers[11] = length 18, hash DC93CC9D + buffers[12] = length 18, hash 1B081BD1 + buffers[13] = length 28, hash 2700AF + buffers[14] = length 22, hash 6D292D94 + buffers[15] = length 18, hash D646C05A + buffers[16] = length 18, hash 14BB0F8E + buffers[17] = length 28, hash 5DE2C2B + buffers[18] = length 22, hash 57E81CD0 + buffers[19] = length 18, hash E176AD96 + buffers[20] = length 18, hash 1FEAFCCA + buffers[21] = length 28, hash C163BE68 + buffers[22] = length 22, hash B0C92D0B + buffers[23] = length 18, hash 3B013BD2 + buffers[24] = length 18, hash 79758B06 + buffers[25] = length 28, hash F72EB1A3 + buffers[26] = length 22, hash 9B881C48 + buffers[27] = length 18, hash 4631290E + buffers[28] = length 18, hash 84A57842 + buffers[29] = length 28, hash E1FCF000 + buffers[30] = length 22, hash 359D2D82 + buffers[31] = length 18, hash 62DE0FC9 + buffers[32] = length 18, hash A1525EFD + buffers[33] = length 28, hash 5350E8FA + buffers[34] = length 22, hash EE2060DF + buffers[35] = length 18, hash 77D95125 + buffers[36] = length 18, hash B64DA059 + buffers[37] = length 28, hash ED67B37 + buffers[38] = length 22, hash 4701711B + buffers[39] = length 18, hash D163DF61 + buffers[40] = length 18, hash FD82E95 + buffers[41] = length 28, hash 44A16E72 + buffers[42] = length 22, hash 31C06057 + buffers[43] = length 18, hash DC93CC9D + buffers[44] = length 18, hash 1B081BD1 + buffers[45] = length 28, hash 2700AF + buffers[46] = length 22, hash 6D292D94 + buffers[47] = length 18, hash D646C05A + buffers[48] = length 18, hash 14BB0F8E + buffers[49] = length 28, hash 5DE2C2B + buffers[50] = length 22, hash 57E81CD0 + buffers[51] = length 18, hash E176AD96 + buffers[52] = length 18, hash 1FEAFCCA + buffers[53] = length 28, hash C163BE68 + buffers[54] = length 22, hash B0C92D0B + buffers[55] = length 18, hash 3B013BD2 + buffers[56] = length 18, hash 79758B06 + buffers[57] = length 28, hash F72EB1A3 + buffers[58] = length 22, hash 9B881C48 + buffers[59] = length 18, hash 4631290E + buffers[60] = length 18, hash 84A57842 + buffers[61] = length 28, hash E1FCF000 + buffers[62] = length 22, hash 359D2D82 + buffers[63] = length 18, hash 62DE0FC9 + buffers[64] = length 18, hash A1525EFD + buffers[65] = length 28, hash 5350E8FA + buffers[66] = length 22, hash EE2060DF + buffers[67] = length 18, hash 77D95125 + buffers[68] = length 18, hash B64DA059 + buffers[69] = length 28, hash ED67B37 + buffers[70] = length 22, hash 4701711B + buffers[71] = length 18, hash D163DF61 + buffers[72] = length 18, hash FD82E95 + buffers[73] = length 28, hash 44A16E72 + buffers[74] = length 22, hash 31C06057 + buffers[75] = length 18, hash DC93CC9D + buffers[76] = length 18, hash 1B081BD1 + buffers[77] = length 28, hash 2700AF + buffers[78] = length 22, hash 6D292D94 + buffers[79] = length 18, hash D646C05A + buffers[80] = length 18, hash 14BB0F8E + buffers[81] = length 28, hash 5DE2C2B + buffers[82] = length 22, hash 57E81CD0 + buffers[83] = length 18, hash E176AD96 + buffers[84] = length 18, hash 1FEAFCCA + buffers[85] = length 28, hash C163BE68 + buffers[86] = length 22, hash B0C92D0B + buffers[87] = length 18, hash 3B013BD2 + buffers[88] = length 18, hash 79758B06 + buffers[89] = length 28, hash F72EB1A3 + buffers[90] = length 22, hash 9B881C48 + buffers[91] = length 18, hash 4631290E + buffers[92] = length 18, hash 84A57842 + buffers[93] = length 33, hash AF5CF49E + buffers[94] = length 22, hash 359D2D82 + buffers[95] = length 18, hash 62DE0FC9 + buffers[96] = length 18, hash A1525EFD + buffers[97] = length 33, hash F4C6DE46 + buffers[98] = length 22, hash EE2060DF + buffers[99] = length 18, hash 77D95125 + buffers[100] = length 18, hash B64DA059 + buffers[101] = length 28, hash ED67B37 + buffers[102] = length 22, hash 4701711B + buffers[103] = length 18, hash D163DF61 + buffers[104] = length 18, hash FD82E95 + buffers[105] = length 28, hash 44A16E72 + buffers[106] = length 22, hash 31C06057 + buffers[107] = length 18, hash DC93CC9D + buffers[108] = length 18, hash 1B081BD1 + buffers[109] = length 28, hash 2700AF + buffers[110] = length 22, hash 6D292D94 + buffers[111] = length 18, hash D646C05A + buffers[112] = length 18, hash 14BB0F8E + buffers[113] = length 27, hash 5292D9E + buffers[114] = length 22, hash 57E81CD0 + buffers[115] = length 18, hash E176AD96 + buffers[116] = length 18, hash 1FEAFCCA + buffers[117] = length 26, hash B0CAA4C9 + buffers[118] = length 22, hash B0C92D0B + buffers[119] = length 18, hash 3B013BD2 + buffers[120] = length 18, hash 79758B06 + buffers[121] = length 26, hash C63A1445 + buffers[122] = length 22, hash 9B881C48 + buffers[123] = length 18, hash 4631290E + buffers[124] = length 18, hash 84A57842 + buffers[125] = length 5245, hash C090A41E + buffers[126] = length 63, hash 5141C80D + buffers[127] = length 22, hash A32E59A1 + buffers[128] = length 20, hash A09DEAB8 + buffers[129] = length 18, hash B64DA059 + buffers[130] = length 28, hash FC8EF2BB + buffers[131] = length 22, hash BF8A4A9F + buffers[132] = length 18, hash D163DF61 + buffers[133] = length 18, hash FD82E95 + buffers[134] = length 28, hash 44A16E72 + buffers[135] = length 22, hash 31C06057 + buffers[136] = length 18, hash DC93CC9D + buffers[137] = length 18, hash 1B081BD1 + buffers[138] = length 28, hash 2700AF + buffers[139] = length 22, hash 6D292D94 + buffers[140] = length 18, hash D646C05A + buffers[141] = length 18, hash 14BB0F8E + buffers[142] = length 28, hash 5DE2C2B + buffers[143] = length 22, hash 57E81CD0 + buffers[144] = length 18, hash E176AD96 + buffers[145] = length 18, hash 1FEAFCCA + buffers[146] = length 28, hash C163BE68 + buffers[147] = length 22, hash B0C92D0B + buffers[148] = length 18, hash 3B013BD2 + buffers[149] = length 18, hash 79758B06 + buffers[150] = length 28, hash F72EB1A3 + buffers[151] = length 22, hash 9B881C48 + buffers[152] = length 18, hash 4631290E + buffers[153] = length 18, hash 84A57842 + buffers[154] = length 28, hash E1FCF000 + buffers[155] = length 22, hash 359D2D82 + buffers[156] = length 18, hash 62DE0FC9 + buffers[157] = length 18, hash A1525EFD + buffers[158] = length 28, hash 5350E8FA + buffers[159] = length 22, hash EE2060DF + buffers[160] = length 18, hash 77D95125 + buffers[161] = length 18, hash B64DA059 + buffers[162] = length 28, hash ED67B37 + buffers[163] = length 22, hash 4701711B + buffers[164] = length 18, hash D163DF61 + buffers[165] = length 18, hash FD82E95 + buffers[166] = length 28, hash 44A16E72 + buffers[167] = length 22, hash 31C06057 + buffers[168] = length 18, hash DC93CC9D + buffers[169] = length 18, hash 1B081BD1 + buffers[170] = length 28, hash 2700AF + buffers[171] = length 22, hash 6D292D94 + buffers[172] = length 18, hash D646C05A + buffers[173] = length 18, hash 14BB0F8E + buffers[174] = length 28, hash 5DE2C2B + buffers[175] = length 22, hash 57E81CD0 + buffers[176] = length 18, hash E176AD96 + buffers[177] = length 18, hash 1FEAFCCA + buffers[178] = length 28, hash C163BE68 + buffers[179] = length 22, hash B0C92D0B + buffers[180] = length 18, hash 3B013BD2 + buffers[181] = length 18, hash 79758B06 + buffers[182] = length 28, hash F72EB1A3 + buffers[183] = length 22, hash 9B881C48 + buffers[184] = length 18, hash 4631290E + buffers[185] = length 18, hash 84A57842 + buffers[186] = length 28, hash E1FCF000 + buffers[187] = length 22, hash 359D2D82 + buffers[188] = length 18, hash 62DE0FC9 + buffers[189] = length 18, hash A1525EFD + buffers[190] = length 28, hash 5350E8FA + buffers[191] = length 22, hash EE2060DF + buffers[192] = length 18, hash 77D95125 + buffers[193] = length 18, hash B64DA059 + buffers[194] = length 28, hash ED67B37 + buffers[195] = length 22, hash 4701711B + buffers[196] = length 18, hash D163DF61 + buffers[197] = length 18, hash FD82E95 + buffers[198] = length 28, hash 44A16E72 + buffers[199] = length 22, hash 31C06057 + buffers[200] = length 18, hash DC93CC9D + buffers[201] = length 18, hash 1B081BD1 + buffers[202] = length 28, hash 2700AF + buffers[203] = length 22, hash 6D292D94 + buffers[204] = length 18, hash D646C05A + buffers[205] = length 18, hash 14BB0F8E + buffers[206] = length 28, hash 5DE2C2B + buffers[207] = length 22, hash 57E81CD0 + buffers[208] = length 18, hash E176AD96 + buffers[209] = length 18, hash 1FEAFCCA + buffers[210] = length 28, hash C163BE68 + buffers[211] = length 22, hash B0C92D0B + buffers[212] = length 18, hash 3B013BD2 + buffers[213] = length 18, hash 79758B06 + buffers[214] = length 28, hash F72EB1A3 + buffers[215] = length 22, hash 9B881C48 + buffers[216] = length 18, hash 4631290E + buffers[217] = length 18, hash 84A57842 + buffers[218] = length 33, hash AF5CF49E + buffers[219] = length 22, hash 359D2D82 + buffers[220] = length 18, hash 62DE0FC9 + buffers[221] = length 18, hash A1525EFD + buffers[222] = length 33, hash F4C6DE46 + buffers[223] = length 22, hash EE2060DF + buffers[224] = length 18, hash 77D95125 + buffers[225] = length 18, hash B64DA059 + buffers[226] = length 28, hash ED67B37 + buffers[227] = length 22, hash 4701711B + buffers[228] = length 18, hash D163DF61 + buffers[229] = length 18, hash FD82E95 + buffers[230] = length 28, hash 44A16E72 + buffers[231] = length 22, hash 31C06057 + buffers[232] = length 18, hash DC93CC9D + buffers[233] = length 18, hash 1B081BD1 + buffers[234] = length 28, hash 2700AF + buffers[235] = length 22, hash 6D292D94 + buffers[236] = length 18, hash D646C05A + buffers[237] = length 18, hash 14BB0F8E + buffers[238] = length 27, hash 5292D9E + buffers[239] = length 22, hash 57E81CD0 + buffers[240] = length 18, hash E176AD96 + buffers[241] = length 18, hash 1FEAFCCA + buffers[242] = length 26, hash B0CAA4C9 + buffers[243] = length 22, hash B0C92D0B + buffers[244] = length 18, hash 3B013BD2 + buffers[245] = length 18, hash 79758B06 + buffers[246] = length 26, hash C63A1445 + buffers[247] = length 22, hash 9B881C48 + buffers[248] = length 18, hash 4631290E + buffers[249] = length 18, hash 84A57842 + buffers[250] = length 0, hash 1 +TextOutput: + Subtitle[0]: + presentationTimeUs = 0 + Cues = [] + Subtitle[1]: + presentationTimeUs = 0 + Cue[0]: + text = This is the first subtitle. + textAlignment = ALIGN_CENTER + line = -1.0 + lineType = 1 + lineAnchor = 0 + position = 0.5 + positionAnchor = 1 + size = 1.0 + Subtitle[2]: + presentationTimeUs = 1234000 + Cues = [] + Subtitle[3]: + presentationTimeUs = 2345000 + Cue[0]: + text = This is the second subtitle. + textAlignment = ALIGN_CENTER + line = -1.0 + lineType = 1 + lineAnchor = 0 + position = 0.5 + positionAnchor = 1 + size = 1.0 + Subtitle[4]: + presentationTimeUs = 3456000 + Cues = [] diff --git a/libraries/test_data/src/test/assets/playbackdumps/webvtt/typical.dump b/libraries/test_data/src/test/assets/playbackdumps/webvtt/typical.dump index db4b4047c5..bc1e7b954a 100644 --- a/libraries/test_data/src/test/assets/playbackdumps/webvtt/typical.dump +++ b/libraries/test_data/src/test/assets/playbackdumps/webvtt/typical.dump @@ -348,8 +348,10 @@ MediaCodecAdapter (exotest.video.avc): buffers[125] = length 0, hash 1 TextOutput: Subtitle[0]: + presentationTimeUs = 0 Cues = [] Subtitle[1]: + presentationTimeUs = 0 Cue[0]: text = This is the first subtitle. textAlignment = ALIGN_CENTER @@ -360,8 +362,10 @@ TextOutput: positionAnchor = 1 size = 1.0 Subtitle[2]: + presentationTimeUs = 1234000 Cues = [] Subtitle[3]: + presentationTimeUs = 2345000 Cue[0]: text = This is the second subtitle. textAlignment = ALIGN_CENTER @@ -372,4 +376,5 @@ TextOutput: positionAnchor = 1 size = 1.0 Subtitle[4]: + presentationTimeUs = 3456000 Cues = [] diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java index 2cfaf781b6..b84b90a9a6 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaControllerListenerTest.java @@ -1920,7 +1920,7 @@ public class MediaControllerListenerTest { Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() - .setCurrentCues(new CueGroup(testCues)) + .setCurrentCues(new CueGroup(testCues, /* presentationTimeUs= */ 1_230_000)) .build(); remoteSession.setPlayer(playerConfig); @@ -1938,7 +1938,7 @@ public class MediaControllerListenerTest { Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() - .setCurrentCues(new CueGroup(testCues)) + .setCurrentCues(new CueGroup(testCues, /* presentationTimeUs= */ 1_230_000)) .build(); remoteSession.setPlayer(playerConfig); @@ -1991,7 +1991,7 @@ public class MediaControllerListenerTest { Bundle playerConfig = new RemoteMediaSession.MockPlayerConfigBuilder() - .setCurrentCues(new CueGroup(testCues)) + .setCurrentCues(new CueGroup(testCues, /* presentationTimeUs= */ 1_230_000)) .build(); remoteSession.setPlayer(playerConfig); @@ -2021,7 +2021,9 @@ public class MediaControllerListenerTest { }; threadTestRule.getHandler().postAndSync(() -> controller.addListener(listener)); - remoteSession.getMockPlayer().notifyCuesChanged(new CueGroup(testCues)); + remoteSession + .getMockPlayer() + .notifyCuesChanged(new CueGroup(testCues, /* presentationTimeUs= */ 1_230_000)); assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); assertThat(cuesFromParam).isEqualTo(testCues); diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java index 6bf44ca49f..fa4605cfaa 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MediaSessionProviderService.java @@ -105,6 +105,8 @@ import java.util.concurrent.Callable; public class MediaSessionProviderService extends Service { private static final String TAG = "MSProviderService"; + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); private Map sessionMap = new HashMap<>(); private RemoteMediaSessionStub sessionBinder; @@ -327,7 +329,7 @@ public class MediaSessionProviderService extends Service { } Bundle cueGroupBundle = config.getBundle(KEY_CURRENT_CUE_GROUP); player.cueGroup = - cueGroupBundle == null ? CueGroup.EMPTY : CueGroup.CREATOR.fromBundle(cueGroupBundle); + cueGroupBundle == null ? EMPTY_CUE_GROUP : CueGroup.CREATOR.fromBundle(cueGroupBundle); @Nullable Bundle deviceInfoBundle = config.getBundle(KEY_DEVICE_INFO); if (deviceInfoBundle != null) { player.deviceInfo = DeviceInfo.CREATOR.fromBundle(deviceInfoBundle); diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java index d9a9258b65..ee26b18e33 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java @@ -196,6 +196,8 @@ public class MockPlayer implements Player { private final ArraySet listeners = new ArraySet<>(); private final ImmutableMap<@Method Integer, ConditionVariable> conditionVariables = createMethodConditionVariables(); + private static final CueGroup EMPTY_CUE_GROUP = + new CueGroup(ImmutableList.of(), /* presentationTimeUs= */ 0); @Nullable PlaybackException playerError; public AudioAttributes audioAttributes; @@ -276,7 +278,7 @@ public class MockPlayer implements Player { repeatMode = Player.REPEAT_MODE_OFF; videoSize = VideoSize.UNKNOWN; volume = 1.0f; - cueGroup = CueGroup.EMPTY; + cueGroup = EMPTY_CUE_GROUP; deviceInfo = DeviceInfo.UNKNOWN; seekPositionMs = C.TIME_UNSET; seekMediaItemIndex = C.INDEX_UNSET; diff --git a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java index 947ab7d82c..8fc015fc9c 100644 --- a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java +++ b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/PlaybackOutput.java @@ -15,6 +15,8 @@ */ package androidx.media3.test.utils.robolectric; +import static java.lang.Math.max; + import android.graphics.Bitmap; import androidx.annotation.Nullable; import androidx.media3.common.Metadata; @@ -56,7 +58,7 @@ public final class PlaybackOutput implements Dumper.Dumpable { private final CapturingRenderersFactory capturingRenderersFactory; private final List metadatas; - private final List> subtitles; + private final List subtitles; private final List> subtitlesFromDeprecatedTextOutput; private PlaybackOutput(ExoPlayer player, CapturingRenderersFactory capturingRenderersFactory) { @@ -65,8 +67,8 @@ public final class PlaybackOutput implements Dumper.Dumpable { metadatas = Collections.synchronizedList(new ArrayList<>()); subtitles = Collections.synchronizedList(new ArrayList<>()); subtitlesFromDeprecatedTextOutput = Collections.synchronizedList(new ArrayList<>()); - // TODO: Consider passing playback position into MetadataOutput and TextOutput. Calling - // player.getCurrentPosition() inside onMetadata/Cues will likely be non-deterministic + // TODO: Consider passing playback position into MetadataOutput. Calling + // player.getCurrentPosition() inside onMetadata will likely be non-deterministic // because renderer-thread != playback-thread. player.addListener( new Player.Listener() { @@ -82,7 +84,7 @@ public final class PlaybackOutput implements Dumper.Dumpable { @Override public void onCues(CueGroup cueGroup) { - subtitles.add(cueGroup.cues); + subtitles.add(cueGroup); } }); } @@ -154,9 +156,9 @@ public final class PlaybackOutput implements Dumper.Dumpable { } private void dumpSubtitles(Dumper dumper) { - if (!subtitles.equals(subtitlesFromDeprecatedTextOutput)) { + if (subtitles.size() != subtitlesFromDeprecatedTextOutput.size()) { throw new IllegalStateException( - "Expected subtitles to be equal from both implementations of onCues method."); + "Expected subtitles to be of equal length from both implementations of onCues method."); } if (subtitles.isEmpty()) { @@ -165,7 +167,15 @@ public final class PlaybackOutput implements Dumper.Dumpable { dumper.startBlock("TextOutput"); for (int i = 0; i < subtitles.size(); i++) { dumper.startBlock("Subtitle[" + i + "]"); - List subtitle = subtitles.get(i); + // TODO: Solving https://github.com/google/ExoPlayer/issues/9672 will allow us to remove this + // hack of forcing presentationTimeUs to be >= 0. + dumper.add("presentationTimeUs", max(0, subtitles.get(i).presentationTimeUs)); + ImmutableList subtitle = subtitles.get(i).cues; + if (!subtitle.equals(subtitlesFromDeprecatedTextOutput.get(i))) { + throw new IllegalStateException( + "Expected subtitle to be equal from both implementations of onCues method for index " + + i); + } if (subtitle.isEmpty()) { dumper.add("Cues", ImmutableList.of()); }