Merge pull request #204 from google/dev

dev -> dev-hls
This commit is contained in:
ojw28 2014-12-15 15:12:21 +00:00
commit 1face38709
7 changed files with 94 additions and 48 deletions

View file

@ -81,6 +81,9 @@ public class DashRendererBuilder implements RendererBuilder,
private static final int SECURITY_LEVEL_1 = 1;
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 url;
private final String contentId;
@ -160,12 +163,8 @@ public class DashRendererBuilder implements RendererBuilder,
// HD streams require L1 security.
filterHdContent = videoAdaptationSet != null && videoAdaptationSet.hasContentProtection()
&& !drmSessionManagerData.second;
} catch (UnsupportedSchemeException e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e));
} catch (Exception e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e));
} catch (UnsupportedDrmException e) {
callback.onRenderersError(e);
return;
}
}
@ -225,15 +224,13 @@ public class DashRendererBuilder implements RendererBuilder,
format.audioSamplingRate + "Hz)");
audioChunkSourceList.add(new DashChunkSource(manifestFetcher, audioAdaptationSetIndex,
new int[] {i}, audioDataSource, audioEvaluator, LIVE_EDGE_LATENCY_MS));
haveAc3Tracks |= format.mimeType.equals(MimeTypes.AUDIO_AC3)
|| format.mimeType.equals(MimeTypes.AUDIO_EC3);
haveAc3Tracks |= AC_3_CODEC.equals(format.codecs) || E_AC_3_CODEC.equals(format.codecs);
}
// Filter out non-AC-3 tracks if there is an AC-3 track, to avoid having to switch renderers.
if (haveAc3Tracks) {
for (int i = audioRepresentations.size() - 1; i >= 0; i--) {
Format format = audioRepresentations.get(i).format;
if (!format.mimeType.equals(MimeTypes.AUDIO_AC3)
&& !format.mimeType.equals(MimeTypes.AUDIO_EC3)) {
if (!AC_3_CODEC.equals(format.codecs) && !E_AC_3_CODEC.equals(format.codecs)) {
audioTrackNameList.remove(i);
audioChunkSourceList.remove(i);
}
@ -327,12 +324,18 @@ public class DashRendererBuilder implements RendererBuilder,
private static class V18Compat {
public static Pair<DrmSessionManager, Boolean> getDrmSessionManagerData(DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException {
StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager(
DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player);
return Pair.create((DrmSessionManager) streamingDrmSessionManager,
getWidevineSecurityLevel(streamingDrmSessionManager) == SECURITY_LEVEL_1);
MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
StreamingDrmSessionManager streamingDrmSessionManager = new StreamingDrmSessionManager(
DemoUtil.WIDEVINE_UUID, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player);
return Pair.create((DrmSessionManager) streamingDrmSessionManager,
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) {

View file

@ -118,12 +118,8 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
try {
drmSessionManager = V18Compat.getDrmSessionManager(manifest.protectionElement.uuid, player,
drmCallback);
} catch (UnsupportedSchemeException e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e));
} catch (Exception e) {
callback.onRenderersError(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e));
} catch (UnsupportedDrmException e) {
callback.onRenderersError(e);
return;
}
}
@ -259,9 +255,15 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
private static class V18Compat {
public static DrmSessionManager getDrmSessionManager(UUID uuid, DemoPlayer player,
MediaDrmCallback drmCallback) throws UnsupportedSchemeException {
return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player);
MediaDrmCallback drmCallback) throws UnsupportedDrmException {
try {
return new StreamingDrmSessionManager(uuid, player.getPlaybackLooper(), drmCallback, null,
player.getMainHandler(), player);
} catch (UnsupportedSchemeException e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
} catch (Exception e) {
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNKNOWN, e);
}
}
}

View file

@ -136,7 +136,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
}
private static boolean handlesMimeType(String mimeType) {
return MimeTypes.AUDIO_AC3.equals(mimeType) || MimeTypes.AUDIO_EC3.equals(mimeType);
return MimeTypes.AUDIO_MP4.equals(mimeType);
}
@Override

View file

@ -641,7 +641,8 @@ public final class AudioTrack {
private long bytesToFrames(long byteCount) {
if (isAc3) {
return byteCount * 8 * sampleRate / (1000 * ac3Bitrate);
return
ac3Bitrate == UNKNOWN_AC3_BITRATE ? 0L : byteCount * 8 * sampleRate / (1000 * ac3Bitrate);
} else {
return byteCount / frameSize;
}

View file

@ -46,6 +46,11 @@ public class Format {
*/
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.
*/
@ -98,7 +103,7 @@ public class Format {
*/
public Format(String id, String mimeType, int width, int height, int numChannels,
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 {
*/
public Format(String id, String mimeType, int width, int height, int numChannels,
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.mimeType = mimeType;
this.width = width;
@ -121,6 +143,7 @@ public class Format {
this.audioSamplingRate = audioSamplingRate;
this.bitrate = bitrate;
this.language = language;
this.codecs = codecs;
this.bandwidth = bitrate / 8;
}

View file

@ -326,13 +326,30 @@ public class DashChunkSource implements ChunkSource {
return;
}
int lastSegmentNum = segmentIndex.getLastSegmentNum();
boolean indexUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED;
// TODO: Use UtcTimingElement where possible.
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;
if (queue.isEmpty()) {
if (currentManifest.dynamic) {
seekPositionUs = getLiveSeekPosition(indexUnbounded);
seekPositionUs = getLiveSeekPosition(nowUs, indexUnbounded);
}
segmentNum = segmentIndex.getSegmentNum(seekPositionUs);
} else {
@ -340,20 +357,20 @@ public class DashChunkSource implements ChunkSource {
- 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 (segmentNum < segmentIndex.getFirstSegmentNum()) {
if (segmentNum < firstAvailableSegmentNum) {
// This is before the first chunk in the current manifest.
fatalError = new BehindLiveWindowException();
return;
} else if (!indexUnbounded && segmentNum > lastSegmentNum) {
// This is beyond the last chunk in the current manifest.
finishedCurrentManifest = true;
} else if (segmentNum > lastAvailableSegmentNum) {
// This chunk is beyond the last chunk in the current manifest. If the index is bounded
// 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;
} else if (!indexUnbounded && segmentNum == lastSegmentNum) {
// This is the last chunk in the current manifest. Mark the manifest as being finished,
// but continue to return the final chunk.
} else if (!indexUnbounded && segmentNum == lastAvailableSegmentNum) {
// This is the last chunk in a dynamic bounded manifest. We'll need to refresh the manifest
// to obtain the next chunk.
finishedCurrentManifest = true;
}
}
@ -457,15 +474,14 @@ public class DashChunkSource implements ChunkSource {
* For live playbacks, determines the seek position that snaps playback to be
* {@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.
* @return The seek position in microseconds.
*/
private long getLiveSeekPosition(boolean indexUnbounded) {
private long getLiveSeekPosition(long nowUs, boolean indexUnbounded) {
long liveEdgeTimestampUs;
if (indexUnbounded) {
// TODO: Use UtcTimingElement where possible.
long nowMs = System.currentTimeMillis();
liveEdgeTimestampUs = (nowMs - currentManifest.availabilityStartTime) * 1000;
liveEdgeTimestampUs = nowUs - currentManifest.availabilityStartTime * 1000;
} else {
liveEdgeTimestampUs = Long.MIN_VALUE;
for (RepresentationHolder representationHolder : representationHolders.values()) {

View file

@ -283,6 +283,7 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
int width = parseInt(xpp, "width");
int height = parseInt(xpp, "height");
mimeType = parseString(xpp, "mimeType", mimeType);
String codecs = parseString(xpp, "codecs", null);
int numChannels = -1;
do {
@ -302,15 +303,15 @@ public class MediaPresentationDescriptionParser extends DefaultHandler
} while (!isEndTag(xpp, "Representation"));
Format format = buildFormat(id, mimeType, width, height, numChannels, audioSamplingRate,
bandwidth, language);
bandwidth, language, codecs);
return buildRepresentation(periodStartMs, periodDurationMs, contentId, -1, format,
segmentBase);
}
protected Format buildFormat(String id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bandwidth, String language) {
return new Format(id, mimeType, width, height, numChannels, audioSamplingRate,
bandwidth, language);
int audioSamplingRate, int bandwidth, String language, String codecs) {
return new Format(id, mimeType, width, height, numChannels, audioSamplingRate, bandwidth,
language, codecs);
}
protected Representation buildRepresentation(long periodStartMs, long periodDurationMs,