Search in sources :

Example 21 with HlsMediaPlaylist

use of androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist in project media by androidx.

the class HlsChunkSource method getAdjustedSeekPositionUs.

/**
 * Adjusts a seek position given the specified {@link SeekParameters}.
 *
 * @param positionUs The seek position in microseconds.
 * @param seekParameters Parameters that control how the seek is performed.
 * @return The adjusted seek position, in microseconds.
 */
public long getAdjustedSeekPositionUs(long positionUs, SeekParameters seekParameters) {
    int selectedIndex = trackSelection.getSelectedIndex();
    @Nullable HlsMediaPlaylist mediaPlaylist = selectedIndex < playlistUrls.length && selectedIndex != C.INDEX_UNSET ? playlistTracker.getPlaylistSnapshot(playlistUrls[trackSelection.getSelectedIndexInTrackGroup()], /* isForPlayback= */
    true) : null;
    if (mediaPlaylist == null || mediaPlaylist.segments.isEmpty() || !mediaPlaylist.hasIndependentSegments) {
        return positionUs;
    }
    // Segments start with sync samples (i.e., EXT-X-INDEPENDENT-SEGMENTS is set) and the playlist
    // is non-empty, so we can use segment start times as sync points. Note that in the rare case
    // that (a) an adaptive quality switch occurs between the adjustment and the seek being
    // performed, and (b) segment start times are not aligned across variants, it's possible that
    // the adjusted position may not be at a sync point when it was intended to be. However, this is
    // very much an edge case, and getting it wrong is worth it for getting the vast majority of
    // cases right whilst keeping the implementation relatively simple.
    long startOfPlaylistInPeriodUs = mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
    long relativePositionUs = positionUs - startOfPlaylistInPeriodUs;
    int segmentIndex = Util.binarySearchFloor(mediaPlaylist.segments, relativePositionUs, /* inclusive= */
    true, /* stayInBounds= */
    true);
    long firstSyncUs = mediaPlaylist.segments.get(segmentIndex).relativeStartTimeUs;
    long secondSyncUs = firstSyncUs;
    if (segmentIndex != mediaPlaylist.segments.size() - 1) {
        secondSyncUs = mediaPlaylist.segments.get(segmentIndex + 1).relativeStartTimeUs;
    }
    return seekParameters.resolveSeekPositionUs(relativePositionUs, firstSyncUs, secondSyncUs) + startOfPlaylistInPeriodUs;
}
Also used : HlsMediaPlaylist(androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist) Nullable(androidx.annotation.Nullable)

Example 22 with HlsMediaPlaylist

use of androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist in project media by androidx.

the class HlsChunkSource method getNextSegmentHolder.

@Nullable
private static SegmentBaseHolder getNextSegmentHolder(HlsMediaPlaylist mediaPlaylist, long nextMediaSequence, int nextPartIndex) {
    int segmentIndexInPlaylist = (int) (nextMediaSequence - mediaPlaylist.mediaSequence);
    if (segmentIndexInPlaylist == mediaPlaylist.segments.size()) {
        int index = nextPartIndex != C.INDEX_UNSET ? nextPartIndex : 0;
        return index < mediaPlaylist.trailingParts.size() ? new SegmentBaseHolder(mediaPlaylist.trailingParts.get(index), nextMediaSequence, index) : null;
    }
    Segment mediaSegment = mediaPlaylist.segments.get(segmentIndexInPlaylist);
    if (nextPartIndex == C.INDEX_UNSET) {
        return new SegmentBaseHolder(mediaSegment, nextMediaSequence, /* partIndex= */
        C.INDEX_UNSET);
    }
    if (nextPartIndex < mediaSegment.parts.size()) {
        // The requested part is available in the requested segment.
        return new SegmentBaseHolder(mediaSegment.parts.get(nextPartIndex), nextMediaSequence, nextPartIndex);
    } else if (segmentIndexInPlaylist + 1 < mediaPlaylist.segments.size()) {
        // The first part of the next segment is requested, but we can use the next full segment.
        return new SegmentBaseHolder(mediaPlaylist.segments.get(segmentIndexInPlaylist + 1), nextMediaSequence + 1, /* partIndex= */
        C.INDEX_UNSET);
    } else if (!mediaPlaylist.trailingParts.isEmpty()) {
        // The part index is rolling over to the first trailing part.
        return new SegmentBaseHolder(mediaPlaylist.trailingParts.get(0), nextMediaSequence + 1, /* partIndex= */
        0);
    }
    // End of stream.
    return null;
}
Also used : Segment(androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment) Nullable(androidx.annotation.Nullable)

Example 23 with HlsMediaPlaylist

use of androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist in project media by androidx.

the class HlsChunkSource method getNextChunk.

/**
 * Returns the next chunk to load.
 *
 * <p>If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream
 * has been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available
 * but the end of the stream has not been reached, {@link HlsChunkHolder#playlistUrl} is set to
 * contain the {@link Uri} that refers to the playlist that needs refreshing.
 *
 * @param playbackPositionUs The current playback position relative to the period start in
 *     microseconds. If playback of the period to which this chunk source belongs has not yet
 *     started, the value will be the starting position in the period minus the duration of any
 *     media in previous periods still to be played.
 * @param loadPositionUs The current load position relative to the period start in microseconds.
 * @param queue The queue of buffered {@link HlsMediaChunk}s.
 * @param allowEndOfStream Whether {@link HlsChunkHolder#endOfStream} is allowed to be set for
 *     non-empty media playlists. If {@code false}, the last available chunk is returned instead.
 *     If the media playlist is empty, {@link HlsChunkHolder#endOfStream} is always set.
 * @param out A holder to populate.
 */
public void getNextChunk(long playbackPositionUs, long loadPositionUs, List<HlsMediaChunk> queue, boolean allowEndOfStream, HlsChunkHolder out) {
    @Nullable HlsMediaChunk previous = queue.isEmpty() ? null : Iterables.getLast(queue);
    int oldTrackIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
    long bufferedDurationUs = loadPositionUs - playbackPositionUs;
    long timeToLiveEdgeUs = resolveTimeToLiveEdgeUs(playbackPositionUs);
    if (previous != null && !independentSegments) {
        // Unless segments are known to be independent, switching tracks requires downloading
        // overlapping segments. Hence we subtract the previous segment's duration from the buffered
        // duration.
        // This may affect the live-streaming adaptive track selection logic, when we compare the
        // buffered duration to time-to-live-edge to decide whether to switch. Therefore, we subtract
        // the duration of the last loaded segment from timeToLiveEdgeUs as well.
        long subtractedDurationUs = previous.getDurationUs();
        bufferedDurationUs = max(0, bufferedDurationUs - subtractedDurationUs);
        if (timeToLiveEdgeUs != C.TIME_UNSET) {
            timeToLiveEdgeUs = max(0, timeToLiveEdgeUs - subtractedDurationUs);
        }
    }
    // Select the track.
    MediaChunkIterator[] mediaChunkIterators = createMediaChunkIterators(previous, loadPositionUs);
    trackSelection.updateSelectedTrack(playbackPositionUs, bufferedDurationUs, timeToLiveEdgeUs, queue, mediaChunkIterators);
    int selectedTrackIndex = trackSelection.getSelectedIndexInTrackGroup();
    boolean switchingTrack = oldTrackIndex != selectedTrackIndex;
    Uri selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
    if (!playlistTracker.isSnapshotValid(selectedPlaylistUrl)) {
        out.playlistUrl = selectedPlaylistUrl;
        seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
        expectedPlaylistUrl = selectedPlaylistUrl;
        // Retry when playlist is refreshed.
        return;
    }
    @Nullable HlsMediaPlaylist playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
    true);
    // playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
    checkNotNull(playlist);
    independentSegments = playlist.hasIndependentSegments;
    updateLiveEdgeTimeUs(playlist);
    // Select the chunk.
    long startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
    Pair<Long, Integer> nextMediaSequenceAndPartIndex = getNextMediaSequenceAndPartIndex(previous, switchingTrack, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
    long chunkMediaSequence = nextMediaSequenceAndPartIndex.first;
    int partIndex = nextMediaSequenceAndPartIndex.second;
    if (chunkMediaSequence < playlist.mediaSequence && previous != null && switchingTrack) {
        // We try getting the next chunk without adapting in case that's the reason for falling
        // behind the live window.
        selectedTrackIndex = oldTrackIndex;
        selectedPlaylistUrl = playlistUrls[selectedTrackIndex];
        playlist = playlistTracker.getPlaylistSnapshot(selectedPlaylistUrl, /* isForPlayback= */
        true);
        // playlistTracker snapshot is valid (checked by if() above), so playlist must be non-null.
        checkNotNull(playlist);
        startOfPlaylistInPeriodUs = playlist.startTimeUs - playlistTracker.getInitialStartTimeUs();
        // Get the next segment/part without switching tracks.
        Pair<Long, Integer> nextMediaSequenceAndPartIndexWithoutAdapting = getNextMediaSequenceAndPartIndex(previous, /* switchingTrack= */
        false, playlist, startOfPlaylistInPeriodUs, loadPositionUs);
        chunkMediaSequence = nextMediaSequenceAndPartIndexWithoutAdapting.first;
        partIndex = nextMediaSequenceAndPartIndexWithoutAdapting.second;
    }
    if (chunkMediaSequence < playlist.mediaSequence) {
        fatalError = new BehindLiveWindowException();
        return;
    }
    @Nullable SegmentBaseHolder segmentBaseHolder = getNextSegmentHolder(playlist, chunkMediaSequence, partIndex);
    if (segmentBaseHolder == null) {
        if (!playlist.hasEndTag) {
            // Reload the playlist in case of a live stream.
            out.playlistUrl = selectedPlaylistUrl;
            seenExpectedPlaylistError &= selectedPlaylistUrl.equals(expectedPlaylistUrl);
            expectedPlaylistUrl = selectedPlaylistUrl;
            return;
        } else if (allowEndOfStream || playlist.segments.isEmpty()) {
            out.endOfStream = true;
            return;
        }
        // Use the last segment available in case of a VOD stream.
        segmentBaseHolder = new SegmentBaseHolder(Iterables.getLast(playlist.segments), playlist.mediaSequence + playlist.segments.size() - 1, /* partIndex= */
        C.INDEX_UNSET);
    }
    // We have a valid media segment, we can discard any playlist errors at this point.
    seenExpectedPlaylistError = false;
    expectedPlaylistUrl = null;
    // Check if the media segment or its initialization segment are fully encrypted.
    @Nullable Uri initSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase.initializationSegment);
    out.chunk = maybeCreateEncryptionChunkFor(initSegmentKeyUri, selectedTrackIndex);
    if (out.chunk != null) {
        return;
    }
    @Nullable Uri mediaSegmentKeyUri = getFullEncryptionKeyUri(playlist, segmentBaseHolder.segmentBase);
    out.chunk = maybeCreateEncryptionChunkFor(mediaSegmentKeyUri, selectedTrackIndex);
    if (out.chunk != null) {
        return;
    }
    boolean shouldSpliceIn = HlsMediaChunk.shouldSpliceIn(previous, selectedPlaylistUrl, playlist, segmentBaseHolder, startOfPlaylistInPeriodUs);
    if (shouldSpliceIn && segmentBaseHolder.isPreload) {
        // becomes fully available (or the track selection selects another track).
        return;
    }
    out.chunk = HlsMediaChunk.createInstance(extractorFactory, mediaDataSource, playlistFormats[selectedTrackIndex], startOfPlaylistInPeriodUs, playlist, segmentBaseHolder, selectedPlaylistUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), isTimestampMaster, timestampAdjusterProvider, previous, /* mediaSegmentKey= */
    keyCache.get(mediaSegmentKeyUri), /* initSegmentKey= */
    keyCache.get(initSegmentKeyUri), shouldSpliceIn, playerId);
}
Also used : Uri(android.net.Uri) BaseMediaChunkIterator(androidx.media3.exoplayer.source.chunk.BaseMediaChunkIterator) MediaChunkIterator(androidx.media3.exoplayer.source.chunk.MediaChunkIterator) BehindLiveWindowException(androidx.media3.exoplayer.source.BehindLiveWindowException) HlsMediaPlaylist(androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist) Nullable(androidx.annotation.Nullable)

Example 24 with HlsMediaPlaylist

use of androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist in project media by androidx.

the class HlsChunkSourceTest method setup.

@Before
public void setup() throws IOException {
    mockPlaylistTracker = Mockito.mock(HlsPlaylistTracker.class);
    InputStream inputStream = TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), PLAYLIST_INDEPENDENT_SEGMENTS);
    HlsMediaPlaylist playlist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(PLAYLIST_URI, inputStream);
    when(mockPlaylistTracker.getPlaylistSnapshot(eq(PLAYLIST_URI), anyBoolean())).thenReturn(playlist);
    testChunkSource = new HlsChunkSource(HlsExtractorFactory.DEFAULT, mockPlaylistTracker, new Uri[] { IFRAME_URI, PLAYLIST_URI }, new Format[] { IFRAME_FORMAT, ExoPlayerTestRunner.VIDEO_FORMAT }, new DefaultHlsDataSourceFactory(new FakeDataSource.Factory()), /* mediaTransferListener= */
    null, new TimestampAdjusterProvider(), /* muxedCaptionFormats= */
    null, PlayerId.UNSET);
    when(mockPlaylistTracker.isSnapshotValid(eq(PLAYLIST_URI))).thenReturn(true);
    // Mock that segments totalling PLAYLIST_START_PERIOD_OFFSET_US in duration have been removed
    // from the start of the playlist.
    when(mockPlaylistTracker.getInitialStartTimeUs()).thenReturn(playlist.startTimeUs - PLAYLIST_START_PERIOD_OFFSET_US);
}
Also used : Format(androidx.media3.common.Format) FakeDataSource(androidx.media3.test.utils.FakeDataSource) HlsPlaylistTracker(androidx.media3.exoplayer.hls.playlist.HlsPlaylistTracker) InputStream(java.io.InputStream) HlsPlaylistParser(androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser) HlsMediaPlaylist(androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist) Uri(android.net.Uri) Before(org.junit.Before)

Example 25 with HlsMediaPlaylist

use of androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist in project media by androidx.

the class HlsChunkSourceTest method getAdjustedSeekPositionUs_emptyPlaylist.

@Test
public void getAdjustedSeekPositionUs_emptyPlaylist() throws IOException {
    InputStream inputStream = TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), PLAYLIST_EMPTY);
    HlsMediaPlaylist playlist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(PLAYLIST_URI, inputStream);
    when(mockPlaylistTracker.getPlaylistSnapshot(eq(PLAYLIST_URI), anyBoolean())).thenReturn(playlist);
    long adjustedPositionUs = testChunkSource.getAdjustedSeekPositionUs(playlistTimeToPeriodTimeUs(100_000_000), SeekParameters.EXACT);
    assertThat(periodTimeToPlaylistTimeUs(adjustedPositionUs)).isEqualTo(100_000_000);
}
Also used : InputStream(java.io.InputStream) HlsPlaylistParser(androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser) HlsMediaPlaylist(androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist) Test(org.junit.Test)

Aggregations

HlsMediaPlaylist (androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist)19 Test (org.junit.Test)18 Uri (android.net.Uri)16 Segment (androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist.Segment)13 InputStream (java.io.InputStream)10 ArrayList (java.util.ArrayList)10 Nullable (androidx.annotation.Nullable)9 DataSpec (androidx.media3.datasource.DataSpec)8 ByteArrayInputStream (java.io.ByteArrayInputStream)8 HlsPlaylistParser (androidx.media3.exoplayer.hls.playlist.HlsPlaylistParser)4 BaseMediaChunkIterator (androidx.media3.exoplayer.source.chunk.BaseMediaChunkIterator)2 MediaChunkIterator (androidx.media3.exoplayer.source.chunk.MediaChunkIterator)2 FakeDataSource (androidx.media3.test.utils.FakeDataSource)2 IOException (java.io.IOException)2 SystemClock (android.os.SystemClock)1 Pair (android.util.Pair)1 VisibleForTesting (androidx.annotation.VisibleForTesting)1 C (androidx.media3.common.C)1 DrmInitData (androidx.media3.common.DrmInitData)1 SchemeData (androidx.media3.common.DrmInitData.SchemeData)1