mirror of
https://github.com/androidx/media.git
synced 2025-05-11 09:39:52 +08:00
Support providing all keys via EXT-X-SESSION-KEY tags
PiperOrigin-RevId: 240333415
This commit is contained in:
parent
c04f5b9c5d
commit
07e3509dd9
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -183,6 +184,25 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||||||
return new DrmInitData(schemeType, false, schemeDatas);
|
return new DrmInitData(schemeType, false, schemeDatas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance containing the {@link #schemeDatas} from both this and {@code other}. The
|
||||||
|
* {@link #schemeType} of the instances being merged must either match, or at least one scheme
|
||||||
|
* type must be {@code null}.
|
||||||
|
*
|
||||||
|
* @param drmInitData The instance to merge.
|
||||||
|
* @return The merged result.
|
||||||
|
*/
|
||||||
|
public DrmInitData merge(DrmInitData drmInitData) {
|
||||||
|
Assertions.checkState(
|
||||||
|
schemeType == null
|
||||||
|
|| drmInitData.schemeType == null
|
||||||
|
|| TextUtils.equals(schemeType, drmInitData.schemeType));
|
||||||
|
String mergedSchemeType = schemeType != null ? this.schemeType : drmInitData.schemeType;
|
||||||
|
SchemeData[] mergedSchemeDatas =
|
||||||
|
Util.nullSafeArrayConcatenation(schemeDatas, drmInitData.schemeDatas);
|
||||||
|
return new DrmInitData(mergedSchemeType, mergedSchemeDatas);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
if (hashCode == 0) {
|
if (hashCode == 0) {
|
||||||
|
@ -314,6 +314,25 @@ public final class Util {
|
|||||||
return Arrays.copyOf(input, length);
|
return Arrays.copyOf(input, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates two non-null type arrays.
|
||||||
|
*
|
||||||
|
* @param first The first array.
|
||||||
|
* @param second The second array.
|
||||||
|
* @return The concatenated result.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"nullness:assignment.type.incompatible"})
|
||||||
|
public static <T> T[] nullSafeArrayConcatenation(T[] first, T[] second) {
|
||||||
|
T[] concatenation = Arrays.copyOf(first, first.length + second.length);
|
||||||
|
System.arraycopy(
|
||||||
|
/* src= */ second,
|
||||||
|
/* srcPos= */ 0,
|
||||||
|
/* dest= */ concatenation,
|
||||||
|
/* destPos= */ first.length,
|
||||||
|
/* length= */ second.length);
|
||||||
|
return concatenation;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Handler} with the specified {@link Handler.Callback} on the current {@link
|
* Creates a {@link Handler} with the specified {@link Handler.Callback} on the current {@link
|
||||||
* Looper} thread. The method accepts partially initialized objects as callback under the
|
* Looper} thread. The method accepts partially initialized objects as callback under the
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
package com.google.android.exoplayer2.source.hls;
|
package com.google.android.exoplayer2.source.hls;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.SeekParameters;
|
import com.google.android.exoplayer2.SeekParameters;
|
||||||
|
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.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
|
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
|
||||||
@ -43,9 +45,11 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaPeriod} that loads an HLS stream.
|
* A {@link MediaPeriod} that loads an HLS stream.
|
||||||
@ -64,6 +68,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private final boolean allowChunklessPreparation;
|
private final boolean allowChunklessPreparation;
|
||||||
|
private final boolean useSessionKeys;
|
||||||
|
|
||||||
private @Nullable Callback callback;
|
private @Nullable Callback callback;
|
||||||
private int pendingPrepareCount;
|
private int pendingPrepareCount;
|
||||||
@ -90,6 +95,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
|
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
|
||||||
* SequenceableLoader}s for when this media source loads data from multiple streams.
|
* SequenceableLoader}s for when this media source loads data from multiple streams.
|
||||||
* @param allowChunklessPreparation Whether chunkless preparation is allowed.
|
* @param allowChunklessPreparation Whether chunkless preparation is allowed.
|
||||||
|
* @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags.
|
||||||
*/
|
*/
|
||||||
public HlsMediaPeriod(
|
public HlsMediaPeriod(
|
||||||
HlsExtractorFactory extractorFactory,
|
HlsExtractorFactory extractorFactory,
|
||||||
@ -100,7 +106,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
EventDispatcher eventDispatcher,
|
EventDispatcher eventDispatcher,
|
||||||
Allocator allocator,
|
Allocator allocator,
|
||||||
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
||||||
boolean allowChunklessPreparation) {
|
boolean allowChunklessPreparation,
|
||||||
|
boolean useSessionKeys) {
|
||||||
this.extractorFactory = extractorFactory;
|
this.extractorFactory = extractorFactory;
|
||||||
this.playlistTracker = playlistTracker;
|
this.playlistTracker = playlistTracker;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
@ -110,6 +117,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||||
this.allowChunklessPreparation = allowChunklessPreparation;
|
this.allowChunklessPreparation = allowChunklessPreparation;
|
||||||
|
this.useSessionKeys = useSessionKeys;
|
||||||
compositeSequenceableLoader =
|
compositeSequenceableLoader =
|
||||||
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader();
|
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader();
|
||||||
streamWrapperIndices = new IdentityHashMap<>();
|
streamWrapperIndices = new IdentityHashMap<>();
|
||||||
@ -427,7 +435,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void buildAndPrepareSampleStreamWrappers(long positionUs) {
|
private void buildAndPrepareSampleStreamWrappers(long positionUs) {
|
||||||
HlsMasterPlaylist masterPlaylist = playlistTracker.getMasterPlaylist();
|
HlsMasterPlaylist masterPlaylist = Assertions.checkNotNull(playlistTracker.getMasterPlaylist());
|
||||||
|
Map<String, DrmInitData> overridingDrmInitData =
|
||||||
|
useSessionKeys
|
||||||
|
? deriveOverridingDrmInitData(masterPlaylist.sessionKeyDrmInitData)
|
||||||
|
: Collections.emptyMap();
|
||||||
|
|
||||||
boolean hasVariants = !masterPlaylist.variants.isEmpty();
|
boolean hasVariants = !masterPlaylist.variants.isEmpty();
|
||||||
List<HlsUrl> audioRenditions = masterPlaylist.audios;
|
List<HlsUrl> audioRenditions = masterPlaylist.audios;
|
||||||
List<HlsUrl> subtitleRenditions = masterPlaylist.subtitles;
|
List<HlsUrl> subtitleRenditions = masterPlaylist.subtitles;
|
||||||
@ -438,20 +451,33 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
|
|
||||||
if (hasVariants) {
|
if (hasVariants) {
|
||||||
buildAndPrepareMainSampleStreamWrapper(
|
buildAndPrepareMainSampleStreamWrapper(
|
||||||
masterPlaylist, positionUs, sampleStreamWrappers, manifestUrlIndicesPerWrapper);
|
masterPlaylist,
|
||||||
|
positionUs,
|
||||||
|
sampleStreamWrappers,
|
||||||
|
manifestUrlIndicesPerWrapper,
|
||||||
|
overridingDrmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Build video stream wrappers here.
|
// TODO: Build video stream wrappers here.
|
||||||
|
|
||||||
buildAndPrepareAudioSampleStreamWrappers(
|
buildAndPrepareAudioSampleStreamWrappers(
|
||||||
positionUs, audioRenditions, sampleStreamWrappers, manifestUrlIndicesPerWrapper);
|
positionUs,
|
||||||
|
audioRenditions,
|
||||||
|
sampleStreamWrappers,
|
||||||
|
manifestUrlIndicesPerWrapper,
|
||||||
|
overridingDrmInitData);
|
||||||
|
|
||||||
// Subtitle stream wrappers. We can always use master playlist information to prepare these.
|
// Subtitle stream wrappers. We can always use master playlist information to prepare these.
|
||||||
for (int i = 0; i < subtitleRenditions.size(); i++) {
|
for (int i = 0; i < subtitleRenditions.size(); i++) {
|
||||||
HlsUrl url = subtitleRenditions.get(i);
|
HlsUrl url = subtitleRenditions.get(i);
|
||||||
HlsSampleStreamWrapper sampleStreamWrapper =
|
HlsSampleStreamWrapper sampleStreamWrapper =
|
||||||
buildSampleStreamWrapper(
|
buildSampleStreamWrapper(
|
||||||
C.TRACK_TYPE_TEXT, new HlsUrl[] {url}, null, Collections.emptyList(), positionUs);
|
C.TRACK_TYPE_TEXT,
|
||||||
|
new HlsUrl[] {url},
|
||||||
|
null,
|
||||||
|
Collections.emptyList(),
|
||||||
|
overridingDrmInitData,
|
||||||
|
positionUs);
|
||||||
manifestUrlIndicesPerWrapper.add(new int[] {i});
|
manifestUrlIndicesPerWrapper.add(new int[] {i});
|
||||||
sampleStreamWrappers.add(sampleStreamWrapper);
|
sampleStreamWrappers.add(sampleStreamWrapper);
|
||||||
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
|
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
|
||||||
@ -495,12 +521,15 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
* which downloading should start. Ignored otherwise.
|
* which downloading should start. Ignored otherwise.
|
||||||
* @param sampleStreamWrappers List to which the built main sample stream wrapper should be added.
|
* @param sampleStreamWrappers List to which the built main sample stream wrapper should be added.
|
||||||
* @param manifestUrlIndicesPerWrapper List to which the selected variant indices should be added.
|
* @param manifestUrlIndicesPerWrapper List to which the selected variant indices should be added.
|
||||||
|
* @param overridingDrmInitData Overriding {@link DrmInitData}, keyed by protection scheme type
|
||||||
|
* (i.e. {@link DrmInitData#schemeType}).
|
||||||
*/
|
*/
|
||||||
private void buildAndPrepareMainSampleStreamWrapper(
|
private void buildAndPrepareMainSampleStreamWrapper(
|
||||||
HlsMasterPlaylist masterPlaylist,
|
HlsMasterPlaylist masterPlaylist,
|
||||||
long positionUs,
|
long positionUs,
|
||||||
List<HlsSampleStreamWrapper> sampleStreamWrappers,
|
List<HlsSampleStreamWrapper> sampleStreamWrappers,
|
||||||
List<int[]> manifestUrlIndicesPerWrapper) {
|
List<int[]> manifestUrlIndicesPerWrapper,
|
||||||
|
Map<String, DrmInitData> overridingDrmInitData) {
|
||||||
int[] variantTypes = new int[masterPlaylist.variants.size()];
|
int[] variantTypes = new int[masterPlaylist.variants.size()];
|
||||||
int videoVariantCount = 0;
|
int videoVariantCount = 0;
|
||||||
int audioVariantCount = 0;
|
int audioVariantCount = 0;
|
||||||
@ -549,6 +578,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
selectedVariants,
|
selectedVariants,
|
||||||
masterPlaylist.muxedAudioFormat,
|
masterPlaylist.muxedAudioFormat,
|
||||||
masterPlaylist.muxedCaptionFormats,
|
masterPlaylist.muxedCaptionFormats,
|
||||||
|
overridingDrmInitData,
|
||||||
positionUs);
|
positionUs);
|
||||||
sampleStreamWrappers.add(sampleStreamWrapper);
|
sampleStreamWrappers.add(sampleStreamWrapper);
|
||||||
manifestUrlIndicesPerWrapper.add(selectedVariantIndices);
|
manifestUrlIndicesPerWrapper.add(selectedVariantIndices);
|
||||||
@ -616,8 +646,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
long positionUs,
|
long positionUs,
|
||||||
List<HlsUrl> audioRenditions,
|
List<HlsUrl> audioRenditions,
|
||||||
List<HlsSampleStreamWrapper> sampleStreamWrappers,
|
List<HlsSampleStreamWrapper> sampleStreamWrappers,
|
||||||
List<int[]> manifestUrlsIndicesPerWrapper) {
|
List<int[]> manifestUrlsIndicesPerWrapper,
|
||||||
|
Map<String, DrmInitData> overridingDrmInitData) {
|
||||||
ArrayList<HlsUrl> scratchRenditionList =
|
ArrayList<HlsUrl> scratchRenditionList =
|
||||||
new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
|
new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
|
||||||
ArrayList<Integer> scratchIndicesList =
|
ArrayList<Integer> scratchIndicesList =
|
||||||
@ -651,6 +681,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
scratchRenditionList.toArray(new HlsUrl[0]),
|
scratchRenditionList.toArray(new HlsUrl[0]),
|
||||||
/* muxedAudioFormat= */ null,
|
/* muxedAudioFormat= */ null,
|
||||||
/* muxedCaptionFormats= */ Collections.emptyList(),
|
/* muxedCaptionFormats= */ Collections.emptyList(),
|
||||||
|
overridingDrmInitData,
|
||||||
positionUs);
|
positionUs);
|
||||||
manifestUrlsIndicesPerWrapper.add(Util.toArray(scratchIndicesList));
|
manifestUrlsIndicesPerWrapper.add(Util.toArray(scratchIndicesList));
|
||||||
sampleStreamWrappers.add(sampleStreamWrapper);
|
sampleStreamWrappers.add(sampleStreamWrapper);
|
||||||
@ -666,8 +697,13 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
|
private HlsSampleStreamWrapper buildSampleStreamWrapper(
|
||||||
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
|
int trackType,
|
||||||
|
HlsUrl[] variants,
|
||||||
|
Format muxedAudioFormat,
|
||||||
|
List<Format> muxedCaptionFormats,
|
||||||
|
Map<String, DrmInitData> overridingDrmInitData,
|
||||||
|
long positionUs) {
|
||||||
HlsChunkSource defaultChunkSource =
|
HlsChunkSource defaultChunkSource =
|
||||||
new HlsChunkSource(
|
new HlsChunkSource(
|
||||||
extractorFactory,
|
extractorFactory,
|
||||||
@ -681,6 +717,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
trackType,
|
trackType,
|
||||||
/* callback= */ this,
|
/* callback= */ this,
|
||||||
defaultChunkSource,
|
defaultChunkSource,
|
||||||
|
overridingDrmInitData,
|
||||||
allocator,
|
allocator,
|
||||||
positionUs,
|
positionUs,
|
||||||
muxedAudioFormat,
|
muxedAudioFormat,
|
||||||
@ -688,6 +725,32 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
eventDispatcher);
|
eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, DrmInitData> deriveOverridingDrmInitData(
|
||||||
|
List<DrmInitData> sessionKeyDrmInitData) {
|
||||||
|
ArrayList<DrmInitData> mutableSessionKeyDrmInitData = new ArrayList<>(sessionKeyDrmInitData);
|
||||||
|
HashMap<String, DrmInitData> drmInitDataBySchemeType = new HashMap<>();
|
||||||
|
for (int i = 0; i < mutableSessionKeyDrmInitData.size(); i++) {
|
||||||
|
DrmInitData drmInitData = sessionKeyDrmInitData.get(i);
|
||||||
|
String scheme = drmInitData.schemeType;
|
||||||
|
// Merge any subsequent drmInitData instances that have the same scheme type. This is valid
|
||||||
|
// due to the assumptions documented on HlsMediaSource.Builder.setUseSessionKeys, and is
|
||||||
|
// necessary to get data for different CDNs (e.g. Widevine and PlayReady) into a single
|
||||||
|
// drmInitData.
|
||||||
|
int j = i + 1;
|
||||||
|
while (j < mutableSessionKeyDrmInitData.size()) {
|
||||||
|
DrmInitData nextDrmInitData = mutableSessionKeyDrmInitData.get(j);
|
||||||
|
if (TextUtils.equals(nextDrmInitData.schemeType, scheme)) {
|
||||||
|
drmInitData = drmInitData.merge(nextDrmInitData);
|
||||||
|
mutableSessionKeyDrmInitData.remove(j);
|
||||||
|
} else {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drmInitDataBySchemeType.put(scheme, drmInitData);
|
||||||
|
}
|
||||||
|
return drmInitDataBySchemeType;
|
||||||
|
}
|
||||||
|
|
||||||
private static Format deriveVideoFormat(Format variantFormat) {
|
private static Format deriveVideoFormat(Format variantFormat) {
|
||||||
String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
|
String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
|
||||||
String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
|
String sampleMimeType = MimeTypes.getMediaMimeType(codecs);
|
||||||
|
@ -67,6 +67,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private boolean allowChunklessPreparation;
|
private boolean allowChunklessPreparation;
|
||||||
|
private boolean useSessionKeys;
|
||||||
private boolean isCreateCalled;
|
private boolean isCreateCalled;
|
||||||
@Nullable private Object tag;
|
@Nullable private Object tag;
|
||||||
|
|
||||||
@ -236,6 +237,20 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether to use #EXT-X-SESSION-KEY tags provided in the master playlist. If enabled, it's
|
||||||
|
* assumed that any single session key declared in the master playlist can be used to obtain all
|
||||||
|
* of the keys required for playback. For media where this is not true, this option should not
|
||||||
|
* be enabled.
|
||||||
|
*
|
||||||
|
* @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags.
|
||||||
|
* @return This factory, for convenience.
|
||||||
|
*/
|
||||||
|
public Factory setUseSessionKeys(boolean useSessionKeys) {
|
||||||
|
this.useSessionKeys = useSessionKeys;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new {@link HlsMediaSource} using the current parameters.
|
* Returns a new {@link HlsMediaSource} using the current parameters.
|
||||||
*
|
*
|
||||||
@ -257,6 +272,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
playlistTrackerFactory.createTracker(
|
playlistTrackerFactory.createTracker(
|
||||||
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
||||||
allowChunklessPreparation,
|
allowChunklessPreparation,
|
||||||
|
useSessionKeys,
|
||||||
tag);
|
tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,6 +305,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private final boolean allowChunklessPreparation;
|
private final boolean allowChunklessPreparation;
|
||||||
|
private final boolean useSessionKeys;
|
||||||
private final HlsPlaylistTracker playlistTracker;
|
private final HlsPlaylistTracker playlistTracker;
|
||||||
private final @Nullable Object tag;
|
private final @Nullable Object tag;
|
||||||
|
|
||||||
@ -302,6 +319,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
HlsPlaylistTracker playlistTracker,
|
HlsPlaylistTracker playlistTracker,
|
||||||
boolean allowChunklessPreparation,
|
boolean allowChunklessPreparation,
|
||||||
|
boolean useSessionKeys,
|
||||||
@Nullable Object tag) {
|
@Nullable Object tag) {
|
||||||
this.manifestUri = manifestUri;
|
this.manifestUri = manifestUri;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
@ -310,6 +328,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
this.playlistTracker = playlistTracker;
|
this.playlistTracker = playlistTracker;
|
||||||
this.allowChunklessPreparation = allowChunklessPreparation;
|
this.allowChunklessPreparation = allowChunklessPreparation;
|
||||||
|
this.useSessionKeys = useSessionKeys;
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +362,8 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
eventDispatcher,
|
eventDispatcher,
|
||||||
allocator,
|
allocator,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
allowChunklessPreparation);
|
allowChunklessPreparation,
|
||||||
|
useSessionKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
@ -52,6 +53,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides
|
* Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides
|
||||||
@ -102,6 +104,7 @@ import java.util.List;
|
|||||||
private final Runnable onTracksEndedRunnable;
|
private final Runnable onTracksEndedRunnable;
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
||||||
|
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||||
|
|
||||||
private SampleQueue[] sampleQueues;
|
private SampleQueue[] sampleQueues;
|
||||||
private int[] sampleQueueTrackIds;
|
private int[] sampleQueueTrackIds;
|
||||||
@ -144,6 +147,10 @@ import java.util.List;
|
|||||||
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||||
* @param callback A callback for the wrapper.
|
* @param callback A callback for the wrapper.
|
||||||
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
|
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
|
||||||
|
* @param overridingDrmInitData Overriding {@link DrmInitData}, keyed by protection scheme type
|
||||||
|
* (i.e. {@link DrmInitData#schemeType}). If the stream has {@link DrmInitData} and uses a
|
||||||
|
* protection scheme type for which overriding {@link DrmInitData} is provided, then the
|
||||||
|
* stream's {@link DrmInitData} will be overridden.
|
||||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||||
* @param positionUs The position from which to start loading media.
|
* @param positionUs The position from which to start loading media.
|
||||||
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist.
|
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist.
|
||||||
@ -154,6 +161,7 @@ import java.util.List;
|
|||||||
int trackType,
|
int trackType,
|
||||||
Callback callback,
|
Callback callback,
|
||||||
HlsChunkSource chunkSource,
|
HlsChunkSource chunkSource,
|
||||||
|
Map<String, DrmInitData> overridingDrmInitData,
|
||||||
Allocator allocator,
|
Allocator allocator,
|
||||||
long positionUs,
|
long positionUs,
|
||||||
Format muxedAudioFormat,
|
Format muxedAudioFormat,
|
||||||
@ -162,6 +170,7 @@ import java.util.List;
|
|||||||
this.trackType = trackType;
|
this.trackType = trackType;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.chunkSource = chunkSource;
|
this.chunkSource = chunkSource;
|
||||||
|
this.overridingDrmInitData = overridingDrmInitData;
|
||||||
this.allocator = allocator;
|
this.allocator = allocator;
|
||||||
this.muxedAudioFormat = muxedAudioFormat;
|
this.muxedAudioFormat = muxedAudioFormat;
|
||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
@ -484,18 +493,28 @@ import java.util.List;
|
|||||||
int result =
|
int result =
|
||||||
sampleQueues[sampleQueueIndex].read(
|
sampleQueues[sampleQueueIndex].read(
|
||||||
formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs);
|
formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs);
|
||||||
if (result == C.RESULT_FORMAT_READ && sampleQueueIndex == primarySampleQueueIndex) {
|
if (result == C.RESULT_FORMAT_READ) {
|
||||||
// Fill in primary sample format with information from the track format.
|
Format format = formatHolder.format;
|
||||||
int chunkUid = sampleQueues[sampleQueueIndex].peekSourceId();
|
if (sampleQueueIndex == primarySampleQueueIndex) {
|
||||||
int chunkIndex = 0;
|
// Fill in primary sample format with information from the track format.
|
||||||
while (chunkIndex < mediaChunks.size() && mediaChunks.get(chunkIndex).uid != chunkUid) {
|
int chunkUid = sampleQueues[sampleQueueIndex].peekSourceId();
|
||||||
chunkIndex++;
|
int chunkIndex = 0;
|
||||||
|
while (chunkIndex < mediaChunks.size() && mediaChunks.get(chunkIndex).uid != chunkUid) {
|
||||||
|
chunkIndex++;
|
||||||
|
}
|
||||||
|
Format trackFormat =
|
||||||
|
chunkIndex < mediaChunks.size()
|
||||||
|
? mediaChunks.get(chunkIndex).trackFormat
|
||||||
|
: upstreamTrackFormat;
|
||||||
|
format = format.copyWithManifestFormatInfo(trackFormat);
|
||||||
}
|
}
|
||||||
Format trackFormat =
|
if (format.drmInitData != null) {
|
||||||
chunkIndex < mediaChunks.size()
|
DrmInitData drmInitData = overridingDrmInitData.get(format.drmInitData.schemeType);
|
||||||
? mediaChunks.get(chunkIndex).trackFormat
|
if (drmInitData != null) {
|
||||||
: upstreamTrackFormat;
|
format = format.copyWithDrmInitData(drmInitData);
|
||||||
formatHolder.format = formatHolder.format.copyWithManifestFormatInfo(trackFormat);
|
}
|
||||||
|
}
|
||||||
|
formatHolder.format = format;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.source.hls.playlist;
|
package com.google.android.exoplayer2.source.hls.playlist;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -37,7 +38,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
/* muxedAudioFormat= */ null,
|
/* muxedAudioFormat= */ null,
|
||||||
/* muxedCaptionFormats= */ Collections.emptyList(),
|
/* muxedCaptionFormats= */ Collections.emptyList(),
|
||||||
/* hasIndependentSegments= */ false,
|
/* hasIndependentSegments= */ false,
|
||||||
/* variableDefinitions= */ Collections.emptyMap());
|
/* variableDefinitions= */ Collections.emptyMap(),
|
||||||
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
|
|
||||||
public static final int GROUP_INDEX_VARIANT = 0;
|
public static final int GROUP_INDEX_VARIANT = 0;
|
||||||
public static final int GROUP_INDEX_AUDIO = 1;
|
public static final int GROUP_INDEX_AUDIO = 1;
|
||||||
@ -122,6 +124,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
public final List<Format> muxedCaptionFormats;
|
public final List<Format> muxedCaptionFormats;
|
||||||
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
|
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
|
||||||
public final Map<String, String> variableDefinitions;
|
public final Map<String, String> variableDefinitions;
|
||||||
|
/** DRM initialization data derived from #EXT-X-SESSION-KEY tags. */
|
||||||
|
public final List<DrmInitData> sessionKeyDrmInitData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param baseUri See {@link #baseUri}.
|
* @param baseUri See {@link #baseUri}.
|
||||||
@ -133,6 +137,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
|
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
|
||||||
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
|
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
|
||||||
* @param variableDefinitions See {@link #variableDefinitions}.
|
* @param variableDefinitions See {@link #variableDefinitions}.
|
||||||
|
* @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}.
|
||||||
*/
|
*/
|
||||||
public HlsMasterPlaylist(
|
public HlsMasterPlaylist(
|
||||||
String baseUri,
|
String baseUri,
|
||||||
@ -143,7 +148,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
Format muxedAudioFormat,
|
Format muxedAudioFormat,
|
||||||
List<Format> muxedCaptionFormats,
|
List<Format> muxedCaptionFormats,
|
||||||
boolean hasIndependentSegments,
|
boolean hasIndependentSegments,
|
||||||
Map<String, String> variableDefinitions) {
|
Map<String, String> variableDefinitions,
|
||||||
|
List<DrmInitData> sessionKeyDrmInitData) {
|
||||||
super(baseUri, tags, hasIndependentSegments);
|
super(baseUri, tags, hasIndependentSegments);
|
||||||
this.variants = Collections.unmodifiableList(variants);
|
this.variants = Collections.unmodifiableList(variants);
|
||||||
this.audios = Collections.unmodifiableList(audios);
|
this.audios = Collections.unmodifiableList(audios);
|
||||||
@ -152,6 +158,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
this.muxedCaptionFormats = muxedCaptionFormats != null
|
this.muxedCaptionFormats = muxedCaptionFormats != null
|
||||||
? Collections.unmodifiableList(muxedCaptionFormats) : null;
|
? Collections.unmodifiableList(muxedCaptionFormats) : null;
|
||||||
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
|
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
|
||||||
|
this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -165,7 +172,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
muxedAudioFormat,
|
muxedAudioFormat,
|
||||||
muxedCaptionFormats,
|
muxedCaptionFormats,
|
||||||
hasIndependentSegments,
|
hasIndependentSegments,
|
||||||
variableDefinitions);
|
variableDefinitions,
|
||||||
|
sessionKeyDrmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +194,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
/* muxedAudioFormat= */ null,
|
/* muxedAudioFormat= */ null,
|
||||||
/* muxedCaptionFormats= */ null,
|
/* muxedCaptionFormats= */ null,
|
||||||
/* hasIndependentSegments= */ false,
|
/* hasIndependentSegments= */ false,
|
||||||
/* variableDefinitions= */ Collections.emptyMap());
|
/* variableDefinitions= */ Collections.emptyMap(),
|
||||||
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<HlsUrl> copyRenditionsList(
|
private static List<HlsUrl> copyRenditionsList(
|
||||||
|
@ -34,7 +34,6 @@ import java.io.BufferedReader;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -73,6 +72,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
private static final String TAG_START = "#EXT-X-START";
|
private static final String TAG_START = "#EXT-X-START";
|
||||||
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
|
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
|
||||||
private static final String TAG_KEY = "#EXT-X-KEY";
|
private static final String TAG_KEY = "#EXT-X-KEY";
|
||||||
|
private static final String TAG_SESSION_KEY = "#EXT-X-SESSION-KEY";
|
||||||
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
|
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
|
||||||
private static final String TAG_GAP = "#EXT-X-GAP";
|
private static final String TAG_GAP = "#EXT-X-GAP";
|
||||||
|
|
||||||
@ -253,6 +253,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
|
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
|
||||||
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
|
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
|
||||||
ArrayList<String> mediaTags = new ArrayList<>();
|
ArrayList<String> mediaTags = new ArrayList<>();
|
||||||
|
ArrayList<DrmInitData> sessionKeyDrmInitData = new ArrayList<>();
|
||||||
ArrayList<String> tags = new ArrayList<>();
|
ArrayList<String> tags = new ArrayList<>();
|
||||||
Format muxedAudioFormat = null;
|
Format muxedAudioFormat = null;
|
||||||
List<Format> muxedCaptionFormats = null;
|
List<Format> muxedCaptionFormats = null;
|
||||||
@ -278,6 +279,15 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
// Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF
|
// Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF
|
||||||
// tags.
|
// tags.
|
||||||
mediaTags.add(line);
|
mediaTags.add(line);
|
||||||
|
} else if (line.startsWith(TAG_SESSION_KEY)) {
|
||||||
|
String keyFormat =
|
||||||
|
parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions);
|
||||||
|
SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
|
||||||
|
if (schemeData != null) {
|
||||||
|
String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
|
||||||
|
String scheme = parseEncryptionScheme(method);
|
||||||
|
sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData));
|
||||||
|
}
|
||||||
} else if (line.startsWith(TAG_STREAM_INF)) {
|
} else if (line.startsWith(TAG_STREAM_INF)) {
|
||||||
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
|
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
|
||||||
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
||||||
@ -423,6 +433,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
if (noClosedCaptions) {
|
if (noClosedCaptions) {
|
||||||
muxedCaptionFormats = Collections.emptyList();
|
muxedCaptionFormats = Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HlsMasterPlaylist(
|
return new HlsMasterPlaylist(
|
||||||
baseUri,
|
baseUri,
|
||||||
tags,
|
tags,
|
||||||
@ -432,7 +443,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
muxedAudioFormat,
|
muxedAudioFormat,
|
||||||
muxedCaptionFormats,
|
muxedCaptionFormats,
|
||||||
hasIndependentSegmentsTag,
|
hasIndependentSegmentsTag,
|
||||||
variableDefinitions);
|
variableDefinitions,
|
||||||
|
sessionKeyDrmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HlsMediaPlaylist parseMediaPlaylist(
|
private static HlsMediaPlaylist parseMediaPlaylist(
|
||||||
@ -557,17 +569,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (encryptionScheme == null) {
|
if (encryptionScheme == null) {
|
||||||
encryptionScheme =
|
encryptionScheme = parseEncryptionScheme(method);
|
||||||
METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method)
|
|
||||||
? C.CENC_TYPE_cenc
|
|
||||||
: C.CENC_TYPE_cbcs;
|
|
||||||
}
|
|
||||||
SchemeData schemeData;
|
|
||||||
if (KEYFORMAT_PLAYREADY.equals(keyFormat)) {
|
|
||||||
schemeData = parsePlayReadySchemeData(line, variableDefinitions);
|
|
||||||
} else {
|
|
||||||
schemeData = parseWidevineSchemeData(line, keyFormat, variableDefinitions);
|
|
||||||
}
|
}
|
||||||
|
SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
|
||||||
if (schemeData != null) {
|
if (schemeData != null) {
|
||||||
cachedDrmInitData = null;
|
cachedDrmInitData = null;
|
||||||
currentSchemeDatas.put(keyFormat, schemeData);
|
currentSchemeDatas.put(keyFormat, schemeData);
|
||||||
@ -713,40 +717,35 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
: Format.NO_VALUE;
|
: Format.NO_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable SchemeData parsePlayReadySchemeData(
|
@Nullable
|
||||||
String line, Map<String, String> variableDefinitions) throws ParserException {
|
private static SchemeData parseDrmSchemeData(
|
||||||
String keyFormatVersions =
|
|
||||||
parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions);
|
|
||||||
if (!"1".equals(keyFormatVersions)) {
|
|
||||||
// Not supported.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
|
||||||
byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT);
|
|
||||||
byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data);
|
|
||||||
return new SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @Nullable SchemeData parseWidevineSchemeData(
|
|
||||||
String line, String keyFormat, Map<String, String> variableDefinitions)
|
String line, String keyFormat, Map<String, String> variableDefinitions)
|
||||||
throws ParserException {
|
throws ParserException {
|
||||||
|
String keyFormatVersions =
|
||||||
|
parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions);
|
||||||
if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) {
|
if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) {
|
||||||
String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
||||||
return new SchemeData(
|
return new SchemeData(
|
||||||
C.WIDEVINE_UUID,
|
C.WIDEVINE_UUID,
|
||||||
MimeTypes.VIDEO_MP4,
|
MimeTypes.VIDEO_MP4,
|
||||||
Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT));
|
Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT));
|
||||||
}
|
} else if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) {
|
||||||
if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) {
|
return new SchemeData(C.WIDEVINE_UUID, "hls", Util.getUtf8Bytes(line));
|
||||||
try {
|
} else if (KEYFORMAT_PLAYREADY.equals(keyFormat) && "1".equals(keyFormatVersions)) {
|
||||||
return new SchemeData(C.WIDEVINE_UUID, "hls", line.getBytes(C.UTF8_NAME));
|
String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
||||||
} catch (UnsupportedEncodingException e) {
|
byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT);
|
||||||
throw new ParserException(e);
|
byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data);
|
||||||
}
|
return new SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String parseEncryptionScheme(String method) {
|
||||||
|
return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method)
|
||||||
|
? C.CENC_TYPE_cenc
|
||||||
|
: C.CENC_TYPE_cbcs;
|
||||||
|
}
|
||||||
|
|
||||||
private static int parseIntAttr(String line, Pattern pattern) throws ParserException {
|
private static int parseIntAttr(String line, Pattern pattern) throws ParserException {
|
||||||
return Integer.parseInt(parseStringAttr(line, pattern, Collections.emptyMap()));
|
return Integer.parseInt(parseStringAttr(line, pattern, Collections.emptyMap()));
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,8 @@ public final class HlsMediaPeriodTest {
|
|||||||
/* mediaTimeOffsetMs= */ 0),
|
/* mediaTimeOffsetMs= */ 0),
|
||||||
mock(Allocator.class),
|
mock(Allocator.class),
|
||||||
mock(CompositeSequenceableLoaderFactory.class),
|
mock(CompositeSequenceableLoaderFactory.class),
|
||||||
/* allowChunklessPreparation =*/ true);
|
/* allowChunklessPreparation =*/ true,
|
||||||
|
/* useSessionKeys= */ false);
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
|
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
|
||||||
@ -110,7 +111,8 @@ public final class HlsMediaPeriodTest {
|
|||||||
muxedAudioFormat,
|
muxedAudioFormat,
|
||||||
muxedCaptionFormats,
|
muxedCaptionFormats,
|
||||||
/* hasIndependentSegments= */ true,
|
/* hasIndependentSegments= */ true,
|
||||||
/* variableDefinitions= */ Collections.emptyMap());
|
/* variableDefinitions= */ Collections.emptyMap(),
|
||||||
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HlsUrl createMuxedVideoAudioVariantHlsUrl(int bitrate) {
|
private static HlsUrl createMuxedVideoAudioVariantHlsUrl(int bitrate) {
|
||||||
|
@ -456,7 +456,8 @@ public class HlsMediaPlaylistParserTest {
|
|||||||
/* muxedAudioFormat= */ null,
|
/* muxedAudioFormat= */ null,
|
||||||
/* muxedCaptionFormats= */ null,
|
/* muxedCaptionFormats= */ null,
|
||||||
/* hasIndependentSegments= */ true,
|
/* hasIndependentSegments= */ true,
|
||||||
/* variableDefinitions */ Collections.emptyMap());
|
/* variableDefinitions= */ Collections.emptyMap(),
|
||||||
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
HlsMediaPlaylist playlistWithInheritance =
|
HlsMediaPlaylist playlistWithInheritance =
|
||||||
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
|
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
|
||||||
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
|
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
|
||||||
@ -515,7 +516,8 @@ public class HlsMediaPlaylistParserTest {
|
|||||||
/* muxedAudioFormat= */ null,
|
/* muxedAudioFormat= */ null,
|
||||||
/* muxedCaptionFormats= */ Collections.emptyList(),
|
/* muxedCaptionFormats= */ Collections.emptyList(),
|
||||||
/* hasIndependentSegments= */ false,
|
/* hasIndependentSegments= */ false,
|
||||||
variableDefinitions);
|
variableDefinitions,
|
||||||
|
/* sessionKeyDrmInitData= */ Collections.emptyList());
|
||||||
HlsMediaPlaylist playlist =
|
HlsMediaPlaylist playlist =
|
||||||
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
|
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
|
||||||
for (int i = 1; i <= 4; i++) {
|
for (int i = 1; i <= 4; i++) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user