mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
HLS: Fix key rotation
Passing EXT-X-KEY DrmInitData through the FragmentedMp4Extractor doesn't work for streams with key rotation, because an extractor instance is used for multiple segments, but is only passed the EXT-X-KEY DrmInitData corresponding to the first segment. This change removes passing DrmInitData through the extractor, and instead passes it via FormatAdjustingSampleQueue. This is in-line with how manifest DrmInitData is handled during DASH playbacks. Issue: #6903 PiperOrigin-RevId: 292323429
This commit is contained in:
parent
cf06589029
commit
ff822ff9fd
@ -25,6 +25,8 @@
|
|||||||
* Fix `SubtitlePainter` to render `EDGE_TYPE_OUTLINE` using the correct color
|
* Fix `SubtitlePainter` to render `EDGE_TYPE_OUTLINE` using the correct color
|
||||||
([#6724](https://github.com/google/ExoPlayer/pull/6724)).
|
([#6724](https://github.com/google/ExoPlayer/pull/6724)).
|
||||||
* DRM: Add support for attaching DRM sessions to clear content in the demo app.
|
* DRM: Add support for attaching DRM sessions to clear content in the demo app.
|
||||||
|
* HLS: Fix playback of DRM protected content that uses key rotation
|
||||||
|
([#6903](https://github.com/google/ExoPlayer/issues/6903)).
|
||||||
* Downloads: Merge downloads in `SegmentDownloader` to improve overall download
|
* Downloads: Merge downloads in `SegmentDownloader` to improve overall download
|
||||||
speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
|
speed ([#5978](https://github.com/google/ExoPlayer/issues/5978)).
|
||||||
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
|
* MP3: Add `IndexSeeker` for accurate seeks in VBR streams
|
||||||
|
@ -804,10 +804,12 @@ public class DefaultDashChunkSource implements DashChunkSource {
|
|||||||
}
|
}
|
||||||
extractor =
|
extractor =
|
||||||
new FragmentedMp4Extractor(
|
new FragmentedMp4Extractor(
|
||||||
flags, null, null, null, closedCaptionFormats, playerEmsgTrackOutput);
|
flags,
|
||||||
|
/* timestampAdjuster= */ null,
|
||||||
|
/* sideloadedTrack= */ null,
|
||||||
|
closedCaptionFormats,
|
||||||
|
playerEmsgTrackOutput);
|
||||||
}
|
}
|
||||||
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
|
|
||||||
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
|
|
||||||
return new ChunkExtractorWrapper(extractor, trackType, representation.format);
|
return new ChunkExtractorWrapper(extractor, trackType, representation.format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,6 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
// Sideloaded data.
|
// Sideloaded data.
|
||||||
private final List<Format> closedCaptionFormats;
|
private final List<Format> closedCaptionFormats;
|
||||||
@Nullable private final DrmInitData sideloadedDrmInitData;
|
|
||||||
|
|
||||||
// Track-linked data bundle, accessible as a whole through trackID.
|
// Track-linked data bundle, accessible as a whole through trackID.
|
||||||
private final SparseArray<TrackBundle> trackBundles;
|
private final SparseArray<TrackBundle> trackBundles;
|
||||||
@ -185,7 +184,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param flags Flags that control the extractor's behavior.
|
* @param flags Flags that control the extractor's behavior.
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(@Flags int flags) {
|
public FragmentedMp4Extractor(@Flags int flags) {
|
||||||
this(flags, null);
|
this(flags, /* timestampAdjuster= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,7 +192,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(@Flags int flags, @Nullable TimestampAdjuster timestampAdjuster) {
|
public FragmentedMp4Extractor(@Flags int flags, @Nullable TimestampAdjuster timestampAdjuster) {
|
||||||
this(flags, timestampAdjuster, null, null);
|
this(flags, timestampAdjuster, /* sideloadedTrack= */ null, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,15 +200,12 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
||||||
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
||||||
* receive a moov box in the input data. Null if a moov box is expected.
|
* receive a moov box in the input data. Null if a moov box is expected.
|
||||||
* @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
|
|
||||||
* pssh boxes (if present) will be used.
|
|
||||||
*/
|
*/
|
||||||
public FragmentedMp4Extractor(
|
public FragmentedMp4Extractor(
|
||||||
@Flags int flags,
|
@Flags int flags,
|
||||||
@Nullable TimestampAdjuster timestampAdjuster,
|
@Nullable TimestampAdjuster timestampAdjuster,
|
||||||
@Nullable Track sideloadedTrack,
|
@Nullable Track sideloadedTrack) {
|
||||||
@Nullable DrmInitData sideloadedDrmInitData) {
|
this(flags, timestampAdjuster, sideloadedTrack, Collections.emptyList());
|
||||||
this(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData, Collections.emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,8 +213,6 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
||||||
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
||||||
* receive a moov box in the input data. Null if a moov box is expected.
|
* receive a moov box in the input data. Null if a moov box is expected.
|
||||||
* @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
|
|
||||||
* pssh boxes (if present) will be used.
|
|
||||||
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
|
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
|
||||||
* caption channels to expose.
|
* caption channels to expose.
|
||||||
*/
|
*/
|
||||||
@ -226,10 +220,13 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
@Flags int flags,
|
@Flags int flags,
|
||||||
@Nullable TimestampAdjuster timestampAdjuster,
|
@Nullable TimestampAdjuster timestampAdjuster,
|
||||||
@Nullable Track sideloadedTrack,
|
@Nullable Track sideloadedTrack,
|
||||||
@Nullable DrmInitData sideloadedDrmInitData,
|
|
||||||
List<Format> closedCaptionFormats) {
|
List<Format> closedCaptionFormats) {
|
||||||
this(flags, timestampAdjuster, sideloadedTrack, sideloadedDrmInitData,
|
this(
|
||||||
closedCaptionFormats, null);
|
flags,
|
||||||
|
timestampAdjuster,
|
||||||
|
sideloadedTrack,
|
||||||
|
closedCaptionFormats,
|
||||||
|
/* additionalEmsgTrackOutput= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -237,8 +234,6 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
|
||||||
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
* @param sideloadedTrack Sideloaded track information, in the case that the extractor will not
|
||||||
* receive a moov box in the input data. Null if a moov box is expected.
|
* receive a moov box in the input data. Null if a moov box is expected.
|
||||||
* @param sideloadedDrmInitData The {@link DrmInitData} to use for encrypted tracks. If null, the
|
|
||||||
* pssh boxes (if present) will be used.
|
|
||||||
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
|
* @param closedCaptionFormats For tracks that contain SEI messages, the formats of the closed
|
||||||
* caption channels to expose.
|
* caption channels to expose.
|
||||||
* @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages
|
* @param additionalEmsgTrackOutput An extra track output that will receive all emsg messages
|
||||||
@ -249,13 +244,11 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
@Flags int flags,
|
@Flags int flags,
|
||||||
@Nullable TimestampAdjuster timestampAdjuster,
|
@Nullable TimestampAdjuster timestampAdjuster,
|
||||||
@Nullable Track sideloadedTrack,
|
@Nullable Track sideloadedTrack,
|
||||||
@Nullable DrmInitData sideloadedDrmInitData,
|
|
||||||
List<Format> closedCaptionFormats,
|
List<Format> closedCaptionFormats,
|
||||||
@Nullable TrackOutput additionalEmsgTrackOutput) {
|
@Nullable TrackOutput additionalEmsgTrackOutput) {
|
||||||
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
this.sideloadedTrack = sideloadedTrack;
|
this.sideloadedTrack = sideloadedTrack;
|
||||||
this.sideloadedDrmInitData = sideloadedDrmInitData;
|
|
||||||
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
|
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
|
||||||
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
|
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
|
||||||
eventMessageEncoder = new EventMessageEncoder();
|
eventMessageEncoder = new EventMessageEncoder();
|
||||||
@ -470,8 +463,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
|
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
|
||||||
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
|
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
|
||||||
|
|
||||||
DrmInitData drmInitData = sideloadedDrmInitData != null ? sideloadedDrmInitData
|
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
|
||||||
: getDrmInitDataFromAtoms(moov.leafChildren);
|
|
||||||
|
|
||||||
// Read declaration of track fragments in the Moov box.
|
// Read declaration of track fragments in the Moov box.
|
||||||
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
||||||
@ -550,9 +542,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
||||||
parseMoof(moof, trackBundles, flags, scratchBytes);
|
parseMoof(moof, trackBundles, flags, scratchBytes);
|
||||||
// If drm init data is sideloaded, we ignore pssh boxes.
|
|
||||||
DrmInitData drmInitData = sideloadedDrmInitData != null ? null
|
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
|
||||||
: getDrmInitDataFromAtoms(moof.leafChildren);
|
|
||||||
if (drmInitData != null) {
|
if (drmInitData != null) {
|
||||||
int trackCount = trackBundles.size();
|
int trackCount = trackBundles.size();
|
||||||
for (int i = 0; i < trackCount; i++) {
|
for (int i = 0; i < trackCount; i++) {
|
||||||
@ -1417,6 +1408,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns DrmInitData from leaf atoms. */
|
/** Returns DrmInitData from leaf atoms. */
|
||||||
|
@Nullable
|
||||||
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
|
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
|
||||||
@Nullable ArrayList<SchemeData> schemeDatas = null;
|
@Nullable ArrayList<SchemeData> schemeDatas = null;
|
||||||
int leafChildrenSize = leafChildren.size();
|
int leafChildrenSize = leafChildren.size();
|
||||||
|
@ -47,7 +47,11 @@ public final class FragmentedMp4ExtractorTest {
|
|||||||
ExtractorFactory extractorFactory =
|
ExtractorFactory extractorFactory =
|
||||||
getExtractorFactory(
|
getExtractorFactory(
|
||||||
Collections.singletonList(
|
Collections.singletonList(
|
||||||
Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, 0, null)));
|
Format.createTextSampleFormat(
|
||||||
|
null,
|
||||||
|
MimeTypes.APPLICATION_CEA608,
|
||||||
|
/* selectionFlags= */ 0,
|
||||||
|
/* language= */ null)));
|
||||||
ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4");
|
ExtractorAsserts.assertBehavior(extractorFactory, "mp4/sample_fragmented_sei.mp4");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +68,11 @@ public final class FragmentedMp4ExtractorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) {
|
private static ExtractorFactory getExtractorFactory(final List<Format> closedCaptionFormats) {
|
||||||
return () -> new FragmentedMp4Extractor(0, null, null, null, closedCaptionFormats);
|
return () ->
|
||||||
|
new FragmentedMp4Extractor(
|
||||||
|
/* flags= */ 0,
|
||||||
|
/* timestampAdjuster= */ null,
|
||||||
|
/* sideloadedTrack= */ null,
|
||||||
|
closedCaptionFormats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
|||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
|
||||||
@ -89,7 +88,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
Uri uri,
|
Uri uri,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
TimestampAdjuster timestampAdjuster,
|
TimestampAdjuster timestampAdjuster,
|
||||||
Map<String, List<String>> responseHeaders,
|
Map<String, List<String>> responseHeaders,
|
||||||
ExtractorInput extractorInput)
|
ExtractorInput extractorInput)
|
||||||
@ -111,8 +109,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
|
|
||||||
// Try selecting the extractor by the file extension.
|
// Try selecting the extractor by the file extension.
|
||||||
Extractor extractorByFileExtension =
|
Extractor extractorByFileExtension =
|
||||||
createExtractorByFileExtension(
|
createExtractorByFileExtension(uri, format, muxedCaptionFormats, timestampAdjuster);
|
||||||
uri, format, muxedCaptionFormats, drmInitData, timestampAdjuster);
|
|
||||||
extractorInput.resetPeekPosition();
|
extractorInput.resetPeekPosition();
|
||||||
if (sniffQuietly(extractorByFileExtension, extractorInput)) {
|
if (sniffQuietly(extractorByFileExtension, extractorInput)) {
|
||||||
return buildResult(extractorByFileExtension);
|
return buildResult(extractorByFileExtension);
|
||||||
@ -159,7 +156,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
|
|
||||||
if (!(extractorByFileExtension instanceof FragmentedMp4Extractor)) {
|
if (!(extractorByFileExtension instanceof FragmentedMp4Extractor)) {
|
||||||
FragmentedMp4Extractor fragmentedMp4Extractor =
|
FragmentedMp4Extractor fragmentedMp4Extractor =
|
||||||
createFragmentedMp4Extractor(timestampAdjuster, format, drmInitData, muxedCaptionFormats);
|
createFragmentedMp4Extractor(timestampAdjuster, format, muxedCaptionFormats);
|
||||||
if (sniffQuietly(fragmentedMp4Extractor, extractorInput)) {
|
if (sniffQuietly(fragmentedMp4Extractor, extractorInput)) {
|
||||||
return buildResult(fragmentedMp4Extractor);
|
return buildResult(fragmentedMp4Extractor);
|
||||||
}
|
}
|
||||||
@ -186,7 +183,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
Uri uri,
|
Uri uri,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
TimestampAdjuster timestampAdjuster) {
|
TimestampAdjuster timestampAdjuster) {
|
||||||
String lastPathSegment = uri.getLastPathSegment();
|
String lastPathSegment = uri.getLastPathSegment();
|
||||||
if (lastPathSegment == null) {
|
if (lastPathSegment == null) {
|
||||||
@ -209,8 +205,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)
|
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)
|
||||||
|| lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)
|
|| lastPathSegment.startsWith(MP4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)
|
||||||
|| lastPathSegment.startsWith(CMF_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) {
|
|| lastPathSegment.startsWith(CMF_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 5)) {
|
||||||
return createFragmentedMp4Extractor(
|
return createFragmentedMp4Extractor(timestampAdjuster, format, muxedCaptionFormats);
|
||||||
timestampAdjuster, format, drmInitData, muxedCaptionFormats);
|
|
||||||
} else {
|
} else {
|
||||||
// For any other file extension, we assume TS format.
|
// For any other file extension, we assume TS format.
|
||||||
return createTsExtractor(
|
return createTsExtractor(
|
||||||
@ -270,7 +265,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
private static FragmentedMp4Extractor createFragmentedMp4Extractor(
|
private static FragmentedMp4Extractor createFragmentedMp4Extractor(
|
||||||
TimestampAdjuster timestampAdjuster,
|
TimestampAdjuster timestampAdjuster,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
@Nullable List<Format> muxedCaptionFormats) {
|
@Nullable List<Format> muxedCaptionFormats) {
|
||||||
// Only enable the EMSG TrackOutput if this is the 'variant' track (i.e. the main one) to avoid
|
// Only enable the EMSG TrackOutput if this is the 'variant' track (i.e. the main one) to avoid
|
||||||
// creating a separate EMSG track for every audio track in a video stream.
|
// creating a separate EMSG track for every audio track in a video stream.
|
||||||
@ -278,7 +272,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
/* flags= */ isFmp4Variant(format) ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK : 0,
|
/* flags= */ isFmp4Variant(format) ? FragmentedMp4Extractor.FLAG_ENABLE_EMSG_TRACK : 0,
|
||||||
timestampAdjuster,
|
timestampAdjuster,
|
||||||
/* sideloadedTrack= */ null,
|
/* sideloadedTrack= */ null,
|
||||||
drmInitData,
|
|
||||||
muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList());
|
muxedCaptionFormats != null ? muxedCaptionFormats : Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source.hls;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
@ -71,7 +70,6 @@ public interface HlsExtractorFactory {
|
|||||||
* @param format A {@link Format} associated with the chunk to extract.
|
* @param format A {@link Format} associated with the chunk to extract.
|
||||||
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
|
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
|
||||||
* information is available in the master playlist.
|
* information is available in the master playlist.
|
||||||
* @param drmInitData {@link DrmInitData} associated with the chunk.
|
|
||||||
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
|
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
|
||||||
* @param responseHeaders The HTTP response headers associated with the media segment or
|
* @param responseHeaders The HTTP response headers associated with the media segment or
|
||||||
* initialization section to extract.
|
* initialization section to extract.
|
||||||
@ -87,7 +85,6 @@ public interface HlsExtractorFactory {
|
|||||||
Uri uri,
|
Uri uri,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
TimestampAdjuster timestampAdjuster,
|
TimestampAdjuster timestampAdjuster,
|
||||||
Map<String, List<String>> responseHeaders,
|
Map<String, List<String>> responseHeaders,
|
||||||
ExtractorInput sniffingExtractorInput)
|
ExtractorInput sniffingExtractorInput)
|
||||||
|
@ -388,7 +388,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private DefaultExtractorInput prepareExtraction(DataSource dataSource, DataSpec dataSpec)
|
private DefaultExtractorInput prepareExtraction(DataSource dataSource, DataSpec dataSpec)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
long bytesToRead = dataSource.open(dataSpec);
|
long bytesToRead = dataSource.open(dataSpec);
|
||||||
|
|
||||||
DefaultExtractorInput extractorInput =
|
DefaultExtractorInput extractorInput =
|
||||||
new DefaultExtractorInput(dataSource, dataSpec.absoluteStreamPosition, bytesToRead);
|
new DefaultExtractorInput(dataSource, dataSpec.absoluteStreamPosition, bytesToRead);
|
||||||
|
|
||||||
@ -402,7 +401,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
dataSpec.uri,
|
dataSpec.uri,
|
||||||
trackFormat,
|
trackFormat,
|
||||||
muxedCaptionFormats,
|
muxedCaptionFormats,
|
||||||
drmInitData,
|
|
||||||
timestampAdjuster,
|
timestampAdjuster,
|
||||||
dataSource.getResponseHeaders(),
|
dataSource.getResponseHeaders(),
|
||||||
extractorInput);
|
extractorInput);
|
||||||
@ -421,7 +419,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
output.onNewExtractor();
|
output.onNewExtractor();
|
||||||
extractor.init(output);
|
extractor.init(output);
|
||||||
}
|
}
|
||||||
|
output.setDrmInitData(drmInitData);
|
||||||
return extractorInput;
|
return extractorInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
||||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||||
|
|
||||||
private SampleQueue[] sampleQueues;
|
private FormatAdjustingSampleQueue[] sampleQueues;
|
||||||
private int[] sampleQueueTrackIds;
|
private int[] sampleQueueTrackIds;
|
||||||
private Set<Integer> sampleQueueMappingDoneByType;
|
private Set<Integer> sampleQueueMappingDoneByType;
|
||||||
private SparseIntArray sampleQueueIndicesByType;
|
private SparseIntArray sampleQueueIndicesByType;
|
||||||
@ -162,6 +162,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private boolean tracksEnded;
|
private boolean tracksEnded;
|
||||||
private long sampleOffsetUs;
|
private long sampleOffsetUs;
|
||||||
|
@Nullable private DrmInitData drmInitData;
|
||||||
private int chunkUid;
|
private int chunkUid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,7 +208,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sampleQueueTrackIds = new int[0];
|
sampleQueueTrackIds = new int[0];
|
||||||
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
|
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
|
||||||
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
|
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
|
||||||
sampleQueues = new SampleQueue[0];
|
sampleQueues = new FormatAdjustingSampleQueue[0];
|
||||||
sampleQueueIsAudioVideoFlags = new boolean[0];
|
sampleQueueIsAudioVideoFlags = new boolean[0];
|
||||||
sampleQueuesEnabledStates = new boolean[0];
|
sampleQueuesEnabledStates = new boolean[0];
|
||||||
mediaChunks = new ArrayList<>();
|
mediaChunks = new ArrayList<>();
|
||||||
@ -904,8 +905,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private SampleQueue createSampleQueue(int id, int type) {
|
private SampleQueue createSampleQueue(int id, int type) {
|
||||||
int trackCount = sampleQueues.length;
|
int trackCount = sampleQueues.length;
|
||||||
|
|
||||||
SampleQueue trackOutput =
|
boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
|
||||||
|
FormatAdjustingSampleQueue trackOutput =
|
||||||
new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData);
|
new FormatAdjustingSampleQueue(allocator, drmSessionManager, overridingDrmInitData);
|
||||||
|
if (isAudioVideo) {
|
||||||
|
trackOutput.setDrmInitData(drmInitData);
|
||||||
|
}
|
||||||
trackOutput.setSampleOffsetUs(sampleOffsetUs);
|
trackOutput.setSampleOffsetUs(sampleOffsetUs);
|
||||||
trackOutput.sourceId(chunkUid);
|
trackOutput.sourceId(chunkUid);
|
||||||
trackOutput.setUpstreamFormatChangeListener(this);
|
trackOutput.setUpstreamFormatChangeListener(this);
|
||||||
@ -913,8 +918,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sampleQueueTrackIds[trackCount] = id;
|
sampleQueueTrackIds[trackCount] = id;
|
||||||
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
|
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
|
||||||
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
|
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
|
||||||
sampleQueueIsAudioVideoFlags[trackCount] =
|
sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo;
|
||||||
type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
|
|
||||||
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
|
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
|
||||||
sampleQueueMappingDoneByType.add(type);
|
sampleQueueMappingDoneByType.add(type);
|
||||||
sampleQueueIndicesByType.append(type, trackCount);
|
sampleQueueIndicesByType.append(type, trackCount);
|
||||||
@ -951,10 +955,53 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sampleQueueMappingDoneByType.clear();
|
sampleQueueMappingDoneByType.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an offset that will be added to the timestamps (and sub-sample timestamps) of samples that
|
||||||
|
* are subsequently loaded by this wrapper.
|
||||||
|
*
|
||||||
|
* @param sampleOffsetUs The timestamp offset in microseconds.
|
||||||
|
*/
|
||||||
public void setSampleOffsetUs(long sampleOffsetUs) {
|
public void setSampleOffsetUs(long sampleOffsetUs) {
|
||||||
this.sampleOffsetUs = sampleOffsetUs;
|
if (this.sampleOffsetUs != sampleOffsetUs) {
|
||||||
for (SampleQueue sampleQueue : sampleQueues) {
|
this.sampleOffsetUs = sampleOffsetUs;
|
||||||
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
|
for (SampleQueue sampleQueue : sampleQueues) {
|
||||||
|
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets default {@link DrmInitData} for samples that are subsequently loaded by this wrapper.
|
||||||
|
*
|
||||||
|
* <p>This method should be called prior to loading each {@link HlsMediaChunk}. The {@link
|
||||||
|
* DrmInitData} passed should be that of an EXT-X-KEY tag that applies to the chunk, or {@code
|
||||||
|
* null} otherwise.
|
||||||
|
*
|
||||||
|
* <p>The final {@link DrmInitData} for subsequently queued samples is determined as followed:
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>It is initially set to {@code drmInitData}, unless {@code drmInitData} is null in which
|
||||||
|
* case it's set to {@link Format#drmInitData} of the upstream {@link Format}.
|
||||||
|
* <li>If the initial {@link DrmInitData} is non-null and {@link #overridingDrmInitData}
|
||||||
|
* contains an entry whose key matches the {@link DrmInitData#schemeType}, then the sample's
|
||||||
|
* {@link DrmInitData} is overridden to be this entry's value.
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @param drmInitData The default {@link DrmInitData} for samples that are subsequently queued. If
|
||||||
|
* non-null then it takes precedence over {@link Format#drmInitData} of the upstream {@link
|
||||||
|
* Format}, but will still be overridden by a matching override in {@link
|
||||||
|
* #overridingDrmInitData}.
|
||||||
|
*/
|
||||||
|
public void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||||
|
if (!Util.areEqual(this.drmInitData, drmInitData)) {
|
||||||
|
this.drmInitData = drmInitData;
|
||||||
|
for (int i = 0; i < sampleQueues.length; i++) {
|
||||||
|
if (sampleQueueIsAudioVideoFlags[i]) {
|
||||||
|
sampleQueues[i].setDrmInitData(drmInitData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1280,6 +1327,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private static final class FormatAdjustingSampleQueue extends SampleQueue {
|
private static final class FormatAdjustingSampleQueue extends SampleQueue {
|
||||||
|
|
||||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||||
|
@Nullable private DrmInitData drmInitData;
|
||||||
|
|
||||||
public FormatAdjustingSampleQueue(
|
public FormatAdjustingSampleQueue(
|
||||||
Allocator allocator,
|
Allocator allocator,
|
||||||
@ -1289,9 +1337,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
this.overridingDrmInitData = overridingDrmInitData;
|
this.overridingDrmInitData = overridingDrmInitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||||
|
this.drmInitData = drmInitData;
|
||||||
|
invalidateUpstreamFormatAdjustment();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Format getAdjustedUpstreamFormat(Format format) {
|
public Format getAdjustedUpstreamFormat(Format format) {
|
||||||
@Nullable DrmInitData drmInitData = format.drmInitData;
|
@Nullable
|
||||||
|
DrmInitData drmInitData = this.drmInitData != null ? this.drmInitData : format.drmInitData;
|
||||||
if (drmInitData != null) {
|
if (drmInitData != null) {
|
||||||
@Nullable
|
@Nullable
|
||||||
DrmInitData overridingDrmInitData = this.overridingDrmInitData.get(drmInitData.schemeType);
|
DrmInitData overridingDrmInitData = this.overridingDrmInitData.get(drmInitData.schemeType);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user