Search in sources :

Example 16 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part in project ExoPlayer by google.

the class HlsChunkSource method getNextMediaSequenceAndPartIndex.

// Private methods.
/**
 * Returns the media sequence number and part index to load next in the {@code mediaPlaylist}.
 *
 * @param previous The last (at least partially) loaded segment.
 * @param switchingTrack Whether the segment to load is not preceded by a segment in the same
 *     track.
 * @param mediaPlaylist The media playlist to which the segment to load belongs.
 * @param startOfPlaylistInPeriodUs The start of {@code mediaPlaylist} relative to the period
 *     start in microseconds.
 * @param loadPositionUs The current load position relative to the period start in microseconds.
 * @return The media sequence and part index to load.
 */
private Pair<Long, Integer> getNextMediaSequenceAndPartIndex(@Nullable HlsMediaChunk previous, boolean switchingTrack, HlsMediaPlaylist mediaPlaylist, long startOfPlaylistInPeriodUs, long loadPositionUs) {
    if (previous == null || switchingTrack) {
        long endOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs + mediaPlaylist.durationUs;
        long targetPositionInPeriodUs = (previous == null || independentSegments) ? loadPositionUs : previous.startTimeUs;
        if (!mediaPlaylist.hasEndTag && targetPositionInPeriodUs >= endOfPlaylistInPeriodUs) {
            // If the playlist is too old to contain the chunk, we need to refresh it.
            return new Pair<>(mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(), /* partIndex */
            C.INDEX_UNSET);
        }
        long targetPositionInPlaylistUs = targetPositionInPeriodUs - startOfPlaylistInPeriodUs;
        int segmentIndexInPlaylist = Util.binarySearchFloor(mediaPlaylist.segments, /* value= */
        targetPositionInPlaylistUs, /* inclusive= */
        true, /* stayInBounds= */
        !playlistTracker.isLive() || previous == null);
        long mediaSequence = segmentIndexInPlaylist + mediaPlaylist.mediaSequence;
        int partIndex = C.INDEX_UNSET;
        if (segmentIndexInPlaylist >= 0) {
            // In case we are inside the live window, we try to pick a part if available.
            Segment segment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
            List<HlsMediaPlaylist.Part> parts = targetPositionInPlaylistUs < segment.relativeStartTimeUs + segment.durationUs ? segment.parts : mediaPlaylist.trailingParts;
            for (int i = 0; i < parts.size(); i++) {
                HlsMediaPlaylist.Part part = parts.get(i);
                if (targetPositionInPlaylistUs < part.relativeStartTimeUs + part.durationUs) {
                    if (part.isIndependent) {
                        partIndex = i;
                        // Increase media sequence by one if the part is a trailing part.
                        mediaSequence += parts == mediaPlaylist.trailingParts ? 1 : 0;
                    }
                    break;
                }
            }
        }
        return new Pair<>(mediaSequence, partIndex);
    }
    // If loading has not completed, we return the previous chunk again.
    return (previous.isLoadCompleted() ? new Pair<>(previous.partIndex == C.INDEX_UNSET ? previous.getNextChunkIndex() : previous.chunkIndex, previous.partIndex == C.INDEX_UNSET ? C.INDEX_UNSET : previous.partIndex + 1) : new Pair<>(previous.chunkIndex, previous.partIndex));
}
Also used : HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) Pair(android.util.Pair)

Example 17 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part in project ExoPlayer by google.

the class HlsChunkSource method getSegmentBaseList.

// Package methods.
/**
 * Returns a list with all segment bases in the playlist starting from {@code mediaSequence} and
 * {@code partIndex} in the given playlist. The list may be empty if the starting point is not in
 * the playlist.
 */
@VisibleForTesting
static /* package */
List<HlsMediaPlaylist.SegmentBase> getSegmentBaseList(HlsMediaPlaylist playlist, long mediaSequence, int partIndex) {
    int firstSegmentIndexInPlaylist = (int) (mediaSequence - playlist.mediaSequence);
    if (firstSegmentIndexInPlaylist < 0 || playlist.segments.size() < firstSegmentIndexInPlaylist) {
        // The first media sequence is not in the playlist.
        return ImmutableList.of();
    }
    List<HlsMediaPlaylist.SegmentBase> segmentBases = new ArrayList<>();
    if (firstSegmentIndexInPlaylist < playlist.segments.size()) {
        if (partIndex != C.INDEX_UNSET) {
            // The iterator starts with a part that belongs to a segment.
            Segment firstSegment = playlist.segments.get(firstSegmentIndexInPlaylist);
            if (partIndex == 0) {
                // Use the full segment instead of the first part.
                segmentBases.add(firstSegment);
            } else if (partIndex < firstSegment.parts.size()) {
                // Add the parts from the first requested segment.
                segmentBases.addAll(firstSegment.parts.subList(partIndex, firstSegment.parts.size()));
            }
            firstSegmentIndexInPlaylist++;
        }
        partIndex = 0;
        // Add all remaining segments.
        segmentBases.addAll(playlist.segments.subList(firstSegmentIndexInPlaylist, playlist.segments.size()));
    }
    if (playlist.partTargetDurationUs != C.TIME_UNSET) {
        // That's a low latency playlist.
        partIndex = partIndex == C.INDEX_UNSET ? 0 : partIndex;
        if (partIndex < playlist.trailingParts.size()) {
            segmentBases.addAll(playlist.trailingParts.subList(partIndex, playlist.trailingParts.size()));
        }
    }
    return Collections.unmodifiableList(segmentBases);
}
Also used : ArrayList(java.util.ArrayList) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) VisibleForTesting(androidx.annotation.VisibleForTesting)

Example 18 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part in project ExoPlayer by google.

the class HlsMediaChunk method feedDataToExtractor.

/**
 * Attempts to feed the given {@code dataSpec} to {@code this.extractor}. Whenever the operation
 * concludes (because of a thrown exception or because the operation finishes), the number of fed
 * bytes is written to {@code nextLoadPosition}.
 */
@RequiresNonNull("output")
private void feedDataToExtractor(DataSource dataSource, DataSpec dataSpec, boolean dataIsEncrypted, boolean initializeTimestampAdjuster) throws IOException {
    // If we previously fed part of this chunk to the extractor, we need to skip it this time. For
    // encrypted content we need to skip the data by reading it through the source, so as to ensure
    // correct decryption of the remainder of the chunk. For clear content, we can request the
    // remainder of the chunk directly.
    DataSpec loadDataSpec;
    boolean skipLoadedBytes;
    if (dataIsEncrypted) {
        loadDataSpec = dataSpec;
        skipLoadedBytes = nextLoadPosition != 0;
    } else {
        loadDataSpec = dataSpec.subrange(nextLoadPosition);
        skipLoadedBytes = false;
    }
    try {
        ExtractorInput input = prepareExtraction(dataSource, loadDataSpec, initializeTimestampAdjuster);
        if (skipLoadedBytes) {
            input.skipFully(nextLoadPosition);
        }
        try {
            while (!loadCanceled && extractor.read(input)) {
            }
        } catch (EOFException e) {
            if ((trackFormat.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
                // See onTruncatedSegmentParsed's javadoc for more info on why we are swallowing the EOF
                // exception for trick play tracks.
                extractor.onTruncatedSegmentParsed();
            } else {
                throw e;
            }
        } finally {
            nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
        }
    } finally {
        DataSourceUtil.closeQuietly(dataSource);
    }
}
Also used : ExtractorInput(com.google.android.exoplayer2.extractor.ExtractorInput) DefaultExtractorInput(com.google.android.exoplayer2.extractor.DefaultExtractorInput) EOFException(java.io.EOFException) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 19 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part in project ExoPlayer by google.

the class ServerSideAdInsertionMediaSourceTest method playbackWithSeek_isHandledCorrectly.

@Test
public void playbackWithSeek_isHandledCorrectly() throws Exception {
    Context context = ApplicationProvider.getApplicationContext();
    ExoPlayer player = new ExoPlayer.Builder(context).setClock(new FakeClock(/* isAutoAdvancing= */
    true)).build();
    player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */
    1)));
    AdPlaybackState adPlaybackState = new AdPlaybackState(/* adsId= */
    new Object());
    adPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
    0, /* contentResumeOffsetUs= */
    0, /* adDurationsUs...= */
    100_000);
    adPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
    600_000, /* contentResumeOffsetUs= */
    1_000_000, /* adDurationsUs...= */
    100_000);
    AdPlaybackState firstAdPlaybackState = addAdGroupToAdPlaybackState(adPlaybackState, /* fromPositionUs= */
    900_000, /* contentResumeOffsetUs= */
    0, /* adDurationsUs...= */
    100_000);
    AtomicReference<ServerSideAdInsertionMediaSource> mediaSourceRef = new AtomicReference<>();
    mediaSourceRef.set(new ServerSideAdInsertionMediaSource(new DefaultMediaSourceFactory(context).createMediaSource(MediaItem.fromUri(TEST_ASSET)), /* adPlaybackStateUpdater= */
    contentTimeline -> {
        Object periodUid = checkNotNull(contentTimeline.getPeriod(/* periodIndex= */
        0, new Timeline.Period(), /* setIds= */
        true).uid);
        mediaSourceRef.get().setAdPlaybackStates(ImmutableMap.of(periodUid, firstAdPlaybackState));
        return true;
    }));
    AnalyticsListener listener = mock(AnalyticsListener.class);
    player.addAnalyticsListener(listener);
    player.setMediaSource(mediaSourceRef.get());
    player.prepare();
    // Play to the first content part, then seek past the midroll.
    playUntilPosition(player, /* mediaItemIndex= */
    0, /* positionMs= */
    100);
    player.seekTo(/* positionMs= */
    1_600);
    runUntilPendingCommandsAreFullyHandled(player);
    long positionAfterSeekMs = player.getCurrentPosition();
    long contentPositionAfterSeekMs = player.getContentPosition();
    player.play();
    runUntilPlaybackState(player, Player.STATE_ENDED);
    player.release();
    // Assert playback has been reported with ads: [ad0][content] seek [ad1][content][ad2][content]
    // 6*2(audio+video) format changes, 4 auto-transitions between parts, 1 seek with adjustment.
    verify(listener, times(4)).onPositionDiscontinuity(any(), any(), any(), eq(Player.DISCONTINUITY_REASON_AUTO_TRANSITION));
    verify(listener, times(1)).onPositionDiscontinuity(any(), any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
    verify(listener, times(1)).onPositionDiscontinuity(any(), any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT));
    verify(listener, times(12)).onDownstreamFormatChanged(any(), any());
    assertThat(contentPositionAfterSeekMs).isEqualTo(1_600);
    // Beginning of second ad.
    assertThat(positionAfterSeekMs).isEqualTo(0);
    // Assert renderers played through without reset, except for the seek.
    verify(listener, times(2)).onVideoEnabled(any(), any());
    verify(listener, times(2)).onAudioEnabled(any(), any());
    // Assert playback progression was smooth (=no unexpected delays that cause audio to underrun)
    verify(listener, never()).onAudioUnderrun(any(), anyInt(), anyLong(), anyLong());
}
Also used : Context(android.content.Context) ArgumentMatchers.any(org.mockito.ArgumentMatchers.any) AnalyticsListener(com.google.android.exoplayer2.analytics.AnalyticsListener) Context(android.content.Context) TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled(com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPendingCommandsAreFullyHandled) ArgumentMatchers.anyLong(org.mockito.ArgumentMatchers.anyLong) ArgumentMatchers.eq(org.mockito.ArgumentMatchers.eq) Pair(android.util.Pair) RunWith(org.junit.runner.RunWith) Player(com.google.android.exoplayer2.Player) DefaultMediaSourceFactory(com.google.android.exoplayer2.source.DefaultMediaSourceFactory) TestPlayerRunHelper.playUntilPosition(com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.playUntilPosition) AndroidJUnit4(androidx.test.ext.junit.runners.AndroidJUnit4) AtomicReference(java.util.concurrent.atomic.AtomicReference) ApplicationProvider(androidx.test.core.app.ApplicationProvider) ExoPlayer(com.google.android.exoplayer2.ExoPlayer) PlayerId(com.google.android.exoplayer2.analytics.PlayerId) ShadowMediaCodecConfig(com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig) ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState) ArgumentMatchers.anyInt(org.mockito.ArgumentMatchers.anyInt) RobolectricUtil.runMainLooperUntil(com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil) SurfaceTexture(android.graphics.SurfaceTexture) MediaItem(com.google.android.exoplayer2.MediaItem) TestPlayerRunHelper.runUntilPlaybackState(com.google.android.exoplayer2.robolectric.TestPlayerRunHelper.runUntilPlaybackState) Surface(android.view.Surface) ImmutableMap(com.google.common.collect.ImmutableMap) CapturingRenderersFactory(com.google.android.exoplayer2.testutil.CapturingRenderersFactory) Mockito.times(org.mockito.Mockito.times) Test(org.junit.Test) FakeClock(com.google.android.exoplayer2.testutil.FakeClock) Truth.assertThat(com.google.common.truth.Truth.assertThat) Mockito.verify(org.mockito.Mockito.verify) FakeMediaSource(com.google.android.exoplayer2.testutil.FakeMediaSource) Mockito.never(org.mockito.Mockito.never) Timeline(com.google.android.exoplayer2.Timeline) FakeTimeline(com.google.android.exoplayer2.testutil.FakeTimeline) Rule(org.junit.Rule) DumpFileAsserts(com.google.android.exoplayer2.testutil.DumpFileAsserts) PlaybackOutput(com.google.android.exoplayer2.robolectric.PlaybackOutput) Assert(org.junit.Assert) Assertions.checkNotNull(com.google.android.exoplayer2.util.Assertions.checkNotNull) Mockito.mock(org.mockito.Mockito.mock) AnalyticsListener(com.google.android.exoplayer2.analytics.AnalyticsListener) FakeClock(com.google.android.exoplayer2.testutil.FakeClock) AtomicReference(java.util.concurrent.atomic.AtomicReference) ExoPlayer(com.google.android.exoplayer2.ExoPlayer) Surface(android.view.Surface) SurfaceTexture(android.graphics.SurfaceTexture) ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState(com.google.android.exoplayer2.source.ads.ServerSideAdInsertionUtil.addAdGroupToAdPlaybackState) DefaultMediaSourceFactory(com.google.android.exoplayer2.source.DefaultMediaSourceFactory) Test(org.junit.Test)

Example 20 with Part

use of com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part in project ExoPlayer by google.

the class SegmentDownloader method download.

@Override
public final void download(@Nullable ProgressListener progressListener) throws IOException, InterruptedException {
    ArrayDeque<Segment> pendingSegments = new ArrayDeque<>();
    ArrayDeque<SegmentDownloadRunnable> recycledRunnables = new ArrayDeque<>();
    if (priorityTaskManager != null) {
        priorityTaskManager.add(C.PRIORITY_DOWNLOAD);
    }
    try {
        CacheDataSource dataSource = cacheDataSourceFactory.createDataSourceForDownloading();
        // Get the manifest and all of the segments.
        M manifest = getManifest(dataSource, manifestDataSpec, /* removing= */
        false);
        if (!streamKeys.isEmpty()) {
            manifest = manifest.copy(streamKeys);
        }
        List<Segment> segments = getSegments(dataSource, manifest, /* removing= */
        false);
        // Sort the segments so that we download media in the right order from the start of the
        // content, and merge segments where possible to minimize the number of server round trips.
        Collections.sort(segments);
        mergeSegments(segments, cacheKeyFactory);
        // Scan the segments, removing any that are fully downloaded.
        int totalSegments = segments.size();
        int segmentsDownloaded = 0;
        long contentLength = 0;
        long bytesDownloaded = 0;
        for (int i = segments.size() - 1; i >= 0; i--) {
            DataSpec dataSpec = segments.get(i).dataSpec;
            String cacheKey = cacheKeyFactory.buildCacheKey(dataSpec);
            long segmentLength = dataSpec.length;
            if (segmentLength == C.LENGTH_UNSET) {
                long resourceLength = ContentMetadata.getContentLength(cache.getContentMetadata(cacheKey));
                if (resourceLength != C.LENGTH_UNSET) {
                    segmentLength = resourceLength - dataSpec.position;
                }
            }
            long segmentBytesDownloaded = cache.getCachedBytes(cacheKey, dataSpec.position, segmentLength);
            bytesDownloaded += segmentBytesDownloaded;
            if (segmentLength != C.LENGTH_UNSET) {
                if (segmentLength == segmentBytesDownloaded) {
                    // The segment is fully downloaded.
                    segmentsDownloaded++;
                    segments.remove(i);
                }
                if (contentLength != C.LENGTH_UNSET) {
                    contentLength += segmentLength;
                }
            } else {
                contentLength = C.LENGTH_UNSET;
            }
        }
        // Download the segments.
        @Nullable ProgressNotifier progressNotifier = progressListener != null ? new ProgressNotifier(progressListener, contentLength, totalSegments, bytesDownloaded, segmentsDownloaded) : null;
        pendingSegments.addAll(segments);
        while (!isCanceled && !pendingSegments.isEmpty()) {
            // Block until there aren't any higher priority tasks.
            if (priorityTaskManager != null) {
                priorityTaskManager.proceed(C.PRIORITY_DOWNLOAD);
            }
            // Create and execute a runnable to download the next segment.
            CacheDataSource segmentDataSource;
            byte[] temporaryBuffer;
            if (!recycledRunnables.isEmpty()) {
                SegmentDownloadRunnable recycledRunnable = recycledRunnables.removeFirst();
                segmentDataSource = recycledRunnable.dataSource;
                temporaryBuffer = recycledRunnable.temporaryBuffer;
            } else {
                segmentDataSource = cacheDataSourceFactory.createDataSourceForDownloading();
                temporaryBuffer = new byte[BUFFER_SIZE_BYTES];
            }
            Segment segment = pendingSegments.removeFirst();
            SegmentDownloadRunnable downloadRunnable = new SegmentDownloadRunnable(segment, segmentDataSource, progressNotifier, temporaryBuffer);
            addActiveRunnable(downloadRunnable);
            executor.execute(downloadRunnable);
            // Clean up runnables that have finished.
            for (int j = activeRunnables.size() - 1; j >= 0; j--) {
                SegmentDownloadRunnable activeRunnable = (SegmentDownloadRunnable) activeRunnables.get(j);
                // it's already finished.
                if (pendingSegments.isEmpty() || activeRunnable.isDone()) {
                    try {
                        activeRunnable.get();
                        removeActiveRunnable(j);
                        recycledRunnables.addLast(activeRunnable);
                    } catch (ExecutionException e) {
                        Throwable cause = Assertions.checkNotNull(e.getCause());
                        if (cause instanceof PriorityTooLowException) {
                            // We need to schedule this segment again in a future loop iteration.
                            pendingSegments.addFirst(activeRunnable.segment);
                            removeActiveRunnable(j);
                            recycledRunnables.addLast(activeRunnable);
                        } else if (cause instanceof IOException) {
                            throw (IOException) cause;
                        } else {
                            // The cause must be an uncaught Throwable type.
                            Util.sneakyThrow(cause);
                        }
                    }
                }
            }
            // Don't move on to the next segment until the runnable for this segment has started. This
            // drip feeds runnables to the executor, rather than providing them all up front.
            downloadRunnable.blockUntilStarted();
        }
    } finally {
        // Cancel them to speed this up.
        for (int i = 0; i < activeRunnables.size(); i++) {
            activeRunnables.get(i).cancel(/* interruptIfRunning= */
            true);
        }
        // do this for the case where the main download thread was interrupted as part of cancelation.
        for (int i = activeRunnables.size() - 1; i >= 0; i--) {
            activeRunnables.get(i).blockUntilFinished();
            removeActiveRunnable(i);
        }
        if (priorityTaskManager != null) {
            priorityTaskManager.remove(C.PRIORITY_DOWNLOAD);
        }
    }
}
Also used : IOException(java.io.IOException) ArrayDeque(java.util.ArrayDeque) PriorityTooLowException(com.google.android.exoplayer2.util.PriorityTaskManager.PriorityTooLowException) CacheDataSource(com.google.android.exoplayer2.upstream.cache.CacheDataSource) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) ExecutionException(java.util.concurrent.ExecutionException) Nullable(androidx.annotation.Nullable)

Aggregations

Test (org.junit.Test)14 MediaItem (com.google.android.exoplayer2.MediaItem)11 Timeline (com.google.android.exoplayer2.Timeline)11 Nullable (androidx.annotation.Nullable)6 HlsMediaPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist)6 DataSpec (com.google.android.exoplayer2.upstream.DataSpec)6 ArrayList (java.util.ArrayList)6 Segment (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)4 Uri (android.net.Uri)3 MediaPeriodId (com.google.android.exoplayer2.source.MediaSource.MediaPeriodId)3 SurfaceTexture (android.graphics.SurfaceTexture)2 Pair (android.util.Pair)2 Surface (android.view.Surface)2 ApplicationProvider (androidx.test.core.app.ApplicationProvider)2 AndroidJUnit4 (androidx.test.ext.junit.runners.AndroidJUnit4)2 ExoPlayer (com.google.android.exoplayer2.ExoPlayer)2 Player (com.google.android.exoplayer2.Player)2 DrmInitData (com.google.android.exoplayer2.drm.DrmInitData)2 MediaChunkIterator (com.google.android.exoplayer2.source.chunk.MediaChunkIterator)2 RequiresNonNull (org.checkerframework.checker.nullness.qual.RequiresNonNull)2