Search in sources :

Example 11 with Part

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

the class HlsSampleStreamWrapper method selectTracks.

/**
 * Called by the parent {@link HlsMediaPeriod} when a track selection occurs.
 *
 * @param selections The renderer track selections.
 * @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained
 *     for each selection. A {@code true} value indicates that the selection is unchanged, and
 *     that the caller does not require that the sample stream be recreated.
 * @param streams The existing sample streams, which will be updated to reflect the provided
 *     selections.
 * @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that
 *     have been retained but with the requirement that the consuming renderer be reset.
 * @param positionUs The current playback position in microseconds.
 * @param forceReset If true then a reset is forced (i.e. a seek will be performed with in-buffer
 *     seeking disabled).
 * @return Whether this wrapper requires the parent {@link HlsMediaPeriod} to perform a seek as
 *     part of the track selection.
 */
public boolean selectTracks(@NullableType ExoTrackSelection[] selections, boolean[] mayRetainStreamFlags, @NullableType SampleStream[] streams, boolean[] streamResetFlags, long positionUs, boolean forceReset) {
    assertIsPrepared();
    int oldEnabledTrackGroupCount = enabledTrackGroupCount;
    // Deselect old tracks.
    for (int i = 0; i < selections.length; i++) {
        HlsSampleStream stream = (HlsSampleStream) streams[i];
        if (stream != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
            enabledTrackGroupCount--;
            stream.unbindSampleQueue();
            streams[i] = null;
        }
    }
    // We'll always need to seek if we're being forced to reset, or if this is a first selection to
    // a position other than the one we started preparing with, or if we're making a selection
    // having previously disabled all tracks.
    boolean seekRequired = forceReset || (seenFirstTrackSelection ? oldEnabledTrackGroupCount == 0 : positionUs != lastSeekPositionUs);
    // Get the old (i.e. current before the loop below executes) primary track selection. The new
    // primary selection will equal the old one unless it's changed in the loop.
    ExoTrackSelection oldPrimaryTrackSelection = chunkSource.getTrackSelection();
    ExoTrackSelection primaryTrackSelection = oldPrimaryTrackSelection;
    // Select new tracks.
    for (int i = 0; i < selections.length; i++) {
        ExoTrackSelection selection = selections[i];
        if (selection == null) {
            continue;
        }
        int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup());
        if (trackGroupIndex == primaryTrackGroupIndex) {
            primaryTrackSelection = selection;
            chunkSource.setTrackSelection(selection);
        }
        if (streams[i] == null) {
            enabledTrackGroupCount++;
            streams[i] = new HlsSampleStream(this, trackGroupIndex);
            streamResetFlags[i] = true;
            if (trackGroupToSampleQueueIndex != null) {
                ((HlsSampleStream) streams[i]).bindSampleQueue();
                // If there's still a chance of avoiding a seek, try and seek within the sample queue.
                if (!seekRequired) {
                    SampleQueue sampleQueue = sampleQueues[trackGroupToSampleQueueIndex[trackGroupIndex]];
                    // A seek can be avoided if we're able to seek to the current playback position in
                    // the sample queue, or if we haven't read anything from the queue since the previous
                    // seek (this case is common for sparse tracks such as metadata tracks). In all other
                    // cases a seek is required.
                    seekRequired = !sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */
                    true) && sampleQueue.getReadIndex() != 0;
                }
            }
        }
    }
    if (enabledTrackGroupCount == 0) {
        chunkSource.reset();
        downstreamTrackFormat = null;
        pendingResetUpstreamFormats = true;
        mediaChunks.clear();
        if (loader.isLoading()) {
            if (sampleQueuesBuilt) {
                // Discard as much as we can synchronously.
                for (SampleQueue sampleQueue : sampleQueues) {
                    sampleQueue.discardToEnd();
                }
            }
            loader.cancelLoading();
        } else {
            resetSampleQueues();
        }
    } else {
        if (!mediaChunks.isEmpty() && !Util.areEqual(primaryTrackSelection, oldPrimaryTrackSelection)) {
            // The primary track selection has changed and we have buffered media. The buffered media
            // may need to be discarded.
            boolean primarySampleQueueDirty = false;
            if (!seenFirstTrackSelection) {
                long bufferedDurationUs = positionUs < 0 ? -positionUs : 0;
                HlsMediaChunk lastMediaChunk = getLastMediaChunk();
                MediaChunkIterator[] mediaChunkIterators = chunkSource.createMediaChunkIterators(lastMediaChunk, positionUs);
                primaryTrackSelection.updateSelectedTrack(positionUs, bufferedDurationUs, C.TIME_UNSET, readOnlyMediaChunks, mediaChunkIterators);
                int chunkIndex = chunkSource.getTrackGroup().indexOf(lastMediaChunk.trackFormat);
                if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
                    // This is the first selection and the chunk loaded during preparation does not match
                    // the initially selected format.
                    primarySampleQueueDirty = true;
                }
            } else {
                // The primary sample queue contains media buffered for the old primary track selection.
                primarySampleQueueDirty = true;
            }
            if (primarySampleQueueDirty) {
                forceReset = true;
                seekRequired = true;
                pendingResetUpstreamFormats = true;
            }
        }
        if (seekRequired) {
            seekToUs(positionUs, forceReset);
            // We'll need to reset renderers consuming from all streams due to the seek.
            for (int i = 0; i < streams.length; i++) {
                if (streams[i] != null) {
                    streamResetFlags[i] = true;
                }
            }
        }
    }
    updateSampleStreams(streams);
    seenFirstTrackSelection = true;
    return seekRequired;
}
Also used : SampleQueue(com.google.android.exoplayer2.source.SampleQueue) MediaChunkIterator(com.google.android.exoplayer2.source.chunk.MediaChunkIterator) ExoTrackSelection(com.google.android.exoplayer2.trackselection.ExoTrackSelection)

Example 12 with Part

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

the class HlsMediaPlaylistSegmentIteratorTest method next_startIteratorAtFirstSegment_correctElements.

@Test
public void next_startIteratorAtFirstSegment_correctElements() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator = new HlsChunkSource.HlsMediaPlaylistSegmentIterator(mediaPlaylist.baseUri, /* startOfPlaylistInPeriodUs= */
    0, HlsChunkSource.getSegmentBaseList(mediaPlaylist, /* mediaSequence= */
    10, /* partIndex= */
    C.INDEX_UNSET));
    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
        datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }
    assertThat(datasSpecs).hasSize(9);
    // The iterator starts with 6 segments.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence10.ts");
    // Followed by trailing parts.
    assertThat(datasSpecs.get(6).uri.toString()).isEqualTo("fileSequence16.0.ts");
    // The preload part is the last.
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence16.2.ts");
}
Also used : ArrayList(java.util.ArrayList) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Test(org.junit.Test)

Example 13 with Part

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

the class HlsMediaPlaylistSegmentIteratorTest method next_startIteratorAtFirstPartInaSegment_usesFullSegment.

@Test
public void next_startIteratorAtFirstPartInaSegment_usesFullSegment() {
    HlsMediaPlaylist mediaPlaylist = getHlsMediaPlaylist(LOW_LATENCY_SEGMENTS_AND_PARTS);
    HlsChunkSource.HlsMediaPlaylistSegmentIterator hlsMediaPlaylistSegmentIterator = new HlsChunkSource.HlsMediaPlaylistSegmentIterator(mediaPlaylist.baseUri, /* startOfPlaylistInPeriodUs= */
    0, HlsChunkSource.getSegmentBaseList(mediaPlaylist, /* mediaSequence= */
    14, /* partIndex= */
    0));
    List<DataSpec> datasSpecs = new ArrayList<>();
    while (hlsMediaPlaylistSegmentIterator.next()) {
        datasSpecs.add(hlsMediaPlaylistSegmentIterator.getDataSpec());
    }
    assertThat(datasSpecs).hasSize(5);
    // The iterator starts with 6 segments.
    assertThat(datasSpecs.get(0).uri.toString()).isEqualTo("fileSequence14.ts");
    assertThat(datasSpecs.get(1).uri.toString()).isEqualTo("fileSequence15.ts");
    // Followed by trailing parts.
    assertThat(datasSpecs.get(2).uri.toString()).isEqualTo("fileSequence16.0.ts");
    assertThat(datasSpecs.get(3).uri.toString()).isEqualTo("fileSequence16.1.ts");
    // The preload part is the last.
    assertThat(Iterables.getLast(datasSpecs).uri.toString()).isEqualTo("fileSequence16.2.ts");
}
Also used : ArrayList(java.util.ArrayList) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) Test(org.junit.Test)

Example 14 with Part

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

the class MatroskaExtractor method writeSampleData.

/**
 * Writes data for a single sample to the track output.
 *
 * @param input The input from which to read sample data.
 * @param track The track to output the sample to.
 * @param size The size of the sample data on the input side.
 * @return The final size of the written sample.
 * @throws IOException If an error occurs reading from the input.
 */
@RequiresNonNull("#2.output")
private int writeSampleData(ExtractorInput input, Track track, int size) throws IOException {
    if (CODEC_ID_SUBRIP.equals(track.codecId)) {
        writeSubtitleSampleData(input, SUBRIP_PREFIX, size);
        return finishWriteSampleData();
    } else if (CODEC_ID_ASS.equals(track.codecId)) {
        writeSubtitleSampleData(input, SSA_PREFIX, size);
        return finishWriteSampleData();
    } else if (CODEC_ID_VTT.equals(track.codecId)) {
        writeSubtitleSampleData(input, VTT_PREFIX, size);
        return finishWriteSampleData();
    }
    TrackOutput output = track.output;
    if (!sampleEncodingHandled) {
        if (track.hasContentEncryption) {
            // If the sample is encrypted, read its encryption signal byte and set the IV size.
            // Clear the encrypted flag.
            blockFlags &= ~C.BUFFER_FLAG_ENCRYPTED;
            if (!sampleSignalByteRead) {
                input.readFully(scratch.getData(), 0, 1);
                sampleBytesRead++;
                if ((scratch.getData()[0] & 0x80) == 0x80) {
                    throw ParserException.createForMalformedContainer("Extension bit is set in signal byte", /* cause= */
                    null);
                }
                sampleSignalByte = scratch.getData()[0];
                sampleSignalByteRead = true;
            }
            boolean isEncrypted = (sampleSignalByte & 0x01) == 0x01;
            if (isEncrypted) {
                boolean hasSubsampleEncryption = (sampleSignalByte & 0x02) == 0x02;
                blockFlags |= C.BUFFER_FLAG_ENCRYPTED;
                if (!sampleInitializationVectorRead) {
                    input.readFully(encryptionInitializationVector.getData(), 0, ENCRYPTION_IV_SIZE);
                    sampleBytesRead += ENCRYPTION_IV_SIZE;
                    sampleInitializationVectorRead = true;
                    // Write the signal byte, containing the IV size and the subsample encryption flag.
                    scratch.getData()[0] = (byte) (ENCRYPTION_IV_SIZE | (hasSubsampleEncryption ? 0x80 : 0x00));
                    scratch.setPosition(0);
                    output.sampleData(scratch, 1, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
                    sampleBytesWritten++;
                    // Write the IV.
                    encryptionInitializationVector.setPosition(0);
                    output.sampleData(encryptionInitializationVector, ENCRYPTION_IV_SIZE, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
                    sampleBytesWritten += ENCRYPTION_IV_SIZE;
                }
                if (hasSubsampleEncryption) {
                    if (!samplePartitionCountRead) {
                        input.readFully(scratch.getData(), 0, 1);
                        sampleBytesRead++;
                        scratch.setPosition(0);
                        samplePartitionCount = scratch.readUnsignedByte();
                        samplePartitionCountRead = true;
                    }
                    int samplePartitionDataSize = samplePartitionCount * 4;
                    scratch.reset(samplePartitionDataSize);
                    input.readFully(scratch.getData(), 0, samplePartitionDataSize);
                    sampleBytesRead += samplePartitionDataSize;
                    short subsampleCount = (short) (1 + (samplePartitionCount / 2));
                    int subsampleDataSize = 2 + 6 * subsampleCount;
                    if (encryptionSubsampleDataBuffer == null || encryptionSubsampleDataBuffer.capacity() < subsampleDataSize) {
                        encryptionSubsampleDataBuffer = ByteBuffer.allocate(subsampleDataSize);
                    }
                    encryptionSubsampleDataBuffer.position(0);
                    encryptionSubsampleDataBuffer.putShort(subsampleCount);
                    // Loop through the partition offsets and write out the data in the way ExoPlayer
                    // wants it (ISO 23001-7 Part 7):
                    // 2 bytes - sub sample count.
                    // for each sub sample:
                    // 2 bytes - clear data size.
                    // 4 bytes - encrypted data size.
                    int partitionOffset = 0;
                    for (int i = 0; i < samplePartitionCount; i++) {
                        int previousPartitionOffset = partitionOffset;
                        partitionOffset = scratch.readUnsignedIntToInt();
                        if ((i % 2) == 0) {
                            encryptionSubsampleDataBuffer.putShort((short) (partitionOffset - previousPartitionOffset));
                        } else {
                            encryptionSubsampleDataBuffer.putInt(partitionOffset - previousPartitionOffset);
                        }
                    }
                    int finalPartitionSize = size - sampleBytesRead - partitionOffset;
                    if ((samplePartitionCount % 2) == 1) {
                        encryptionSubsampleDataBuffer.putInt(finalPartitionSize);
                    } else {
                        encryptionSubsampleDataBuffer.putShort((short) finalPartitionSize);
                        encryptionSubsampleDataBuffer.putInt(0);
                    }
                    encryptionSubsampleData.reset(encryptionSubsampleDataBuffer.array(), subsampleDataSize);
                    output.sampleData(encryptionSubsampleData, subsampleDataSize, TrackOutput.SAMPLE_DATA_PART_ENCRYPTION);
                    sampleBytesWritten += subsampleDataSize;
                }
            }
        } else if (track.sampleStrippedBytes != null) {
            // If the sample has header stripping, prepare to read/output the stripped bytes first.
            sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length);
        }
        if (track.maxBlockAdditionId > 0) {
            blockFlags |= C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA;
            blockAdditionalData.reset(/* limit= */
            0);
            // If there is supplemental data, the structure of the sample data is:
            // sample size (4 bytes) || sample data || supplemental data
            scratch.reset(/* limit= */
            4);
            scratch.getData()[0] = (byte) ((size >> 24) & 0xFF);
            scratch.getData()[1] = (byte) ((size >> 16) & 0xFF);
            scratch.getData()[2] = (byte) ((size >> 8) & 0xFF);
            scratch.getData()[3] = (byte) (size & 0xFF);
            output.sampleData(scratch, 4, TrackOutput.SAMPLE_DATA_PART_SUPPLEMENTAL);
            sampleBytesWritten += 4;
        }
        sampleEncodingHandled = true;
    }
    size += sampleStrippedBytes.limit();
    if (CODEC_ID_H264.equals(track.codecId) || CODEC_ID_H265.equals(track.codecId)) {
        // TODO: Deduplicate with Mp4Extractor.
        // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
        // they're only 1 or 2 bytes long.
        byte[] nalLengthData = nalLength.getData();
        nalLengthData[0] = 0;
        nalLengthData[1] = 0;
        nalLengthData[2] = 0;
        int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength;
        int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
        // start codes as we encounter them.
        while (sampleBytesRead < size) {
            if (sampleCurrentNalBytesRemaining == 0) {
                // Read the NAL length so that we know where we find the next one.
                writeToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
                sampleBytesRead += nalUnitLengthFieldLength;
                nalLength.setPosition(0);
                sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
                // Write a start code for the current NAL unit.
                nalStartCode.setPosition(0);
                output.sampleData(nalStartCode, 4);
                sampleBytesWritten += 4;
            } else {
                // Write the payload of the NAL unit.
                int bytesWritten = writeToOutput(input, output, sampleCurrentNalBytesRemaining);
                sampleBytesRead += bytesWritten;
                sampleBytesWritten += bytesWritten;
                sampleCurrentNalBytesRemaining -= bytesWritten;
            }
        }
    } else {
        if (track.trueHdSampleRechunker != null) {
            checkState(sampleStrippedBytes.limit() == 0);
            track.trueHdSampleRechunker.startSample(input);
        }
        while (sampleBytesRead < size) {
            int bytesWritten = writeToOutput(input, output, size - sampleBytesRead);
            sampleBytesRead += bytesWritten;
            sampleBytesWritten += bytesWritten;
        }
    }
    if (CODEC_ID_VORBIS.equals(track.codecId)) {
        // Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the
        // number of samples in the current page. This definition holds good only for Ogg and
        // irrelevant for Matroska. So we always set this to -1 (the decoder will ignore this value if
        // we set it to -1). The android platform media extractor [2] does the same.
        // [1]
        // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314
        // [2]
        // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474
        vorbisNumPageSamples.setPosition(0);
        output.sampleData(vorbisNumPageSamples, 4);
        sampleBytesWritten += 4;
    }
    return finishWriteSampleData();
}
Also used : TrackOutput(com.google.android.exoplayer2.extractor.TrackOutput) RequiresNonNull(org.checkerframework.checker.nullness.qual.RequiresNonNull)

Example 15 with Part

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

the class DefaultPlaybackSessionManager method updateSessions.

@Override
public synchronized void updateSessions(EventTime eventTime) {
    Assertions.checkNotNull(listener);
    if (eventTime.timeline.isEmpty()) {
        // Don't try to create new sessions for empty timelines.
        return;
    }
    @Nullable SessionDescriptor currentSession = sessions.get(currentSessionId);
    if (eventTime.mediaPeriodId != null && currentSession != null) {
        // If we receive an event associated with a media period, then it needs to be either part of
        // the current window if it's the first created media period, or a window that will be played
        // in the future. Otherwise, we know that it belongs to a session that was already finished
        // and we can ignore the event.
        boolean isAlreadyFinished = currentSession.windowSequenceNumber == C.INDEX_UNSET ? currentSession.windowIndex != eventTime.windowIndex : eventTime.mediaPeriodId.windowSequenceNumber < currentSession.windowSequenceNumber;
        if (isAlreadyFinished) {
            return;
        }
    }
    SessionDescriptor eventSession = getOrAddSession(eventTime.windowIndex, eventTime.mediaPeriodId);
    if (currentSessionId == null) {
        currentSessionId = eventSession.sessionId;
    }
    if (eventTime.mediaPeriodId != null && eventTime.mediaPeriodId.isAd()) {
        // Ensure that the content session for an ad session is created first.
        MediaPeriodId contentMediaPeriodId = new MediaPeriodId(eventTime.mediaPeriodId.periodUid, eventTime.mediaPeriodId.windowSequenceNumber, eventTime.mediaPeriodId.adGroupIndex);
        SessionDescriptor contentSession = getOrAddSession(eventTime.windowIndex, contentMediaPeriodId);
        if (!contentSession.isCreated) {
            contentSession.isCreated = true;
            eventTime.timeline.getPeriodByUid(eventTime.mediaPeriodId.periodUid, period);
            long adGroupPositionMs = Util.usToMs(period.getAdGroupTimeUs(eventTime.mediaPeriodId.adGroupIndex)) + period.getPositionInWindowMs();
            // getAdGroupTimeUs may return 0 for prerolls despite period offset.
            adGroupPositionMs = max(0, adGroupPositionMs);
            EventTime eventTimeForContent = new EventTime(eventTime.realtimeMs, eventTime.timeline, eventTime.windowIndex, contentMediaPeriodId, /* eventPlaybackPositionMs= */
            adGroupPositionMs, eventTime.currentTimeline, eventTime.currentWindowIndex, eventTime.currentMediaPeriodId, eventTime.currentPlaybackPositionMs, eventTime.totalBufferedDurationMs);
            listener.onSessionCreated(eventTimeForContent, contentSession.sessionId);
        }
    }
    if (!eventSession.isCreated) {
        eventSession.isCreated = true;
        listener.onSessionCreated(eventTime, eventSession.sessionId);
    }
    if (eventSession.sessionId.equals(currentSessionId) && !eventSession.isActive) {
        eventSession.isActive = true;
        listener.onSessionActive(eventTime, eventSession.sessionId);
    }
}
Also used : EventTime(com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime) MediaPeriodId(com.google.android.exoplayer2.source.MediaSource.MediaPeriodId) 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