Merge pull request #194 from google/dev

dev -> dev-hls
This commit is contained in:
ojw28 2014-12-08 20:17:17 +00:00
commit 1344b36da9
10 changed files with 94 additions and 36 deletions

View File

@ -279,8 +279,8 @@ public class DashRendererBuilder implements RendererBuilder,
public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player, public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException { MediaDrmCallback drmCallback) throws UnsupportedSchemeException {
StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager( StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager(
DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, player.getMainHandler(), DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, null,
player); player.getMainHandler(), player);
return Pair.create((DrmSessionManager) streamingDrmSessionManager, return Pair.create((DrmSessionManager) streamingDrmSessionManager,
getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1); getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1);
} }

View File

@ -252,7 +252,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player, public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException { MediaDrmCallback drmCallback) throws UnsupportedSchemeException {
return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player); player.getMainHandler(), player);
} }

View File

@ -40,6 +40,17 @@ public final class Mp4MediaChunk extends MediaChunk {
private MediaFormat mediaFormat; private MediaFormat mediaFormat;
private Map<UUID, byte[]> psshInfo; private Map<UUID, byte[]> psshInfo;
/**
* @deprecated Use the other constructor, passing null as {@code psshInfo}.
*/
@Deprecated
public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex,
Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) {
this(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex,
extractor, null, maybeSelfContained, sampleOffsetUs);
}
/** /**
* @param dataSource A {@link DataSource} for loading the data. * @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded. * @param dataSpec Defines the data to be loaded.
@ -49,6 +60,8 @@ public final class Mp4MediaChunk extends MediaChunk {
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param extractor The extractor that will be used to extract the samples. * @param extractor The extractor that will be used to extract the samples.
* @param psshInfo Pssh data. May be null if pssh data is present within the stream, meaning it
* can be obtained directly from {@code extractor}, or if no pssh data is required.
* @param maybeSelfContained Set to true if this chunk might be self contained, meaning it might * @param maybeSelfContained Set to true if this chunk might be self contained, meaning it might
* contain a moov atom defining the media format of the chunk. This parameter can always be * contain a moov atom defining the media format of the chunk. This parameter can always be
* safely set to true. Setting to false where the chunk is known to not be self contained may * safely set to true. Setting to false where the chunk is known to not be self contained may
@ -56,12 +69,13 @@ public final class Mp4MediaChunk extends MediaChunk {
* @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor. * @param sampleOffsetUs An offset to subtract from the sample timestamps parsed by the extractor.
*/ */
public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, public Mp4MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format,
int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, int trigger, long startTimeUs, long endTimeUs, int nextChunkIndex, Extractor extractor,
Extractor extractor, boolean maybeSelfContained, long sampleOffsetUs) { Map<UUID, byte[]> psshInfo, boolean maybeSelfContained, long sampleOffsetUs) {
super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex); super(dataSource, dataSpec, format, trigger, startTimeUs, endTimeUs, nextChunkIndex);
this.extractor = extractor; this.extractor = extractor;
this.maybeSelfContained = maybeSelfContained; this.maybeSelfContained = maybeSelfContained;
this.sampleOffsetUs = sampleOffsetUs; this.sampleOffsetUs = sampleOffsetUs;
this.psshInfo = psshInfo;
} }
@Override @Override
@ -97,7 +111,10 @@ public final class Mp4MediaChunk extends MediaChunk {
} }
if (prepared) { if (prepared) {
mediaFormat = extractor.getFormat(); mediaFormat = extractor.getFormat();
psshInfo = extractor.getPsshInfo(); Map<UUID, byte[]> extractorPsshInfo = extractor.getPsshInfo();
if (extractorPsshInfo != null) {
psshInfo = extractorPsshInfo;
}
} }
} }
return prepared; return prepared;

View File

@ -31,6 +31,7 @@ import com.google.android.exoplayer.chunk.MediaChunk;
import com.google.android.exoplayer.chunk.Mp4MediaChunk; import com.google.android.exoplayer.chunk.Mp4MediaChunk;
import com.google.android.exoplayer.chunk.SingleSampleMediaChunk; import com.google.android.exoplayer.chunk.SingleSampleMediaChunk;
import com.google.android.exoplayer.dash.mpd.AdaptationSet; import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.ContentProtection;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription; import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.Period; import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.RangedUri; import com.google.android.exoplayer.dash.mpd.RangedUri;
@ -53,6 +54,8 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
/** /**
* An {@link ChunkSource} for DASH streams. * An {@link ChunkSource} for DASH streams.
@ -92,6 +95,7 @@ public class DashChunkSource implements ChunkSource {
private final ManifestFetcher<MediaPresentationDescription> manifestFetcher; private final ManifestFetcher<MediaPresentationDescription> manifestFetcher;
private final int adaptationSetIndex; private final int adaptationSetIndex;
private final int[] representationIndices; private final int[] representationIndices;
private final Map<UUID, byte[]> psshInfo;
private MediaPresentationDescription currentManifest; private MediaPresentationDescription currentManifest;
private boolean finishedCurrentManifest; private boolean finishedCurrentManifest;
@ -180,6 +184,7 @@ public class DashChunkSource implements ChunkSource {
this.evaluation = new Evaluation(); this.evaluation = new Evaluation();
this.headerBuilder = new StringBuilder(); this.headerBuilder = new StringBuilder();
psshInfo = getPsshInfo(currentManifest, adaptationSetIndex);
Representation[] representations = getFilteredRepresentations(currentManifest, Representation[] representations = getFilteredRepresentations(currentManifest,
adaptationSetIndex, representationIndices); adaptationSetIndex, representationIndices);
long periodDurationUs = (representations[0].periodDurationMs == TrackRenderer.UNKNOWN_TIME_US) long periodDurationUs = (representations[0].periodDurationMs == TrackRenderer.UNKNOWN_TIME_US)
@ -438,7 +443,7 @@ public class DashChunkSource implements ChunkSource {
startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader); startTimeUs, endTimeUs, nextAbsoluteSegmentNum, null, representationHolder.vttHeader);
} else { } else {
return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs, return new Mp4MediaChunk(dataSource, dataSpec, representation.format, trigger, startTimeUs,
endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, false, endTimeUs, nextAbsoluteSegmentNum, representationHolder.extractor, psshInfo, false,
presentationTimeOffsetUs); presentationTimeOffsetUs);
} }
} }
@ -463,8 +468,8 @@ public class DashChunkSource implements ChunkSource {
private static Representation[] getFilteredRepresentations(MediaPresentationDescription manifest, private static Representation[] getFilteredRepresentations(MediaPresentationDescription manifest,
int adaptationSetIndex, int[] representationIndices) { int adaptationSetIndex, int[] representationIndices) {
List<Representation> representations = AdaptationSet adaptationSet = manifest.periods.get(0).adaptationSets.get(adaptationSetIndex);
manifest.periods.get(0).adaptationSets.get(adaptationSetIndex).representations; List<Representation> representations = adaptationSet.representations;
if (representationIndices == null) { if (representationIndices == null) {
Representation[] filteredRepresentations = new Representation[representations.size()]; Representation[] filteredRepresentations = new Representation[representations.size()];
representations.toArray(filteredRepresentations); representations.toArray(filteredRepresentations);
@ -478,6 +483,22 @@ public class DashChunkSource implements ChunkSource {
} }
} }
private static Map<UUID, byte[]> getPsshInfo(MediaPresentationDescription manifest,
int adaptationSetIndex) {
AdaptationSet adaptationSet = manifest.periods.get(0).adaptationSets.get(adaptationSetIndex);
if (adaptationSet.contentProtections.isEmpty()) {
return null;
} else {
Map<UUID, byte[]> psshInfo = new HashMap<UUID, byte[]>();
for (ContentProtection contentProtection : adaptationSet.contentProtections) {
if (contentProtection.uuid != null && contentProtection.data != null) {
psshInfo.put(contentProtection.uuid, contentProtection.data);
}
}
return psshInfo.isEmpty() ? null : psshInfo;
}
}
private static MediaPresentationDescription buildManifest(List<Representation> representations) { private static MediaPresentationDescription buildManifest(List<Representation> representations) {
Representation firstRepresentation = representations.get(0); Representation firstRepresentation = representations.get(0);
AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations); AdaptationSet adaptationSet = new AdaptationSet(0, AdaptationSet.TYPE_UNKNOWN, representations);

View File

@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer.dash.mpd; package com.google.android.exoplayer.dash.mpd;
import java.util.UUID;
/** /**
* Represents a ContentProtection tag in an AdaptationSet. * Represents a ContentProtection tag in an AdaptationSet.
*/ */
@ -26,10 +28,24 @@ public class ContentProtection {
public final String schemeUriId; public final String schemeUriId;
/** /**
* @param schemeUriId Identifies the content protection scheme. * The UUID of the protection scheme. May be null.
*/ */
public ContentProtection(String schemeUriId) { public final UUID uuid;
/**
* Protection scheme specific data. May be null.
*/
public final byte[] data;
/**
* @param schemeUriId Identifies the content protection scheme.
* @param uuid The UUID of the protection scheme, if known. May be null.
* @param data Protection scheme specific initialization data. May be null.
*/
public ContentProtection(String schemeUriId, UUID uuid, byte[] data) {
this.schemeUriId = schemeUriId; this.schemeUriId = schemeUriId;
this.uuid = uuid;
this.data = data;
} }
} }

View File

@ -257,7 +257,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} }
protected ContentProtection buildContentProtection(String schemeIdUri) { protected ContentProtection buildContentProtection(String schemeIdUri) {
return new ContentProtection(schemeIdUri); return new ContentProtection(schemeIdUri, null, null);
} }
/** /**

View File

@ -30,6 +30,7 @@ import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -61,6 +62,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private final MediaDrm mediaDrm; private final MediaDrm mediaDrm;
private final HashMap<String, String> optionalKeyRequestParameters;
/* package */ final MediaDrmHandler mediaDrmHandler; /* package */ final MediaDrmHandler mediaDrmHandler;
/* package */ final MediaDrmCallback callback; /* package */ final MediaDrmCallback callback;
@ -79,20 +81,33 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
private byte[] schemePsshData; private byte[] schemePsshData;
private byte[] sessionId; private byte[] sessionId;
/**
* @deprecated Use the other constructor, passing null as {@code optionalKeyRequestParameters}.
*/
@Deprecated
public StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback,
Handler eventHandler, EventListener eventListener) throws UnsupportedSchemeException {
this(uuid, playbackLooper, callback, null, eventHandler, eventListener);
}
/** /**
* @param uuid The UUID of the drm scheme. * @param uuid The UUID of the drm scheme.
* @param playbackLooper The looper associated with the media playback thread. Should usually be * @param playbackLooper The looper associated with the media playback thread. Should usually be
* obtained using {@link com.google.android.exoplayer.ExoPlayer#getPlaybackLooper()}. * obtained using {@link com.google.android.exoplayer.ExoPlayer#getPlaybackLooper()}.
* @param callback Performs key and provisioning requests. * @param callback Performs key and provisioning requests.
* @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument
* to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
* @throws UnsupportedSchemeException If the specified DRM scheme is not supported. * @throws UnsupportedSchemeException If the specified DRM scheme is not supported.
*/ */
public StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback, public StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback,
Handler eventHandler, EventListener eventListener) throws UnsupportedSchemeException { HashMap<String, String> optionalKeyRequestParameters, Handler eventHandler,
EventListener eventListener) throws UnsupportedSchemeException {
this.uuid = uuid; this.uuid = uuid;
this.callback = callback; this.callback = callback;
this.optionalKeyRequestParameters = optionalKeyRequestParameters;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
mediaDrm = new MediaDrm(uuid); mediaDrm = new MediaDrm(uuid);
@ -250,7 +265,7 @@ public class StreamingDrmSessionManager implements DrmSessionManager {
KeyRequest keyRequest; KeyRequest keyRequest;
try { try {
keyRequest = mediaDrm.getKeyRequest(sessionId, schemePsshData, mimeType, keyRequest = mediaDrm.getKeyRequest(sessionId, schemePsshData, mimeType,
MediaDrm.KEY_TYPE_STREAMING, null); MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters);
postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget();
} catch (NotProvisionedException e) { } catch (NotProvisionedException e) {
onKeysError(e); onKeysError(e);

View File

@ -189,20 +189,6 @@ public final class FragmentedMp4Extractor implements Extractor {
this.track = track; this.track = track;
} }
/**
* Sideloads pssh information into the extractor, so that it can be read through
* {@link #getPsshInfo()}.
*
* @param uuid The UUID of the scheme for which information is being sideloaded.
* @param data The corresponding data.
*/
public void putPsshInfo(UUID uuid, byte[] data) {
// TODO: This is for SmoothStreaming. Consider using something other than
// FragmentedMp4Extractor.getPsshInfo to obtain the pssh data for that use case, so that we can
// remove this method.
psshData.put(uuid, data);
}
@Override @Override
public Map<UUID, byte[]> getPsshInfo() { public Map<UUID, byte[]> getPsshInfo() {
return psshData.isEmpty() ? null : psshData; return psshData.isEmpty() ? null : psshData;

View File

@ -48,6 +48,8 @@ import java.io.IOException;
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;
import java.util.UUID;
/** /**
* An {@link ChunkSource} for SmoothStreaming. * An {@link ChunkSource} for SmoothStreaming.
@ -69,6 +71,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
private final int maxHeight; private final int maxHeight;
private final SparseArray<FragmentedMp4Extractor> extractors; private final SparseArray<FragmentedMp4Extractor> extractors;
private final Map<UUID, byte[]> psshInfo;
private final SmoothStreamingFormat[] formats; private final SmoothStreamingFormat[] formats;
private SmoothStreamingManifest currentManifest; private SmoothStreamingManifest currentManifest;
@ -140,6 +143,9 @@ public class SmoothStreamingChunkSource implements ChunkSource {
byte[] keyId = getKeyId(protectionElement.data); byte[] keyId = getKeyId(protectionElement.data);
trackEncryptionBoxes = new TrackEncryptionBox[1]; trackEncryptionBoxes = new TrackEncryptionBox[1];
trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId);
psshInfo = Collections.singletonMap(protectionElement.uuid, protectionElement.data);
} else {
psshInfo = null;
} }
int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length; int trackCount = trackIndices != null ? trackIndices.length : streamElement.tracks.length;
@ -163,9 +169,6 @@ public class SmoothStreamingChunkSource implements ChunkSource {
FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME); FragmentedMp4Extractor.WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat, extractor.setTrack(new Track(trackIndex, trackType, streamElement.timescale, mediaFormat,
trackEncryptionBoxes)); trackEncryptionBoxes));
if (protectionElement != null) {
extractor.putPsshInfo(protectionElement.uuid, protectionElement.data);
}
extractors.put(trackIndex, extractor); extractors.put(trackIndex, extractor);
} }
this.maxHeight = maxHeight; this.maxHeight = maxHeight;
@ -296,8 +299,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
Uri uri = streamElement.buildRequestUri(selectedFormat.trackIndex, chunkIndex); Uri uri = streamElement.buildRequestUri(selectedFormat.trackIndex, chunkIndex);
Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null,
extractors.get(Integer.parseInt(selectedFormat.id)), dataSource, currentAbsoluteChunkIndex, extractors.get(Integer.parseInt(selectedFormat.id)), psshInfo, dataSource,
isLastChunk, chunkStartTimeUs, nextChunkStartTimeUs, 0); currentAbsoluteChunkIndex, isLastChunk, chunkStartTimeUs, nextChunkStartTimeUs, 0);
out.chunk = mediaChunk; out.chunk = mediaChunk;
} }
@ -361,7 +364,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
} }
private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey,
Extractor extractor, DataSource dataSource, int chunkIndex, Extractor extractor, Map<UUID, byte[]> psshInfo, DataSource dataSource, int chunkIndex,
boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) { boolean isLast, long chunkStartTimeUs, long nextChunkStartTimeUs, int trigger) {
int nextChunkIndex = isLast ? -1 : chunkIndex + 1; int nextChunkIndex = isLast ? -1 : chunkIndex + 1;
long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs; long nextStartTimeUs = isLast ? -1 : nextChunkStartTimeUs;
@ -370,7 +373,7 @@ public class SmoothStreamingChunkSource implements ChunkSource {
// In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk.
// To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs. // To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs.
return new Mp4MediaChunk(dataSource, dataSpec, formatInfo, trigger, chunkStartTimeUs, return new Mp4MediaChunk(dataSource, dataSpec, formatInfo, trigger, chunkStartTimeUs,
nextStartTimeUs, nextChunkIndex, extractor, false, -chunkStartTimeUs); nextStartTimeUs, nextChunkIndex, extractor, psshInfo, false, -chunkStartTimeUs);
} }
private static byte[] getKeyId(byte[] initData) { private static byte[] getKeyId(byte[] initData) {

View File

@ -274,7 +274,7 @@ public class SmoothStreamingManifest {
String chunkUrl = chunkTemplate String chunkUrl = chunkTemplate
.replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate)) .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].bitrate))
.replace(URL_PLACEHOLDER_START_TIME, Long.toString(chunkStartTimes.get(chunkIndex))); .replace(URL_PLACEHOLDER_START_TIME, Long.toString(chunkStartTimes.get(chunkIndex)));
return baseUri.buildUpon().appendEncodedPath(chunkUrl).build(); return Util.getMergedUri(baseUri, chunkUrl);
} }
} }