|
18 | 18 | import static androidx.media3.common.C.DATA_TYPE_MEDIA;
|
19 | 19 | import static androidx.media3.common.util.Assertions.checkNotNull;
|
20 | 20 | import static androidx.media3.exoplayer.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState;
|
| 21 | +import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.END_OF_STREAM_ITEM; |
| 22 | +import static androidx.media3.test.utils.FakeSampleStream.FakeSampleStreamItem.oneByteSample; |
21 | 23 | import static androidx.media3.test.utils.robolectric.RobolectricUtil.runMainLooperUntil;
|
22 | 24 | import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.playUntilPosition;
|
23 | 25 | import static androidx.media3.test.utils.robolectric.TestPlayerRunHelper.runUntilIsLoading;
|
|
45 | 47 | import androidx.media3.common.MediaItem;
|
46 | 48 | import androidx.media3.common.Player;
|
47 | 49 | import androidx.media3.common.Timeline;
|
| 50 | +import androidx.media3.common.TrackGroup; |
48 | 51 | import androidx.media3.common.util.Clock;
|
49 | 52 | import androidx.media3.common.util.Util;
|
| 53 | +import androidx.media3.datasource.TransferListener; |
| 54 | +import androidx.media3.decoder.DecoderInputBuffer; |
50 | 55 | import androidx.media3.exoplayer.ExoPlayer;
|
| 56 | +import androidx.media3.exoplayer.FormatHolder; |
51 | 57 | import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
52 | 58 | import androidx.media3.exoplayer.analytics.PlayerId;
|
| 59 | +import androidx.media3.exoplayer.drm.DrmSessionEventListener; |
| 60 | +import androidx.media3.exoplayer.drm.DrmSessionManager; |
53 | 61 | import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
54 | 62 | import androidx.media3.exoplayer.source.MediaLoadData;
|
55 | 63 | import androidx.media3.exoplayer.source.MediaPeriod;
|
56 | 64 | import androidx.media3.exoplayer.source.MediaSource;
|
57 | 65 | import androidx.media3.exoplayer.source.MediaSourceEventListener;
|
| 66 | +import androidx.media3.exoplayer.source.SampleStream; |
58 | 67 | import androidx.media3.exoplayer.source.SinglePeriodTimeline;
|
| 68 | +import androidx.media3.exoplayer.source.TrackGroupArray; |
| 69 | +import androidx.media3.exoplayer.trackselection.ExoTrackSelection; |
| 70 | +import androidx.media3.exoplayer.trackselection.FixedTrackSelection; |
| 71 | +import androidx.media3.exoplayer.upstream.Allocator; |
59 | 72 | import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
60 | 73 | import androidx.media3.test.utils.CapturingRenderersFactory;
|
61 | 74 | import androidx.media3.test.utils.DumpFileAsserts;
|
62 | 75 | import androidx.media3.test.utils.FakeClock;
|
| 76 | +import androidx.media3.test.utils.FakeMediaPeriod; |
63 | 77 | import androidx.media3.test.utils.FakeMediaSource;
|
| 78 | +import androidx.media3.test.utils.FakeSampleStream; |
64 | 79 | import androidx.media3.test.utils.FakeTimeline;
|
65 | 80 | import androidx.media3.test.utils.robolectric.PlaybackOutput;
|
| 81 | +import androidx.media3.test.utils.robolectric.RobolectricUtil; |
66 | 82 | import androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig;
|
67 | 83 | import androidx.test.core.app.ApplicationProvider;
|
68 | 84 | import androidx.test.ext.junit.runners.AndroidJUnit4;
|
| 85 | +import com.google.common.collect.ImmutableList; |
69 | 86 | import com.google.common.collect.ImmutableMap;
|
70 | 87 | import java.util.ArrayList;
|
| 88 | +import java.util.List; |
| 89 | +import java.util.concurrent.atomic.AtomicBoolean; |
71 | 90 | import java.util.concurrent.atomic.AtomicReference;
|
72 | 91 | import org.junit.Assert;
|
73 | 92 | import org.junit.Rule;
|
@@ -689,4 +708,128 @@ public void playbackWithSeek_isHandledCorrectly() throws Exception {
|
689 | 708 | // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun)
|
690 | 709 | verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong());
|
691 | 710 | }
|
| 711 | + |
| 712 | + @Test |
| 713 | + public void serverSideAdInsertionSampleStream_withFastLoadingSourceAfterFirstRead_canBeReadFully() |
| 714 | + throws Exception { |
| 715 | + TrackGroup trackGroup = new TrackGroup(new Format.Builder().build()); |
| 716 | + // Set up MediaPeriod with no samples and only add samples after the first SampleStream read. |
| 717 | + FakeMediaPeriod mediaPeriod = |
| 718 | + new FakeMediaPeriod( |
| 719 | + new TrackGroupArray(trackGroup), |
| 720 | + new DefaultAllocator(/* trimOnReset= */ true, /* individualAllocationSize= */ 1024), |
| 721 | + /* trackDataFactory= */ (format, mediaPeriodId) -> ImmutableList.of(), |
| 722 | + new MediaSourceEventListener.EventDispatcher() |
| 723 | + .withParameters( |
| 724 | + /* windowIndex= */ 0, |
| 725 | + new MediaSource.MediaPeriodId(/* periodUid= */ new Object())), |
| 726 | + DrmSessionManager.DRM_UNSUPPORTED, |
| 727 | + new DrmSessionEventListener.EventDispatcher(), |
| 728 | + /* deferOnPrepared= */ false) { |
| 729 | + @Override |
| 730 | + protected FakeSampleStream createSampleStream( |
| 731 | + Allocator allocator, |
| 732 | + @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, |
| 733 | + DrmSessionManager drmSessionManager, |
| 734 | + DrmSessionEventListener.EventDispatcher drmEventDispatcher, |
| 735 | + Format initialFormat, |
| 736 | + List<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems) { |
| 737 | + return new FakeSampleStream( |
| 738 | + allocator, |
| 739 | + mediaSourceEventDispatcher, |
| 740 | + drmSessionManager, |
| 741 | + drmEventDispatcher, |
| 742 | + initialFormat, |
| 743 | + /* fakeSampleStreamItems= */ ImmutableList.of()) { |
| 744 | + private boolean addedSamples = false; |
| 745 | + |
| 746 | + @Override |
| 747 | + public int readData( |
| 748 | + FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { |
| 749 | + int result = super.readData(formatHolder, buffer, readFlags); |
| 750 | + if (!addedSamples) { |
| 751 | + append( |
| 752 | + ImmutableList.of( |
| 753 | + oneByteSample(/* timeUs= */ 0, C.BUFFER_FLAG_KEY_FRAME), |
| 754 | + oneByteSample(/* timeUs= */ 200, C.BUFFER_FLAG_KEY_FRAME), |
| 755 | + oneByteSample(/* timeUs= */ 400, C.BUFFER_FLAG_KEY_FRAME), |
| 756 | + oneByteSample(/* timeUs= */ 600, C.BUFFER_FLAG_KEY_FRAME), |
| 757 | + oneByteSample(/* timeUs= */ 800, C.BUFFER_FLAG_KEY_FRAME), |
| 758 | + END_OF_STREAM_ITEM)); |
| 759 | + writeData(/* startPositionUs= */ 0); |
| 760 | + addedSamples = true; |
| 761 | + } |
| 762 | + return result; |
| 763 | + } |
| 764 | + }; |
| 765 | + } |
| 766 | + }; |
| 767 | + FakeMediaSource mediaSource = |
| 768 | + new FakeMediaSource() { |
| 769 | + @Override |
| 770 | + protected MediaPeriod createMediaPeriod( |
| 771 | + MediaPeriodId id, |
| 772 | + TrackGroupArray trackGroupArray, |
| 773 | + Allocator allocator, |
| 774 | + MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, |
| 775 | + DrmSessionManager drmSessionManager, |
| 776 | + DrmSessionEventListener.EventDispatcher drmEventDispatcher, |
| 777 | + @Nullable TransferListener transferListener) { |
| 778 | + return mediaPeriod; |
| 779 | + } |
| 780 | + }; |
| 781 | + ServerSideAdInsertionMediaSource serverSideAdInsertionMediaSource = |
| 782 | + new ServerSideAdInsertionMediaSource(mediaSource, /* adPlaybackStateUpdater= */ null); |
| 783 | + Timeline timeline = new FakeTimeline(); |
| 784 | + Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 0); |
| 785 | + serverSideAdInsertionMediaSource.setAdPlaybackStates( |
| 786 | + ImmutableMap.of(periodUid, new AdPlaybackState(/* adsId= */ new Object())), timeline); |
| 787 | + AtomicBoolean sourcePrepared = new AtomicBoolean(); |
| 788 | + serverSideAdInsertionMediaSource.prepareSource( |
| 789 | + (source, newTimeline) -> sourcePrepared.set(true), |
| 790 | + /* mediaTransferListener= */ null, |
| 791 | + PlayerId.UNSET); |
| 792 | + RobolectricUtil.runMainLooperUntil(sourcePrepared::get); |
| 793 | + MediaPeriod serverSideAdInsertionMediaPeriod = |
| 794 | + serverSideAdInsertionMediaSource.createPeriod( |
| 795 | + new MediaSource.MediaPeriodId(periodUid), |
| 796 | + /* allocator= */ null, |
| 797 | + /* startPositionUs= */ 0); |
| 798 | + AtomicBoolean periodPrepared = new AtomicBoolean(); |
| 799 | + serverSideAdInsertionMediaPeriod.prepare( |
| 800 | + new MediaPeriod.Callback() { |
| 801 | + @Override |
| 802 | + public void onPrepared(MediaPeriod mediaPeriod) { |
| 803 | + periodPrepared.set(true); |
| 804 | + } |
| 805 | + |
| 806 | + @Override |
| 807 | + public void onContinueLoadingRequested(MediaPeriod source) { |
| 808 | + serverSideAdInsertionMediaPeriod.continueLoading(/* positionUs= */ 0); |
| 809 | + } |
| 810 | + }, |
| 811 | + /* positionUs= */ 0); |
| 812 | + RobolectricUtil.runMainLooperUntil(periodPrepared::get); |
| 813 | + SampleStream[] sampleStreams = new SampleStream[1]; |
| 814 | + serverSideAdInsertionMediaPeriod.selectTracks( |
| 815 | + new ExoTrackSelection[] {new FixedTrackSelection(trackGroup, /* track= */ 0)}, |
| 816 | + /* mayRetainStreamFlags= */ new boolean[] {false}, |
| 817 | + sampleStreams, |
| 818 | + /* streamResetFlags= */ new boolean[] {true}, |
| 819 | + /* positionUs= */ 0); |
| 820 | + FormatHolder formatHolder = new FormatHolder(); |
| 821 | + DecoderInputBuffer buffer = |
| 822 | + new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); |
| 823 | + ArrayList<Long> readSamples = new ArrayList<>(); |
| 824 | + |
| 825 | + int result; |
| 826 | + do { |
| 827 | + result = sampleStreams[0].readData(formatHolder, buffer, /* readFlags= */ 0); |
| 828 | + if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream()) { |
| 829 | + readSamples.add(buffer.timeUs); |
| 830 | + } |
| 831 | + } while (result != C.RESULT_BUFFER_READ || !buffer.isEndOfStream()); |
| 832 | + |
| 833 | + assertThat(readSamples).containsExactly(0L, 200L, 400L, 600L, 800L).inOrder(); |
| 834 | + } |
692 | 835 | }
|
0 commit comments