Fix DRM protected SmoothStreaming with subtitles

Issue: #5378
PiperOrigin-RevId: 229261658
This commit is contained in:
olly 2019-01-14 23:02:03 +00:00 committed by Oliver Woodman
parent 66ca43ed1d
commit 546af063d6
8 changed files with 55 additions and 31 deletions

View File

@ -4,6 +4,8 @@
* IMA extension: Clear ads loader listeners on release * IMA extension: Clear ads loader listeners on release
([#4114](https://github.com/google/ExoPlayer/issues/4114)). ([#4114](https://github.com/google/ExoPlayer/issues/4114)).
* SmoothStreaming: Fix support for subtitles in DRM protected streams
([#5378](https://github.com/google/ExoPlayer/issues/5378)).
* FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior * FFmpeg extension: Treat invalid data errors as non-fatal to match the behavior
of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)). of MediaCodec ([#5293](https://github.com/google/ExoPlayer/issues/5293)).
* Fix issue where sending callbacks for playlist changes may cause problems * Fix issue where sending callbacks for playlist changes may cause problems

View File

@ -61,14 +61,13 @@ public class DefaultSsChunkSource implements SsChunkSource {
SsManifest manifest, SsManifest manifest,
int elementIndex, int elementIndex,
TrackSelection trackSelection, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes,
@Nullable TransferListener transferListener) { @Nullable TransferListener transferListener) {
DataSource dataSource = dataSourceFactory.createDataSource(); DataSource dataSource = dataSourceFactory.createDataSource();
if (transferListener != null) { if (transferListener != null) {
dataSource.addTransferListener(transferListener); dataSource.addTransferListener(transferListener);
} }
return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex, return new DefaultSsChunkSource(
trackSelection, dataSource, trackEncryptionBoxes); manifestLoaderErrorThrower, manifest, elementIndex, trackSelection, dataSource);
} }
} }
@ -90,15 +89,13 @@ public class DefaultSsChunkSource implements SsChunkSource {
* @param streamElementIndex The index of the stream element in the manifest. * @param streamElementIndex The index of the stream element in the manifest.
* @param trackSelection The track selection. * @param trackSelection The track selection.
* @param dataSource A {@link DataSource} suitable for loading the media data. * @param dataSource A {@link DataSource} suitable for loading the media data.
* @param trackEncryptionBoxes Track encryption boxes for the stream.
*/ */
public DefaultSsChunkSource( public DefaultSsChunkSource(
LoaderErrorThrower manifestLoaderErrorThrower, LoaderErrorThrower manifestLoaderErrorThrower,
SsManifest manifest, SsManifest manifest,
int streamElementIndex, int streamElementIndex,
TrackSelection trackSelection, TrackSelection trackSelection,
DataSource dataSource, DataSource dataSource) {
TrackEncryptionBox[] trackEncryptionBoxes) {
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
this.manifest = manifest; this.manifest = manifest;
this.streamElementIndex = streamElementIndex; this.streamElementIndex = streamElementIndex;
@ -110,6 +107,8 @@ public class DefaultSsChunkSource implements SsChunkSource {
for (int i = 0; i < extractorWrappers.length; i++) { for (int i = 0; i < extractorWrappers.length; i++) {
int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i); int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i);
Format format = streamElement.formats[manifestTrackIndex]; Format format = streamElement.formats[manifestTrackIndex];
TrackEncryptionBox[] trackEncryptionBoxes =
format.drmInitData != null ? manifest.protectionElement.trackEncryptionBoxes : null;
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0; int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0;
Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale, Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale,
C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE, C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE,

View File

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.source.smoothstreaming; package com.google.android.exoplayer2.source.smoothstreaming;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.chunk.ChunkSource; import com.google.android.exoplayer2.source.chunk.ChunkSource;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
@ -38,7 +37,6 @@ public interface SsChunkSource extends ChunkSource {
* @param manifest The initial manifest. * @param manifest The initial manifest.
* @param streamElementIndex The index of the corresponding stream element in the manifest. * @param streamElementIndex The index of the corresponding stream element in the manifest.
* @param trackSelection The track selection. * @param trackSelection The track selection.
* @param trackEncryptionBoxes Track encryption boxes for the stream.
* @param transferListener The transfer listener which should be informed of any data transfers. * @param transferListener The transfer listener which should be informed of any data transfers.
* May be null if no listener is available. * May be null if no listener is available.
* @return The created {@link SsChunkSource}. * @return The created {@link SsChunkSource}.
@ -48,7 +46,6 @@ public interface SsChunkSource extends ChunkSource {
SsManifest manifest, SsManifest manifest,
int streamElementIndex, int streamElementIndex,
TrackSelection trackSelection, TrackSelection trackSelection,
TrackEncryptionBox[] trackEncryptionBoxes,
@Nullable TransferListener transferListener); @Nullable TransferListener transferListener);
} }

View File

@ -29,7 +29,6 @@ import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream; import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@ -44,8 +43,6 @@ import java.util.ArrayList;
/* package */ final class SsMediaPeriod implements MediaPeriod, /* package */ final class SsMediaPeriod implements MediaPeriod,
SequenceableLoader.Callback<ChunkSampleStream<SsChunkSource>> { SequenceableLoader.Callback<ChunkSampleStream<SsChunkSource>> {
private static final int INITIALIZATION_VECTOR_SIZE = 8;
private final SsChunkSource.Factory chunkSourceFactory; private final SsChunkSource.Factory chunkSourceFactory;
private final @Nullable TransferListener transferListener; private final @Nullable TransferListener transferListener;
private final LoaderErrorThrower manifestLoaderErrorThrower; private final LoaderErrorThrower manifestLoaderErrorThrower;
@ -53,7 +50,6 @@ import java.util.ArrayList;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final Allocator allocator; private final Allocator allocator;
private final TrackGroupArray trackGroups; private final TrackGroupArray trackGroups;
private final TrackEncryptionBox[] trackEncryptionBoxes;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory; private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private @Nullable Callback callback; private @Nullable Callback callback;
@ -71,6 +67,7 @@ import java.util.ArrayList;
EventDispatcher eventDispatcher, EventDispatcher eventDispatcher,
LoaderErrorThrower manifestLoaderErrorThrower, LoaderErrorThrower manifestLoaderErrorThrower,
Allocator allocator) { Allocator allocator) {
this.manifest = manifest;
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
this.transferListener = transferListener; this.transferListener = transferListener;
this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; this.manifestLoaderErrorThrower = manifestLoaderErrorThrower;
@ -78,18 +75,7 @@ import java.util.ArrayList;
this.eventDispatcher = eventDispatcher; this.eventDispatcher = eventDispatcher;
this.allocator = allocator; this.allocator = allocator;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
trackGroups = buildTrackGroups(manifest); trackGroups = buildTrackGroups(manifest);
ProtectionElement protectionElement = manifest.protectionElement;
if (protectionElement != null) {
byte[] keyId = getProtectionElementKeyId(protectionElement.data);
// We assume pattern encryption does not apply.
trackEncryptionBoxes = new TrackEncryptionBox[] {
new TrackEncryptionBox(true, null, INITIALIZATION_VECTOR_SIZE, keyId, 0, 0, null)};
} else {
trackEncryptionBoxes = null;
}
this.manifest = manifest;
sampleStreams = newSampleStreamArray(0); sampleStreams = newSampleStreamArray(0);
compositeSequenceableLoader = compositeSequenceableLoader =
compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams);
@ -229,7 +215,6 @@ import java.util.ArrayList;
manifest, manifest,
streamElementIndex, streamElementIndex,
selection, selection,
trackEncryptionBoxes,
transferListener); transferListener);
return new ChunkSampleStream<>( return new ChunkSampleStream<>(
manifest.streamElements[streamElementIndex].type, manifest.streamElements[streamElementIndex].type,
@ -277,5 +262,4 @@ import java.util.ArrayList;
data[firstPosition] = data[secondPosition]; data[firstPosition] = data[secondPosition];
data[secondPosition] = temp; data[secondPosition] = temp;
} }
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.source.smoothstreaming.manifest;
import android.net.Uri; import android.net.Uri;
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.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.offline.FilterableManifest; import com.google.android.exoplayer2.offline.FilterableManifest;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -41,10 +42,12 @@ public class SsManifest implements FilterableManifest<SsManifest> {
public final UUID uuid; public final UUID uuid;
public final byte[] data; public final byte[] data;
public final TrackEncryptionBox[] trackEncryptionBoxes;
public ProtectionElement(UUID uuid, byte[] data) { public ProtectionElement(UUID uuid, byte[] data, TrackEncryptionBox[] trackEncryptionBoxes) {
this.uuid = uuid; this.uuid = uuid;
this.data = data; this.data = data;
this.trackEncryptionBoxes = trackEncryptionBoxes;
} }
} }

View File

@ -25,6 +25,7 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil;
import com.google.android.exoplayer2.extractor.mp4.TrackEncryptionBox;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
@ -397,9 +398,10 @@ public class SsManifestParser implements ParsingLoadable.Parser<SsManifest> {
public static final String TAG = "Protection"; public static final String TAG = "Protection";
public static final String TAG_PROTECTION_HEADER = "ProtectionHeader"; public static final String TAG_PROTECTION_HEADER = "ProtectionHeader";
public static final String KEY_SYSTEM_ID = "SystemID"; public static final String KEY_SYSTEM_ID = "SystemID";
private static final int INITIALIZATION_VECTOR_SIZE = 8;
private boolean inProtectionHeader; private boolean inProtectionHeader;
private UUID uuid; private UUID uuid;
private byte[] initData; private byte[] initData;
@ -439,7 +441,44 @@ public class SsManifestParser implements ParsingLoadable.Parser<SsManifest> {
@Override @Override
public Object build() { public Object build() {
return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData)); return new ProtectionElement(
uuid, PsshAtomUtil.buildPsshAtom(uuid, initData), buildTrackEncryptionBoxes(initData));
}
private static TrackEncryptionBox[] buildTrackEncryptionBoxes(byte[] initData) {
return new TrackEncryptionBox[] {
new TrackEncryptionBox(
/* isEncrypted= */ true,
/* schemeType= */ null,
INITIALIZATION_VECTOR_SIZE,
getProtectionElementKeyId(initData),
/* defaultEncryptedBlocks= */ 0,
/* defaultClearBlocks= */ 0,
/* defaultInitializationVector= */ null)
};
}
private static byte[] getProtectionElementKeyId(byte[] initData) {
StringBuilder initDataStringBuilder = new StringBuilder();
for (int i = 0; i < initData.length; i += 2) {
initDataStringBuilder.append((char) initData[i]);
}
String initDataString = initDataStringBuilder.toString();
String keyIdString =
initDataString.substring(
initDataString.indexOf("<KID>") + 5, initDataString.indexOf("</KID>"));
byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT);
swap(keyId, 0, 3);
swap(keyId, 1, 2);
swap(keyId, 4, 5);
swap(keyId, 6, 7);
return keyId;
}
private static void swap(byte[] data, int firstPosition, int secondPosition) {
byte temp = data[firstPosition];
data[firstPosition] = data[secondPosition];
data[secondPosition] = temp;
} }
private static String stripCurlyBraces(String uuidString) { private static String stripCurlyBraces(String uuidString) {

View File

@ -3,7 +3,7 @@
Duration="2300000000" TimeScale="10000000"> Duration="2300000000" TimeScale="10000000">
<Protection> <Protection>
<ProtectionHeader SystemID="9A04F079-9840-4286-AB92-E65BE0885F95"> <ProtectionHeader SystemID="9A04F079-9840-4286-AB92-E65BE0885F95">
<!-- Base 64-Encoded data omitted for clarity --> fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
</ProtectionHeader> </ProtectionHeader>
</Protection> </Protection>

View File

@ -3,7 +3,7 @@
Duration="2300000000" TimeScale="10000000"> Duration="2300000000" TimeScale="10000000">
<Protection> <Protection>
<ProtectionHeader SystemID="{9A04F079-9840-4286-AB92-E65BE0885F95}"> <ProtectionHeader SystemID="{9A04F079-9840-4286-AB92-E65BE0885F95}">
<!-- Base 64-Encoded data omitted for clarity --> fgMAAAEAAQB0AzwAVwBSAE0ASABFAEEARABFAFIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiACAAdgBlAHIAcwBpAG8AbgA9ACIANAAuADAALgAwAC4AMAAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsARQBZAEwARQBOAD4AMQA2ADwALwBLAEUAWQBMAEUATgA+ADwAQQBMAEcASQBEAD4AQQBFAFMAQwBUAFIAPAAvAEEATABHAEkARAA+ADwALwBQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAD4AQgBhAFUATQBPAEcAYwBzAGgAVQBDAEQAZAB3ADMANABZAGMAawBmAFoAQQA9AD0APAAvAEsASQBEAD4APABDAEgARQBDAEsAUwBVAE0APgBnADcATgBhAFIARABJAEkATwA5ADAAPQA8AC8AQwBIAEUAQwBLAFMAVQBNAD4APABMAEEAXwBVAFIATAA+AGgAdAB0AHAAcwA6AC8ALwBUAC0ATwBOAEwASQBOAEUALgBEAFUATQBNAFkALQBTAEUAUgBWAEUAUgAvAEEAcgB0AGUAbQBpAHMATABpAGMAZQBuAHMAZQBTAGUAcgB2AGUAcgAvAFAAbABhAHkAUgBlAGEAZAB5AE0AYQBuAGEAZwBlAHIALgBhAHMAbQB4ADwALwBMAEEAXwBVAFIATAA+ADwAQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwAQwBJAEQAPgAxADcANQA4ADIANgA8AC8AQwBJAEQAPgA8AEkASQBTAF8ARABSAE0AXwBWAEUAUgBTAEkATwBOAD4ANwAuADEALgAxADUANgA1AC4ANAA8AC8ASQBJAFMAXwBEAFIATQBfAFYARQBSAFMASQBPAE4APgA8AC8AQwBVAFMAVABPAE0AQQBUAFQAUgBJAEIAVQBUAEUAUwA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A
</ProtectionHeader> </ProtectionHeader>
</Protection> </Protection>