Search in sources :

Example 1 with Segment

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

the class HlsMediaPlaylistParserTest method testParseMediaPlaylist.

public void testParseMediaPlaylist() {
    Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
    String playlistString = "#EXTM3U\n" + "#EXT-X-VERSION:3\n" + "#EXT-X-PLAYLIST-TYPE:VOD\n" + "#EXT-X-START:TIME-OFFSET=-25" + "#EXT-X-TARGETDURATION:8\n" + "#EXT-X-MEDIA-SEQUENCE:2679\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:4\n" + "#EXT-X-ALLOW-CACHE:YES\n" + "\n" + "#EXTINF:7.975,\n" + "#EXT-X-BYTERANGE:51370@0\n" + "https://priv.example.com/fileSequence2679.ts\n" + "\n" + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n" + "#EXTINF:7.975,\n" + "#EXT-X-BYTERANGE:51501@2147483648\n" + "https://priv.example.com/fileSequence2680.ts\n" + "\n" + "#EXT-X-KEY:METHOD=NONE\n" + "#EXTINF:7.941,\n" + // @2147535149
    "#EXT-X-BYTERANGE:51501\n" + "https://priv.example.com/fileSequence2681.ts\n" + "\n" + "#EXT-X-DISCONTINUITY\n" + "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n" + "#EXTINF:7.975,\n" + // @2147586650
    "#EXT-X-BYTERANGE:51740\n" + "https://priv.example.com/fileSequence2682.ts\n" + "\n" + "#EXTINF:7.975,\n" + "https://priv.example.com/fileSequence2683.ts\n" + "#EXT-X-ENDLIST";
    InputStream inputStream = new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
    try {
        HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
        assertNotNull(playlist);
        assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
        HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
        assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
        assertEquals(mediaPlaylist.durationUs - 25000000, mediaPlaylist.startOffsetUs);
        assertEquals(2679, mediaPlaylist.mediaSequence);
        assertEquals(3, mediaPlaylist.version);
        assertTrue(mediaPlaylist.hasEndTag);
        List<Segment> segments = mediaPlaylist.segments;
        assertNotNull(segments);
        assertEquals(5, segments.size());
        Segment segment = segments.get(0);
        assertEquals(4, mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence);
        assertEquals(7975000, segment.durationUs);
        assertFalse(segment.isEncrypted);
        assertEquals(null, segment.encryptionKeyUri);
        assertEquals(null, segment.encryptionIV);
        assertEquals(51370, segment.byterangeLength);
        assertEquals(0, segment.byterangeOffset);
        assertEquals("https://priv.example.com/fileSequence2679.ts", segment.url);
        segment = segments.get(1);
        assertEquals(0, segment.relativeDiscontinuitySequence);
        assertEquals(7975000, segment.durationUs);
        assertTrue(segment.isEncrypted);
        assertEquals("https://priv.example.com/key.php?r=2680", segment.encryptionKeyUri);
        assertEquals("0x1566B", segment.encryptionIV);
        assertEquals(51501, segment.byterangeLength);
        assertEquals(2147483648L, segment.byterangeOffset);
        assertEquals("https://priv.example.com/fileSequence2680.ts", segment.url);
        segment = segments.get(2);
        assertEquals(0, segment.relativeDiscontinuitySequence);
        assertEquals(7941000, segment.durationUs);
        assertFalse(segment.isEncrypted);
        assertEquals(null, segment.encryptionKeyUri);
        assertEquals(null, segment.encryptionIV);
        assertEquals(51501, segment.byterangeLength);
        assertEquals(2147535149L, segment.byterangeOffset);
        assertEquals("https://priv.example.com/fileSequence2681.ts", segment.url);
        segment = segments.get(3);
        assertEquals(1, segment.relativeDiscontinuitySequence);
        assertEquals(7975000, segment.durationUs);
        assertTrue(segment.isEncrypted);
        assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
        // 0xA7A == 2682.
        assertNotNull(segment.encryptionIV);
        assertEquals("A7A", segment.encryptionIV.toUpperCase(Locale.getDefault()));
        assertEquals(51740, segment.byterangeLength);
        assertEquals(2147586650L, segment.byterangeOffset);
        assertEquals("https://priv.example.com/fileSequence2682.ts", segment.url);
        segment = segments.get(4);
        assertEquals(1, segment.relativeDiscontinuitySequence);
        assertEquals(7975000, segment.durationUs);
        assertTrue(segment.isEncrypted);
        assertEquals("https://priv.example.com/key.php?r=2682", segment.encryptionKeyUri);
        // 0xA7B == 2683.
        assertNotNull(segment.encryptionIV);
        assertEquals("A7B", segment.encryptionIV.toUpperCase(Locale.getDefault()));
        assertEquals(C.LENGTH_UNSET, segment.byterangeLength);
        assertEquals(0, segment.byterangeOffset);
        assertEquals("https://priv.example.com/fileSequence2683.ts", segment.url);
    } catch (IOException exception) {
        fail(exception.getMessage());
    }
}
Also used : ByteArrayInputStream(java.io.ByteArrayInputStream) ByteArrayInputStream(java.io.ByteArrayInputStream) InputStream(java.io.InputStream) IOException(java.io.IOException) Uri(android.net.Uri) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 2 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#playlist} is set to
   * contain the {@link HlsUrl} that refers to the playlist that needs refreshing.
   *
   * @param previous The most recently loaded media chunk.
   * @param playbackPositionUs The current playback position. If {@code previous} is null then this
   *     parameter is the position from which playback is expected to start (or restart) and hence
   *     should be interpreted as a seek position.
   * @param out A holder to populate.
   */
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
    int oldVariantIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
    // Use start time of the previous chunk rather than its end time because switching format will
    // require downloading overlapping segments.
    long bufferedDurationUs = previous == null ? 0 : Math.max(0, previous.startTimeUs - playbackPositionUs);
    // Select the variant.
    trackSelection.updateSelectedTrack(bufferedDurationUs);
    int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
    boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
    HlsUrl selectedUrl = variants[selectedVariantIndex];
    if (!playlistTracker.isSnapshotValid(selectedUrl)) {
        out.playlist = selectedUrl;
        // Retry when playlist is refreshed.
        return;
    }
    HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
    // Select the chunk.
    int chunkMediaSequence;
    if (previous == null || switchingVariant) {
        long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs;
        if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) {
            // If the playlist is too old to contain the chunk, we need to refresh it.
            chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
        } else {
            chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, targetPositionUs - mediaPlaylist.startTimeUs, true, !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence;
            if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) {
                // We try getting the next chunk without adapting in case that's the reason for falling
                // behind the live window.
                selectedVariantIndex = oldVariantIndex;
                selectedUrl = variants[selectedVariantIndex];
                mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
                chunkMediaSequence = previous.getNextChunkIndex();
            }
        }
    } else {
        chunkMediaSequence = previous.getNextChunkIndex();
    }
    if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
        fatalError = new BehindLiveWindowException();
        return;
    }
    int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
    if (chunkIndex >= mediaPlaylist.segments.size()) {
        if (mediaPlaylist.hasEndTag) {
            out.endOfStream = true;
        } else /* Live */
        {
            out.playlist = selectedUrl;
        }
        return;
    }
    // Handle encryption.
    HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
    // Check if encryption is specified.
    if (segment.isEncrypted) {
        Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
        if (!keyUri.equals(encryptionKeyUri)) {
            // Encryption is specified and the key has changed.
            out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex, trackSelection.getSelectionReason(), trackSelection.getSelectionData());
            return;
        }
        if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
            setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
        }
    } else {
        clearEncryptionData();
    }
    DataSpec initDataSpec = null;
    Segment initSegment = mediaPlaylist.initializationSegment;
    if (initSegment != null) {
        Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
        initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, initSegment.byterangeLength, null);
    }
    // Compute start time of the next chunk.
    long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
    int discontinuitySequence = mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence;
    TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(discontinuitySequence);
    // Configure the data source and spec for the chunk.
    Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
    DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null);
    out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv);
}
Also used : Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) HlsUrl(com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl) BehindLiveWindowException(com.google.android.exoplayer2.source.BehindLiveWindowException) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) TimestampAdjuster(com.google.android.exoplayer2.util.TimestampAdjuster) Uri(android.net.Uri) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 3 with Segment

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

the class HlsMediaChunk method loadMedia.

private void loadMedia() throws IOException, InterruptedException {
    // 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 (isEncrypted) {
        loadDataSpec = dataSpec;
        skipLoadedBytes = bytesLoaded != 0;
    } else {
        loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
        skipLoadedBytes = false;
    }
    if (!isMasterTimestampSource) {
        timestampAdjuster.waitUntilInitialized();
    } else if (timestampAdjuster.getFirstSampleTimestampUs() == TimestampAdjuster.DO_NOT_OFFSET) {
        // We're the master and we haven't set the desired first sample timestamp yet.
        timestampAdjuster.setFirstSampleTimestampUs(startTimeUs);
    }
    try {
        ExtractorInput input = new DefaultExtractorInput(dataSource, loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
        if (extractor == null) {
            // Media segment format is packed audio.
            long id3Timestamp = peekId3PrivTimestamp(input);
            extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET ? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
        }
        if (skipLoadedBytes) {
            input.skipFully(bytesLoaded);
        }
        try {
            int result = Extractor.RESULT_CONTINUE;
            while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
                result = extractor.read(input, null);
            }
        } finally {
            bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
        }
    } finally {
        Util.closeQuietly(dataSource);
    }
    loadCompleted = true;
}
Also used : ExtractorInput(com.google.android.exoplayer2.extractor.ExtractorInput) DefaultExtractorInput(com.google.android.exoplayer2.extractor.DefaultExtractorInput) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) DefaultExtractorInput(com.google.android.exoplayer2.extractor.DefaultExtractorInput)

Example 4 with Segment

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

the class HlsPlaylistTracker method getLoadedPlaylistStartTimeUs.

private long getLoadedPlaylistStartTimeUs(HlsMediaPlaylist oldPlaylist, HlsMediaPlaylist loadedPlaylist) {
    if (loadedPlaylist.hasProgramDateTime) {
        return loadedPlaylist.startTimeUs;
    }
    long primarySnapshotStartTimeUs = primaryUrlSnapshot != null ? primaryUrlSnapshot.startTimeUs : 0;
    if (oldPlaylist == null) {
        return primarySnapshotStartTimeUs;
    }
    int oldPlaylistSize = oldPlaylist.segments.size();
    Segment firstOldOverlappingSegment = getFirstOldOverlappingSegment(oldPlaylist, loadedPlaylist);
    if (firstOldOverlappingSegment != null) {
        return oldPlaylist.startTimeUs + firstOldOverlappingSegment.relativeStartTimeUs;
    } else if (oldPlaylistSize == loadedPlaylist.mediaSequence - oldPlaylist.mediaSequence) {
        return oldPlaylist.getEndTimeUs();
    } else {
        // No segments overlap, we assume the new playlist start coincides with the primary playlist.
        return primarySnapshotStartTimeUs;
    }
}
Also used : Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 5 with Segment

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

the class DefaultDashChunkSource method getNextChunk.

@Override
public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
    if (fatalError != null) {
        return;
    }
    long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
    trackSelection.updateSelectedTrack(bufferedDurationUs);
    RepresentationHolder representationHolder = representationHolders[trackSelection.getSelectedIndex()];
    Representation selectedRepresentation = representationHolder.representation;
    DashSegmentIndex segmentIndex = representationHolder.segmentIndex;
    RangedUri pendingInitializationUri = null;
    RangedUri pendingIndexUri = null;
    if (representationHolder.extractorWrapper.getSampleFormats() == null) {
        pendingInitializationUri = selectedRepresentation.getInitializationUri();
    }
    if (segmentIndex == null) {
        pendingIndexUri = selectedRepresentation.getIndexUri();
    }
    if (pendingInitializationUri != null || pendingIndexUri != null) {
        // We have initialization and/or index requests to make.
        out.chunk = newInitializationChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri);
        return;
    }
    long nowUs = getNowUnixTimeUs();
    int availableSegmentCount = representationHolder.getSegmentCount();
    if (availableSegmentCount == 0) {
        // The index doesn't define any segments.
        out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
        return;
    }
    int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum();
    int lastAvailableSegmentNum;
    if (availableSegmentCount == DashSegmentIndex.INDEX_UNBOUNDED) {
        // The index is itself unbounded. We need to use the current time to calculate the range of
        // available segments.
        long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000;
        long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000;
        long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs;
        if (manifest.timeShiftBufferDepth != C.TIME_UNSET) {
            long bufferDepthUs = manifest.timeShiftBufferDepth * 1000;
            firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs));
        }
        // getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
        // index of the last completed segment.
        lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1;
    } else {
        lastAvailableSegmentNum = firstAvailableSegmentNum + availableSegmentCount - 1;
    }
    int segmentNum;
    if (previous == null) {
        segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs), firstAvailableSegmentNum, lastAvailableSegmentNum);
    } else {
        segmentNum = previous.getNextChunkIndex();
        if (segmentNum < firstAvailableSegmentNum) {
            // This is before the first chunk in the current manifest.
            fatalError = new BehindLiveWindowException();
            return;
        }
    }
    if (segmentNum > lastAvailableSegmentNum || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) {
        // This is beyond the last chunk in the current manifest.
        out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1);
        return;
    }
    int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1);
    out.chunk = newMediaChunk(representationHolder, dataSource, trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segmentNum, maxSegmentCount);
}
Also used : BehindLiveWindowException(com.google.android.exoplayer2.source.BehindLiveWindowException) RangedUri(com.google.android.exoplayer2.source.dash.manifest.RangedUri) Representation(com.google.android.exoplayer2.source.dash.manifest.Representation)

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