mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
commit
1face38709
7 changed files with 94 additions and 48 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue