Support providing all keys via EXT-X-SESSION-KEY tags

PiperOrigin-RevId: 240333415
This commit is contained in:
olly 2019-03-26 13:11:55 +00:00 committed by Oliver Woodman
parent c04f5b9c5d
commit 07e3509dd9
9 changed files with 217 additions and 64 deletions

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.drm;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
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);
}
/**
* 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
public int hashCode() {
if (hashCode == 0) {

View File

@ -314,6 +314,25 @@ public final class Util {
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
* Looper} thread. The method accepts partially initialized objects as callback under the

View File

@ -16,9 +16,11 @@
package com.google.android.exoplayer2.source.hls;
import androidx.annotation.Nullable;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
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.offline.StreamKey;
import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
@ -43,9 +45,11 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* 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 CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final boolean allowChunklessPreparation;
private final boolean useSessionKeys;
private @Nullable Callback callback;
private int pendingPrepareCount;
@ -90,6 +95,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
* @param compositeSequenceableLoaderFactory A factory to create composite {@link
* SequenceableLoader}s for when this media source loads data from multiple streams.
* @param allowChunklessPreparation Whether chunkless preparation is allowed.
* @param useSessionKeys Whether to use #EXT-X-SESSION-KEY tags.
*/
public HlsMediaPeriod(
HlsExtractorFactory extractorFactory,
@ -100,7 +106,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
EventDispatcher eventDispatcher,
Allocator allocator,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
boolean allowChunklessPreparation) {
boolean allowChunklessPreparation,
boolean useSessionKeys) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory;
@ -110,6 +117,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
this.allocator = allocator;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.allowChunklessPreparation = allowChunklessPreparation;
this.useSessionKeys = useSessionKeys;
compositeSequenceableLoader =
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader();
streamWrapperIndices = new IdentityHashMap<>();
@ -427,7 +435,12 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
// Internal methods.
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();
List<HlsUrl> audioRenditions = masterPlaylist.audios;
List<HlsUrl> subtitleRenditions = masterPlaylist.subtitles;
@ -438,20 +451,33 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
if (hasVariants) {
buildAndPrepareMainSampleStreamWrapper(
masterPlaylist, positionUs, sampleStreamWrappers, manifestUrlIndicesPerWrapper);
masterPlaylist,
positionUs,
sampleStreamWrappers,
manifestUrlIndicesPerWrapper,
overridingDrmInitData);
}
// TODO: Build video stream wrappers here.
buildAndPrepareAudioSampleStreamWrappers(
positionUs, audioRenditions, sampleStreamWrappers, manifestUrlIndicesPerWrapper);
positionUs,
audioRenditions,
sampleStreamWrappers,
manifestUrlIndicesPerWrapper,
overridingDrmInitData);
// Subtitle stream wrappers. We can always use master playlist information to prepare these.
for (int i = 0; i < subtitleRenditions.size(); i++) {
HlsUrl url = subtitleRenditions.get(i);
HlsSampleStreamWrapper sampleStreamWrapper =
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});
sampleStreamWrappers.add(sampleStreamWrapper);
sampleStreamWrapper.prepareWithMasterPlaylistInfo(
@ -495,12 +521,15 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
* which downloading should start. Ignored otherwise.
* @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 overridingDrmInitData Overriding {@link DrmInitData}, keyed by protection scheme type
* (i.e. {@link DrmInitData#schemeType}).
*/
private void buildAndPrepareMainSampleStreamWrapper(
HlsMasterPlaylist masterPlaylist,
long positionUs,
List<HlsSampleStreamWrapper> sampleStreamWrappers,
List<int[]> manifestUrlIndicesPerWrapper) {
List<int[]> manifestUrlIndicesPerWrapper,
Map<String, DrmInitData> overridingDrmInitData) {
int[] variantTypes = new int[masterPlaylist.variants.size()];
int videoVariantCount = 0;
int audioVariantCount = 0;
@ -549,6 +578,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
selectedVariants,
masterPlaylist.muxedAudioFormat,
masterPlaylist.muxedCaptionFormats,
overridingDrmInitData,
positionUs);
sampleStreamWrappers.add(sampleStreamWrapper);
manifestUrlIndicesPerWrapper.add(selectedVariantIndices);
@ -616,8 +646,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
long positionUs,
List<HlsUrl> audioRenditions,
List<HlsSampleStreamWrapper> sampleStreamWrappers,
List<int[]> manifestUrlsIndicesPerWrapper) {
List<int[]> manifestUrlsIndicesPerWrapper,
Map<String, DrmInitData> overridingDrmInitData) {
ArrayList<HlsUrl> scratchRenditionList =
new ArrayList<>(/* initialCapacity= */ audioRenditions.size());
ArrayList<Integer> scratchIndicesList =
@ -651,6 +681,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
scratchRenditionList.toArray(new HlsUrl[0]),
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
overridingDrmInitData,
positionUs);
manifestUrlsIndicesPerWrapper.add(Util.toArray(scratchIndicesList));
sampleStreamWrappers.add(sampleStreamWrapper);
@ -666,8 +697,13 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
}
}
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
private HlsSampleStreamWrapper buildSampleStreamWrapper(
int trackType,
HlsUrl[] variants,
Format muxedAudioFormat,
List<Format> muxedCaptionFormats,
Map<String, DrmInitData> overridingDrmInitData,
long positionUs) {
HlsChunkSource defaultChunkSource =
new HlsChunkSource(
extractorFactory,
@ -681,6 +717,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
trackType,
/* callback= */ this,
defaultChunkSource,
overridingDrmInitData,
allocator,
positionUs,
muxedAudioFormat,
@ -688,6 +725,32 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
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) {
String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
String sampleMimeType = MimeTypes.getMediaMimeType(codecs);

View File

@ -67,6 +67,7 @@ public final class HlsMediaSource extends BaseMediaSource
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private boolean allowChunklessPreparation;
private boolean useSessionKeys;
private boolean isCreateCalled;
@Nullable private Object tag;
@ -236,6 +237,20 @@ public final class HlsMediaSource extends BaseMediaSource
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.
*
@ -257,6 +272,7 @@ public final class HlsMediaSource extends BaseMediaSource
playlistTrackerFactory.createTracker(
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
allowChunklessPreparation,
useSessionKeys,
tag);
}
@ -289,6 +305,7 @@ public final class HlsMediaSource extends BaseMediaSource
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private final boolean allowChunklessPreparation;
private final boolean useSessionKeys;
private final HlsPlaylistTracker playlistTracker;
private final @Nullable Object tag;
@ -302,6 +319,7 @@ public final class HlsMediaSource extends BaseMediaSource
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
HlsPlaylistTracker playlistTracker,
boolean allowChunklessPreparation,
boolean useSessionKeys,
@Nullable Object tag) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
@ -310,6 +328,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
this.playlistTracker = playlistTracker;
this.allowChunklessPreparation = allowChunklessPreparation;
this.useSessionKeys = useSessionKeys;
this.tag = tag;
}
@ -343,7 +362,8 @@ public final class HlsMediaSource extends BaseMediaSource
eventDispatcher,
allocator,
compositeSequenceableLoaderFactory,
allowChunklessPreparation);
allowChunklessPreparation,
useSessionKeys);
}
@Override

View File

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
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.ExtractorOutput;
import com.google.android.exoplayer2.extractor.SeekMap;
@ -52,6 +53,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 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 Handler handler;
private final ArrayList<HlsSampleStream> hlsSampleStreams;
private final Map<String, DrmInitData> overridingDrmInitData;
private SampleQueue[] sampleQueues;
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 callback A callback for the wrapper.
* @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 positionUs The position from which to start loading media.
* @param muxedAudioFormat Optional muxed audio {@link Format} as defined by the master playlist.
@ -154,6 +161,7 @@ import java.util.List;
int trackType,
Callback callback,
HlsChunkSource chunkSource,
Map<String, DrmInitData> overridingDrmInitData,
Allocator allocator,
long positionUs,
Format muxedAudioFormat,
@ -162,6 +170,7 @@ import java.util.List;
this.trackType = trackType;
this.callback = callback;
this.chunkSource = chunkSource;
this.overridingDrmInitData = overridingDrmInitData;
this.allocator = allocator;
this.muxedAudioFormat = muxedAudioFormat;
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
@ -484,18 +493,28 @@ import java.util.List;
int result =
sampleQueues[sampleQueueIndex].read(
formatHolder, buffer, requireFormat, loadingFinished, lastSeekPositionUs);
if (result == C.RESULT_FORMAT_READ && sampleQueueIndex == primarySampleQueueIndex) {
// Fill in primary sample format with information from the track format.
int chunkUid = sampleQueues[sampleQueueIndex].peekSourceId();
int chunkIndex = 0;
while (chunkIndex < mediaChunks.size() && mediaChunks.get(chunkIndex).uid != chunkUid) {
chunkIndex++;
if (result == C.RESULT_FORMAT_READ) {
Format format = formatHolder.format;
if (sampleQueueIndex == primarySampleQueueIndex) {
// Fill in primary sample format with information from the track format.
int chunkUid = sampleQueues[sampleQueueIndex].peekSourceId();
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 =
chunkIndex < mediaChunks.size()
? mediaChunks.get(chunkIndex).trackFormat
: upstreamTrackFormat;
formatHolder.format = formatHolder.format.copyWithManifestFormatInfo(trackFormat);
if (format.drmInitData != null) {
DrmInitData drmInitData = overridingDrmInitData.get(format.drmInitData.schemeType);
if (drmInitData != null) {
format = format.copyWithDrmInitData(drmInitData);
}
}
formatHolder.format = format;
}
return result;
}

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.hls.playlist;
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.util.MimeTypes;
import java.util.ArrayList;
@ -37,7 +38,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* 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_AUDIO = 1;
@ -122,6 +124,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
public final List<Format> muxedCaptionFormats;
/** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */
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}.
@ -133,6 +137,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param variableDefinitions See {@link #variableDefinitions}.
* @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}.
*/
public HlsMasterPlaylist(
String baseUri,
@ -143,7 +148,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
Format muxedAudioFormat,
List<Format> muxedCaptionFormats,
boolean hasIndependentSegments,
Map<String, String> variableDefinitions) {
Map<String, String> variableDefinitions,
List<DrmInitData> sessionKeyDrmInitData) {
super(baseUri, tags, hasIndependentSegments);
this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios);
@ -152,6 +158,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
this.muxedCaptionFormats = muxedCaptionFormats != null
? Collections.unmodifiableList(muxedCaptionFormats) : null;
this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions);
this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData);
}
@Override
@ -165,7 +172,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
muxedAudioFormat,
muxedCaptionFormats,
hasIndependentSegments,
variableDefinitions);
variableDefinitions,
sessionKeyDrmInitData);
}
/**
@ -186,7 +194,8 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ false,
/* variableDefinitions= */ Collections.emptyMap());
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
private static List<HlsUrl> copyRenditionsList(

View File

@ -34,7 +34,6 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayDeque;
import java.util.ArrayList;
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_ENDLIST = "#EXT-X-ENDLIST";
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_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> subtitles = new ArrayList<>();
ArrayList<String> mediaTags = new ArrayList<>();
ArrayList<DrmInitData> sessionKeyDrmInitData = new ArrayList<>();
ArrayList<String> tags = new ArrayList<>();
Format muxedAudioFormat = 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
// tags.
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)) {
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
@ -423,6 +433,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
if (noClosedCaptions) {
muxedCaptionFormats = Collections.emptyList();
}
return new HlsMasterPlaylist(
baseUri,
tags,
@ -432,7 +443,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
muxedAudioFormat,
muxedCaptionFormats,
hasIndependentSegmentsTag,
variableDefinitions);
variableDefinitions,
sessionKeyDrmInitData);
}
private static HlsMediaPlaylist parseMediaPlaylist(
@ -557,17 +569,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
} else {
if (encryptionScheme == null) {
encryptionScheme =
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);
encryptionScheme = parseEncryptionScheme(method);
}
SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
if (schemeData != null) {
cachedDrmInitData = null;
currentSchemeDatas.put(keyFormat, schemeData);
@ -713,40 +717,35 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
: Format.NO_VALUE;
}
private static @Nullable SchemeData parsePlayReadySchemeData(
String line, Map<String, String> variableDefinitions) throws ParserException {
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(
@Nullable
private static SchemeData parseDrmSchemeData(
String line, String keyFormat, Map<String, String> variableDefinitions)
throws ParserException {
String keyFormatVersions =
parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions);
if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) {
String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions);
return new SchemeData(
C.WIDEVINE_UUID,
MimeTypes.VIDEO_MP4,
Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT));
}
if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) {
try {
return new SchemeData(C.WIDEVINE_UUID, "hls", line.getBytes(C.UTF8_NAME));
} catch (UnsupportedEncodingException e) {
throw new ParserException(e);
}
} else if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) {
return new SchemeData(C.WIDEVINE_UUID, "hls", Util.getUtf8Bytes(line));
} else if (KEYFORMAT_PLAYREADY.equals(keyFormat) && "1".equals(keyFormatVersions)) {
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);
}
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 {
return Integer.parseInt(parseStringAttr(line, pattern, Collections.emptyMap()));
}

View File

@ -88,7 +88,8 @@ public final class HlsMediaPeriodTest {
/* mediaTimeOffsetMs= */ 0),
mock(Allocator.class),
mock(CompositeSequenceableLoaderFactory.class),
/* allowChunklessPreparation =*/ true);
/* allowChunklessPreparation =*/ true,
/* useSessionKeys= */ false);
};
MediaPeriodAsserts.assertGetStreamKeysAndManifestFilterIntegration(
@ -110,7 +111,8 @@ public final class HlsMediaPeriodTest {
muxedAudioFormat,
muxedCaptionFormats,
/* hasIndependentSegments= */ true,
/* variableDefinitions= */ Collections.emptyMap());
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
}
private static HlsUrl createMuxedVideoAudioVariantHlsUrl(int bitrate) {

View File

@ -456,7 +456,8 @@ public class HlsMediaPlaylistParserTest {
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ null,
/* hasIndependentSegments= */ true,
/* variableDefinitions */ Collections.emptyMap());
/* variableDefinitions= */ Collections.emptyMap(),
/* sessionKeyDrmInitData= */ Collections.emptyList());
HlsMediaPlaylist playlistWithInheritance =
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
assertThat(playlistWithInheritance.hasIndependentSegments).isTrue();
@ -515,7 +516,8 @@ public class HlsMediaPlaylistParserTest {
/* muxedAudioFormat= */ null,
/* muxedCaptionFormats= */ Collections.emptyList(),
/* hasIndependentSegments= */ false,
variableDefinitions);
variableDefinitions,
/* sessionKeyDrmInitData= */ Collections.emptyList());
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser(masterPlaylist).parse(playlistUri, inputStream);
for (int i = 1; i <= 4; i++) {