Search in sources :

Example 21 with Segment

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

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(com.google.android.exoplayer2.source.chunk.BaseMediaChunkIterator) MediaChunkIterator(com.google.android.exoplayer2.source.chunk.MediaChunkIterator) BehindLiveWindowException(com.google.android.exoplayer2.source.BehindLiveWindowException) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Nullable(androidx.annotation.Nullable)

Example 22 with Segment

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

the class HlsMediaChunk method createInstance.

/**
 * Creates a new instance.
 *
 * @param extractorFactory A {@link HlsExtractorFactory} from which the {@link
 *     HlsMediaChunkExtractor} is obtained.
 * @param dataSource The source from which the data should be loaded.
 * @param format The chunk format.
 * @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
 * @param mediaPlaylist The media playlist from which this chunk was obtained.
 * @param segmentBaseHolder The segment holder.
 * @param playlistUrl The url of the playlist from which this chunk was obtained.
 * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
 *     information is available in the multivariant playlist.
 * @param trackSelectionReason See {@link #trackSelectionReason}.
 * @param trackSelectionData See {@link #trackSelectionData}.
 * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
 * @param timestampAdjusterProvider The provider from which to obtain the {@link
 *     TimestampAdjuster}.
 * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
 * @param mediaSegmentKey The media segment decryption key, if fully encrypted. Null otherwise.
 * @param initSegmentKey The initialization segment decryption key, if fully encrypted. Null
 *     otherwise.
 * @param shouldSpliceIn Whether samples for this chunk should be spliced into existing samples.
 */
public static HlsMediaChunk createInstance(HlsExtractorFactory extractorFactory, DataSource dataSource, Format format, long startOfPlaylistInPeriodUs, HlsMediaPlaylist mediaPlaylist, HlsChunkSource.SegmentBaseHolder segmentBaseHolder, Uri playlistUrl, @Nullable List<Format> muxedCaptionFormats, @C.SelectionReason int trackSelectionReason, @Nullable Object trackSelectionData, boolean isMasterTimestampSource, TimestampAdjusterProvider timestampAdjusterProvider, @Nullable HlsMediaChunk previousChunk, @Nullable byte[] mediaSegmentKey, @Nullable byte[] initSegmentKey, boolean shouldSpliceIn, PlayerId playerId) {
    // Media segment.
    HlsMediaPlaylist.SegmentBase mediaSegment = segmentBaseHolder.segmentBase;
    DataSpec dataSpec = new DataSpec.Builder().setUri(UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url)).setPosition(mediaSegment.byteRangeOffset).setLength(mediaSegment.byteRangeLength).setFlags(segmentBaseHolder.isPreload ? FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED : 0).build();
    boolean mediaSegmentEncrypted = mediaSegmentKey != null;
    @Nullable byte[] mediaSegmentIv = mediaSegmentEncrypted ? getEncryptionIvArray(Assertions.checkNotNull(mediaSegment.encryptionIV)) : null;
    DataSource mediaDataSource = buildDataSource(dataSource, mediaSegmentKey, mediaSegmentIv);
    // Init segment.
    HlsMediaPlaylist.Segment initSegment = mediaSegment.initializationSegment;
    DataSpec initDataSpec = null;
    boolean initSegmentEncrypted = false;
    @Nullable DataSource initDataSource = null;
    if (initSegment != null) {
        initSegmentEncrypted = initSegmentKey != null;
        @Nullable byte[] initSegmentIv = initSegmentEncrypted ? getEncryptionIvArray(Assertions.checkNotNull(initSegment.encryptionIV)) : null;
        Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
        initDataSpec = new DataSpec(initSegmentUri, initSegment.byteRangeOffset, initSegment.byteRangeLength);
        initDataSource = buildDataSource(dataSource, initSegmentKey, initSegmentIv);
    }
    long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + mediaSegment.relativeStartTimeUs;
    long segmentEndTimeInPeriodUs = segmentStartTimeInPeriodUs + mediaSegment.durationUs;
    int discontinuitySequenceNumber = mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
    @Nullable HlsMediaChunkExtractor previousExtractor = null;
    Id3Decoder id3Decoder;
    ParsableByteArray scratchId3Data;
    if (previousChunk != null) {
        boolean isSameInitData = initDataSpec == previousChunk.initDataSpec || (initDataSpec != null && previousChunk.initDataSpec != null && initDataSpec.uri.equals(previousChunk.initDataSpec.uri) && initDataSpec.position == previousChunk.initDataSpec.position);
        boolean isFollowingChunk = playlistUrl.equals(previousChunk.playlistUrl) && previousChunk.loadCompleted;
        id3Decoder = previousChunk.id3Decoder;
        scratchId3Data = previousChunk.scratchId3Data;
        previousExtractor = isSameInitData && isFollowingChunk && !previousChunk.extractorInvalidated && previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber ? previousChunk.extractor : null;
    } else {
        id3Decoder = new Id3Decoder();
        scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
    }
    return new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, format, mediaSegmentEncrypted, initDataSource, initDataSpec, initSegmentEncrypted, playlistUrl, muxedCaptionFormats, trackSelectionReason, trackSelectionData, segmentStartTimeInPeriodUs, segmentEndTimeInPeriodUs, segmentBaseHolder.mediaSequence, segmentBaseHolder.partIndex, /* isPublished= */
    !segmentBaseHolder.isPreload, discontinuitySequenceNumber, mediaSegment.hasGapTag, isMasterTimestampSource, /* timestampAdjuster= */
    timestampAdjusterProvider.getAdjuster(discontinuitySequenceNumber), mediaSegment.drmInitData, previousExtractor, id3Decoder, scratchId3Data, shouldSpliceIn, playerId);
}
Also used : Uri(android.net.Uri) DataSource(com.google.android.exoplayer2.upstream.DataSource) ParsableByteArray(com.google.android.exoplayer2.util.ParsableByteArray) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Id3Decoder(com.google.android.exoplayer2.metadata.id3.Id3Decoder) Nullable(androidx.annotation.Nullable)

Example 23 with Segment

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

the class HlsMediaPlaylistParserTest method encryptedMapTagWithNoIvFails.

@Test
public void encryptedMapTagWithNoIvFails() throws IOException {
    Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
    String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-TARGETDURATION:5\n" + "#EXT-X-MEDIA-SEQUENCE:10\n" + "#EXT-X-KEY:METHOD=AES-128," + "URI=\"https://priv.example.com/key.php?r=2680\"\n" + "#EXT-X-MAP:URI=\"init1.ts\"" + "#EXTINF:5.005,\n" + "02/00/32.ts\n";
    InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
    try {
        new HlsPlaylistParser().parse(playlistUri, inputStream);
        fail();
    } catch (ParserException e) {
    // Expected because the initialization segment does not have a defined initialization vector,
    // although it is affected by an EXT-X-KEY tag.
    }
}
Also used : ParserException(com.google.android.exoplayer2.ParserException) ByteArrayInputStream(java.io.ByteArrayInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) InputStream(java.io.InputStream) Uri(android.net.Uri) Test(org.junit.Test)

Example 24 with Segment

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

the class HlsMediaPlaylistParserTest method parseMediaPlaylist_withByteRanges.

@Test
public void parseMediaPlaylist_withByteRanges() throws Exception {
    Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
    String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-TARGETDURATION:5\n" + "\n" + "#EXT-X-BYTERANGE:200@100\n" + "#EXT-X-MAP:URI=\"stream.mp4\"\n" + "#EXTINF:5,\n" + "#EXT-X-BYTERANGE:400\n" + "stream.mp4\n" + "#EXTINF:5,\n" + "#EXT-X-BYTERANGE:500\n" + "stream.mp4\n" + "#EXT-X-DISCONTINUITY\n" + "#EXT-X-MAP:URI=\"init.mp4\"\n" + "#EXTINF:5,\n" + "segment.mp4\n";
    InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
    HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
    HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
    List<Segment> segments = mediaPlaylist.segments;
    assertThat(segments).isNotNull();
    assertThat(segments).hasSize(3);
    Segment segment = segments.get(0);
    assertThat(segment.initializationSegment.byteRangeOffset).isEqualTo(100);
    assertThat(segment.initializationSegment.byteRangeLength).isEqualTo(200);
    assertThat(segment.byteRangeOffset).isEqualTo(300);
    assertThat(segment.byteRangeLength).isEqualTo(400);
    segment = segments.get(1);
    assertThat(segment.byteRangeOffset).isEqualTo(700);
    assertThat(segment.byteRangeLength).isEqualTo(500);
    segment = segments.get(2);
    assertThat(segment.initializationSegment.byteRangeOffset).isEqualTo(0);
    assertThat(segment.initializationSegment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
    assertThat(segment.byteRangeOffset).isEqualTo(0);
    assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
}
Also used : ByteArrayInputStream(java.io.ByteArrayInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) InputStream(java.io.InputStream) Uri(android.net.Uri) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) Test(org.junit.Test)

Example 25 with Segment

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

the class HlsMediaPlaylistParserTest method iframeOnly_withExplicitInitSegment_hasCorrectByteRange.

@Test
public void iframeOnly_withExplicitInitSegment_hasCorrectByteRange() throws IOException {
    Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
    String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:6\n" + "#EXT-X-MEDIA-SEQUENCE:1616630672\n" + "#EXT-X-TARGETDURATION:7\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:491 \n" + "#EXT-X-MAP:URI=\"iframe0.tsv\",BYTERANGE=\"564@0\"\n" + "\n" + "#EXT-X-I-FRAMES-ONLY\n" + "#EXT-X-PROGRAM-DATE-TIME:2021-04-12T17:08:22.000Z\n" + "#EXTINF:1.001000,\n" + "#EXT-X-BYTERANGE:121260@1128\n" + "iframe0.tsv";
    InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
    HlsMediaPlaylist standalonePlaylist = (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
    @Nullable Segment initSegment = standalonePlaylist.segments.get(0).initializationSegment;
    assertThat(standalonePlaylist.segments).hasSize(1);
    assertThat(initSegment.byteRangeLength).isEqualTo(564);
    assertThat(initSegment.byteRangeOffset).isEqualTo(0);
}
Also used : ByteArrayInputStream(java.io.ByteArrayInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) InputStream(java.io.InputStream) Uri(android.net.Uri) Nullable(androidx.annotation.Nullable) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) Test(org.junit.Test)

Aggregations

Test (org.junit.Test)20 Uri (android.net.Uri)18 Segment (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)18 Nullable (androidx.annotation.Nullable)16 DataSpec (com.google.android.exoplayer2.upstream.DataSpec)11 ByteArrayInputStream (java.io.ByteArrayInputStream)9 IOException (java.io.IOException)9 InputStream (java.io.InputStream)9 ArrayList (java.util.ArrayList)9 HlsMediaPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist)8 Format (com.google.android.exoplayer2.Format)6 SlowMotionData (com.google.android.exoplayer2.metadata.mp4.SlowMotionData)5 SingleSampleMediaChunk (com.google.android.exoplayer2.source.chunk.SingleSampleMediaChunk)5 RangedUri (com.google.android.exoplayer2.source.dash.manifest.RangedUri)5 Representation (com.google.android.exoplayer2.source.dash.manifest.Representation)5 BehindLiveWindowException (com.google.android.exoplayer2.source.BehindLiveWindowException)4 ContainerMediaChunk (com.google.android.exoplayer2.source.chunk.ContainerMediaChunk)4 Segment (com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment)4 Window (com.google.android.exoplayer2.Timeline.Window)3 Segment (com.google.android.exoplayer2.metadata.mp4.SlowMotionData.Segment)3