Commit 1face387 by ojw28

Merge pull request #204 from google/dev

dev -> dev-hls
parents b8056923 57068a64
...@@ -81,6 +81,9 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -81,6 +81,9 @@ public class DashRendererBuilder implements RendererBuilder,
private static final int SECURITY_LEVEL_1 = 1; private static final int SECURITY_LEVEL_1 = 1;
private static final int SECURITY_LEVEL_3 = 3; private static final int SECURITY_LEVEL_3 = 3;
private static final String AC_3_CODEC = "ac-3";
private static final String E_AC_3_CODEC = "ec-3";
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final String contentId; private final String contentId;
...@@ -160,12 +163,8 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -160,12 +163,8 @@ public class DashRendererBuilder implements RendererBuilder,
// HD streams require L1 security. // HD streams require L1 security.
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection() filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection()
&& !drmSessionManagerData.second; && !drmSessionManagerData.second;
} catch (UnsupportedSchemeException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError( callback.onRenderersError(e);
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e));
} catch (Exception e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e));
return; return;
} }
} }
...@@ -225,15 +224,13 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -225,15 +224,13 @@ public class DashRendererBuilder implements RendererBuilder,
format.audioSamplingRate + "Hz)"); format.audioSamplingRate + "Hz)");
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex, audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS)); new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS));
haveAc3Tracks |= format.mimeType.equals(MimeTypes.AUDIO_AC3) haveAc3Tracks |= AC_3_CODEC.equals(format.codecs) || E_AC_3_CODEC.equals(format.codecs);
|| format.mimeType.equals(MimeTypes.AUDIO_EC3);
} }
// Filter out non-AC-3 tracks if there is an AC-3 track, to avoid having to switch renderers. // Filter out non-AC-3 tracks if there is an AC-3 track, to avoid having to switch renderers.
if (haveAc3Tracks) { if (haveAc3Tracks) {
for (int i = audioRepresentations.size() - 1; i >= 0; i--) { for (int i = audioRepresentations.size() - 1; i >= 0; i--) {
Format format = audioRepresentations.get(i).format; Format format = audioRepresentations.get(i).format;
if (!format.mimeType.equals(MimeTypes.AUDIO_AC3) if (!AC_3_CODEC.equals(format.codecs) && !E_AC_3_CODEC.equals(format.codecs)) {
&& !format.mimeType.equals(MimeTypes.AUDIO_EC3)) {
audioTrackNameList.remove(i); audioTrackNameList.remove(i);
audioChunkSourceList.remove(i); audioChunkSourceList.remove(i);
} }
...@@ -327,12 +324,18 @@ public class DashRendererBuilder implements RendererBuilder, ...@@ -327,12 +324,18 @@ public class DashRendererBuilder implements RendererBuilder,
private static class V18Compat { private static class V18Compat {
public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player, public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException { MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager( StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager(
DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, null, DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player); player.getMainHandler(), player);
return Pair.create((DrmSessionManager) streamingDrmSessionManager, return Pair.create((DrmSessionManager) streamingDrmSessionManager,
getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1); getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e);
}
} }
private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) { private static int getWidevineSecurityLevel(StreamingDrmSessionManager sessionManager) {
......
...@@ -118,12 +118,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -118,12 +118,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
try { try {
drmSessionManager = V18Compat.getDrmSessionManager(manifest.protectionElement.uuid, player, drmSessionManager = V18Compat.getDrmSessionManager(manifest.protectionElement.uuid, player,
drmCallback); drmCallback);
} catch (UnsupportedSchemeException e) { } catch (UnsupportedDrmException e) {
callback.onRenderersError( callback.onRenderersError(e);
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e));
} catch (Exception e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e));
return; return;
} }
} }
...@@ -259,9 +255,15 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder, ...@@ -259,9 +255,15 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private static class V18Compat { private static class V18Compat {
public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player, public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException { MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null, return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player); player.getMainHandler(), player);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e);
}
} }
} }
......
...@@ -136,7 +136,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer { ...@@ -136,7 +136,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
} }
private static boolean handlesMimeType(String mimeType) { private static boolean handlesMimeType(String mimeType) {
return MimeTypes.AUDIO_AC3.equals(mimeType) || MimeTypes.AUDIO_EC3.equals(mimeType); return MimeTypes.AUDIO_MP4.equals(mimeType);
} }
@Override @Override
......
...@@ -641,7 +641,8 @@ public final class AudioTrack { ...@@ -641,7 +641,8 @@ public final class AudioTrack {
private long bytesToFrames(long byteCount) { private long bytesToFrames(long byteCount) {
if (isAc3) { if (isAc3) {
return byteCount * 8 * sampleRate / (1000 * ac3Bitrate); return
ac3Bitrate == UNKNOWN_AC3_BITRATE ? 0L : byteCount * 8 * sampleRate / (1000 * ac3Bitrate);
} else { } else {
return byteCount / frameSize; return byteCount / frameSize;
} }
......
...@@ -47,6 +47,11 @@ public class Format { ...@@ -47,6 +47,11 @@ public class Format {
public final String mimeType; public final String mimeType;
/** /**
* The codecs used to decode the format, or {@code null} if they are not specified.
*/
public final String codecs;
/**
* The width of the video in pixels, or -1 for non-video formats. * The width of the video in pixels, or -1 for non-video formats.
*/ */
public final int width; public final int width;
...@@ -98,7 +103,7 @@ public class Format { ...@@ -98,7 +103,7 @@ public class Format {
*/ */
public Format(String id, String mimeType, int width, int height, int numChannels, public Format(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bitrate) { int audioSamplingRate, int bitrate) {
this(id, mimeType, width, height, numChannels, audioSamplingRate, bitrate, null); this(id, mimeType, width, height, numChannels, audioSamplingRate, bitrate, null, null);
} }
/** /**
...@@ -113,6 +118,23 @@ public class Format { ...@@ -113,6 +118,23 @@ public class Format {
*/ */
public Format(String id, String mimeType, int width, int height, int numChannels, public Format(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bitrate, String language) { int audioSamplingRate, int bitrate, String language) {
this(id, mimeType, width, height, numChannels, audioSamplingRate, bitrate, language, null);
}
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 for non-video formats.
* @param height The height of the video in pixels, or -1 for non-video formats.
* @param numChannels The number of audio channels, or -1 for non-audio formats.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param bitrate The average bandwidth of the format in bits per second.
* @param language The language of the format.
* @param codecs The codecs used to decode the format.
*/
public Format(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bitrate, String language, String codecs) {
this.id = Assertions.checkNotNull(id); this.id = Assertions.checkNotNull(id);
this.mimeType = mimeType; this.mimeType = mimeType;
this.width = width; this.width = width;
...@@ -121,6 +143,7 @@ public class Format { ...@@ -121,6 +143,7 @@ public class Format {
this.audioSamplingRate = audioSamplingRate; this.audioSamplingRate = audioSamplingRate;
this.bitrate = bitrate; this.bitrate = bitrate;
this.language = language; this.language = language;
this.codecs = codecs;
this.bandwidth = bitrate / 8; this.bandwidth = bitrate / 8;
} }
......
...@@ -326,13 +326,30 @@ public class DashChunkSource implements ChunkSource { ...@@ -326,13 +326,30 @@ public class DashChunkSource implements ChunkSource {
return; return;
} }
int lastSegmentNum = segmentIndex.getLastSegmentNum(); // TODO: Use UtcTimingElement where possible.
boolean indexUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; long nowUs = System.currentTimeMillis() * 1000;
int firstAvailableSegmentNum = segmentIndex.getFirstSegmentNum();
int lastAvailableSegmentNum = segmentIndex.getLastSegmentNum();
boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
if (indexUnbounded) {
// The index is itself unbounded. We need to use the current time to calculate the range of
// available segments.
long liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
if (currentManifest.timeShiftBufferDepth != -1) {
long bufferDepthUs = currentManifest.timeShiftBufferDepth * 1000;
firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum,
segmentIndex.getSegmentNum(liveEdgeTimestampUs - bufferDepthUs));
}
// getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the
// index of the last completed segment.
lastAvailableSegmentNum = segmentIndex.getSegmentNum(liveEdgeTimestampUs) - 1;
}
int segmentNum; int segmentNum;
if (queue.isEmpty()) { if (queue.isEmpty()) {
if (currentManifest.dynamic) { if (currentManifest.dynamic) {
seekPositionUs = getLiveSeekPosition(indexUnbounded); seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded);
} }
segmentNum = segmentIndex.getSegmentNum(seekPositionUs); segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
} else { } else {
...@@ -340,20 +357,20 @@ public class DashChunkSource implements ChunkSource { ...@@ -340,20 +357,20 @@ public class DashChunkSource implements ChunkSource {
- representationHolder.segmentNumShift; - representationHolder.segmentNumShift;
} }
// TODO: For unbounded manifests, we need to enforce that we don't try and request chunks
// behind or in front of the live window.
if (currentManifest.dynamic) { if (currentManifest.dynamic) {
if (segmentNum < segmentIndex.getFirstSegmentNum()) { if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest. // This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException(); fatalError = new BehindLiveWindowException();
return; return;
} else if (!indexUnbounded && segmentNum > lastSegmentNum) { } else if (segmentNum > lastAvailableSegmentNum) {
// This is beyond the last chunk in the current manifest. // This chunk is beyond the last chunk in the current manifest. If the index is bounded
finishedCurrentManifest = true; // we'll need to refresh it. If it's unbounded we just need to wait for a while before
// attempting to load the chunk.
finishedCurrentManifest = !indexUnbounded;
return; return;
} else if (!indexUnbounded && segmentNum == lastSegmentNum) { } else if (!indexUnbounded && segmentNum == lastAvailableSegmentNum) {
// This is the last chunk in the current manifest. Mark the manifest as being finished, // This is the last chunk in a dynamic bounded manifest. We'll need to refresh the manifest
// but continue to return the final chunk. // to obtain the next chunk.
finishedCurrentManifest = true; finishedCurrentManifest = true;
} }
} }
...@@ -457,15 +474,14 @@ public class DashChunkSource implements ChunkSource { ...@@ -457,15 +474,14 @@ public class DashChunkSource implements ChunkSource {
* For live playbacks, determines the seek position that snaps playback to be * For live playbacks, determines the seek position that snaps playback to be
* {@link #liveEdgeLatencyUs} behind the live edge of the current manifest * {@link #liveEdgeLatencyUs} behind the live edge of the current manifest
* *
* @param nowUs An estimate of the current server time, in microseconds.
* @param indexUnbounded True if the segment index for this source is unbounded. False otherwise. * @param indexUnbounded True if the segment index for this source is unbounded. False otherwise.
* @return The seek position in microseconds. * @return The seek position in microseconds.
*/ */
private long getLiveSeekPosition(boolean indexUnbounded) { private long getLiveSeekPosition(long nowUs, boolean indexUnbounded) {
long liveEdgeTimestampUs; long liveEdgeTimestampUs;
if (indexUnbounded) { if (indexUnbounded) {
// TODO: Use UtcTimingElement where possible. liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
long nowMs = System.currentTimeMillis();
liveEdgeTimestampUs = (nowMs - currentManifest.availabilityStartTime) * 1000;
} else { } else {
liveEdgeTimestampUs = Long.MIN_VALUE; liveEdgeTimestampUs = Long.MIN_VALUE;
for (RepresentationHolder representationHolder : representationHolders.values()) { for (RepresentationHolder representationHolder : representationHolders.values()) {
......
...@@ -283,6 +283,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -283,6 +283,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
int width = parseInt(xpp, "width"); int width = parseInt(xpp, "width");
int height = parseInt(xpp, "height"); int height = parseInt(xpp, "height");
mimeType = parseString(xpp, "mimeType", mimeType); mimeType = parseString(xpp, "mimeType", mimeType);
String codecs = parseString(xpp, "codecs", null);
int numChannels = -1; int numChannels = -1;
do { do {
...@@ -302,15 +303,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler ...@@ -302,15 +303,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} while (!isEndTag(xpp, "Representation")); } while (!isEndTag(xpp, "Representation"));
Format format = buildFormat(id, mimeType, width, height, numChannels, audioSamplingRate, Format format = buildFormat(id, mimeType, width, height, numChannels, audioSamplingRate,
bandwidth, language); bandwidth, language, codecs);
return buildRepresentation(periodStartMs, periodDurationMs, contentId, -1, format, return buildRepresentation(periodStartMs, periodDurationMs, contentId, -1, format,
segmentBase); segmentBase);
} }
protected Format buildFormat(String id, String mimeType, int width, int height, int numChannels, protected Format buildFormat(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bandwidth, String language) { int audioSamplingRate, int bandwidth, String language, String codecs) {
return new Format(id, mimeType, width, height, numChannels, audioSamplingRate, return new Format(id, mimeType, width, height, numChannels, audioSamplingRate, bandwidth,
bandwidth, language); language, codecs);
} }
protected Representation buildRepresentation(long periodStartMs, long periodDurationMs, protected Representation buildRepresentation(long periodStartMs, long periodDurationMs,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment