mirror of
https://github.com/samsonjs/media.git
synced 2026-04-20 13:45:47 +00:00
Add layer of indirection for DRM.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=138383979
This commit is contained in:
parent
a6e2770116
commit
4cd8c77053
15 changed files with 445 additions and 36 deletions
|
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
|
|||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
/**
|
||||
|
|
@ -71,7 +72,8 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected FfmpegDecoder createDecoder(Format format) throws FfmpegDecoderException {
|
||||
protected FfmpegDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
|
||||
throws FfmpegDecoderException {
|
||||
decoder = new FfmpegDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
|
||||
format.sampleMimeType, format.initializationData);
|
||||
return decoder;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
|
|||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
/**
|
||||
|
|
@ -63,7 +64,8 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected FlacDecoder createDecoder(Format format) throws FlacDecoderException {
|
||||
protected FlacDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
|
||||
throws FlacDecoderException {
|
||||
return new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.initializationData);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
|
|||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +59,21 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
|
|||
super(eventHandler, eventListener, audioCapabilities, streamType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. 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.
|
||||
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
|
||||
* default capabilities (no encoded audio passthrough support) should be assumed.
|
||||
* @param streamType The type of audio stream for the {@link AudioTrack}.
|
||||
*/
|
||||
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
|
||||
AudioCapabilities audioCapabilities, int streamType,
|
||||
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
|
||||
super(eventHandler, eventListener, audioCapabilities, streamType, drmSessionManager,
|
||||
playClearSamplesWithoutKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int supportsFormat(Format format) {
|
||||
return OpusLibrary.isAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)
|
||||
|
|
@ -64,9 +81,10 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected OpusDecoder createDecoder(Format format) throws OpusDecoderException {
|
||||
protected OpusDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
|
||||
throws OpusDecoderException {
|
||||
return new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
|
||||
format.initializationData);
|
||||
format.initializationData, mediaCrypto);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@
|
|||
package com.google.android.exoplayer2.ext.opus;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.decoder.CryptoInfo;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
||||
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
||||
import com.google.android.exoplayer2.drm.DecryptionException;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
|
|
@ -36,6 +39,12 @@ import java.util.List;
|
|||
*/
|
||||
private static final int SAMPLE_RATE = 48000;
|
||||
|
||||
private static final int NO_ERROR = 0;
|
||||
private static final int DECODE_ERROR = -1;
|
||||
private static final int DRM_ERROR = -2;
|
||||
|
||||
private final ExoMediaCrypto exoMediaCrypto;
|
||||
|
||||
private final int channelCount;
|
||||
private final int headerSkipSamples;
|
||||
private final int headerSeekPreRollSamples;
|
||||
|
|
@ -52,14 +61,20 @@ import java.util.List;
|
|||
* @param initializationData Codec-specific initialization data. The first element must contain an
|
||||
* opus header. Optionally, the list may contain two additional buffers, which must contain
|
||||
* the encoder delay and seek pre roll values in nanoseconds, encoded as longs.
|
||||
* @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted
|
||||
* content. Maybe null and can be ignored if decoder does not handle encrypted content.
|
||||
* @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder.
|
||||
*/
|
||||
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
|
||||
List<byte[]> initializationData) throws OpusDecoderException {
|
||||
List<byte[]> initializationData, ExoMediaCrypto exoMediaCrypto) throws OpusDecoderException {
|
||||
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
|
||||
if (!OpusLibrary.isAvailable()) {
|
||||
throw new OpusDecoderException("Failed to load decoder native libraries.");
|
||||
}
|
||||
this.exoMediaCrypto = exoMediaCrypto;
|
||||
if (exoMediaCrypto != null && !OpusLibrary.opusIsSecureDecodeSupported()) {
|
||||
throw new OpusDecoderException("Opus decoder does not support secure decode.");
|
||||
}
|
||||
byte[] headerBytes = initializationData.get(0);
|
||||
if (headerBytes.length < 19) {
|
||||
throw new OpusDecoderException("Header size is too small.");
|
||||
|
|
@ -139,11 +154,25 @@ import java.util.List;
|
|||
skipSamples = (inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples;
|
||||
}
|
||||
ByteBuffer inputData = inputBuffer.data;
|
||||
int result = opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(),
|
||||
outputBuffer, SAMPLE_RATE);
|
||||
CryptoInfo cryptoInfo = inputBuffer.cryptoInfo;
|
||||
int result = inputBuffer.isEncrypted()
|
||||
? opusSecureDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(),
|
||||
outputBuffer, SAMPLE_RATE, exoMediaCrypto, cryptoInfo.mode,
|
||||
cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples,
|
||||
cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData)
|
||||
: opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(),
|
||||
outputBuffer, SAMPLE_RATE);
|
||||
if (result < 0) {
|
||||
return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result));
|
||||
if (result == DRM_ERROR) {
|
||||
String message = "Drm error: " + opusGetErrorMessage(nativeDecoderContext);
|
||||
DecryptionException cause = new DecryptionException(
|
||||
opusGetErrorCode(nativeDecoderContext), message);
|
||||
return new OpusDecoderException(message, cause);
|
||||
} else {
|
||||
return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result));
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer outputData = outputBuffer.data;
|
||||
outputData.position(0);
|
||||
outputData.limit(result);
|
||||
|
|
@ -182,8 +211,13 @@ import java.util.List;
|
|||
int gain, byte[] streamMap);
|
||||
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize,
|
||||
SimpleOutputBuffer outputBuffer, int sampleRate);
|
||||
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer,
|
||||
int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate,
|
||||
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
|
||||
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
|
||||
private native void opusClose(long decoder);
|
||||
private native void opusReset(long decoder);
|
||||
private native String opusGetErrorMessage(int errorCode);
|
||||
private native int opusGetErrorCode(long decoder);
|
||||
private native String opusGetErrorMessage(long decoder);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ public final class OpusDecoderException extends AudioDecoderException {
|
|||
super(message);
|
||||
}
|
||||
|
||||
/* package */ OpusDecoderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,5 +50,5 @@ public final class OpusLibrary {
|
|||
}
|
||||
|
||||
public static native String opusGetVersion();
|
||||
|
||||
public static native boolean opusIsSecureDecodeSupported();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,11 +60,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||
|
||||
static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples.
|
||||
static int channelCount;
|
||||
static int errorCode;
|
||||
|
||||
DECODER_FUNC(jlong, opusInit, jint sampleRate, jint channelCount,
|
||||
jint numStreams, jint numCoupled, jint gain, jbyteArray jStreamMap) {
|
||||
int status = OPUS_INVALID_STATE;
|
||||
::channelCount = channelCount;
|
||||
errorCode = 0;
|
||||
jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0);
|
||||
uint8_t* streamMap = reinterpret_cast<uint8_t*>(streamMapBytes);
|
||||
OpusMSDecoder* decoder = opus_multistream_decoder_create(
|
||||
|
|
@ -109,10 +111,24 @@ DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs,
|
|||
env->GetDirectBufferAddress(jOutputBufferData));
|
||||
int sampleCount = opus_multistream_decode(decoder, inputBuffer, inputSize,
|
||||
outputBufferData, outputSize, 0);
|
||||
// record error code
|
||||
errorCode = (sampleCount < 0) ? sampleCount : 0;
|
||||
return (sampleCount < 0) ? sampleCount
|
||||
: sampleCount * kBytesPerSample * channelCount;
|
||||
}
|
||||
|
||||
DECODER_FUNC(jint, opusSecureDecode, jlong jDecoder, jlong jTimeUs,
|
||||
jobject jInputBuffer, jint inputSize, jobject jOutputBuffer,
|
||||
jint sampleRate, jobject mediaCrypto, jint inputMode, jbyteArray key,
|
||||
jbyteArray javaIv, jint inputNumSubSamples, jintArray numBytesOfClearData,
|
||||
jintArray numBytesOfEncryptedData) {
|
||||
// Doesn't support
|
||||
// Java client should have checked vpxSupportSecureDecode
|
||||
// and avoid calling this
|
||||
// return -2 (DRM Error)
|
||||
return -2;
|
||||
}
|
||||
|
||||
DECODER_FUNC(void, opusClose, jlong jDecoder) {
|
||||
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
|
|
@ -123,10 +139,19 @@ DECODER_FUNC(void, opusReset, jlong jDecoder) {
|
|||
opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
DECODER_FUNC(jstring, opusGetErrorMessage, jint errorCode) {
|
||||
DECODER_FUNC(jstring, opusGetErrorMessage, jlong jContext) {
|
||||
return env->NewStringUTF(opus_strerror(errorCode));
|
||||
}
|
||||
|
||||
DECODER_FUNC(jint, opusGetErrorCode, jlong jContext) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
LIBRARY_FUNC(jstring, opusIsSecureDecodeSupported) {
|
||||
// Doesn't support
|
||||
return 0;
|
||||
}
|
||||
|
||||
LIBRARY_FUNC(jstring, opusGetVersion) {
|
||||
return env->NewStringUTF(opus_get_version_string());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.vp9;
|
|||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.BaseRenderer;
|
||||
|
|
@ -28,8 +29,12 @@ import com.google.android.exoplayer2.Format;
|
|||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
|
||||
|
||||
|
|
@ -56,8 +61,10 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
private final boolean scaleToFit;
|
||||
private final long allowedJoiningTimeMs;
|
||||
private final int maxDroppedFramesToNotify;
|
||||
private final boolean playClearSamplesWithoutKeys;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final FormatHolder formatHolder;
|
||||
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
|
||||
|
||||
private DecoderCounters decoderCounters;
|
||||
private Format format;
|
||||
|
|
@ -65,6 +72,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
private DecoderInputBuffer inputBuffer;
|
||||
private VpxOutputBuffer outputBuffer;
|
||||
private VpxOutputBuffer nextOutputBuffer;
|
||||
private DrmSession<ExoMediaCrypto> drmSession;
|
||||
private DrmSession<ExoMediaCrypto> pendingDrmSession;
|
||||
|
||||
private Bitmap bitmap;
|
||||
private boolean renderedFirstFrame;
|
||||
|
|
@ -72,6 +81,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
private Surface surface;
|
||||
private VpxOutputBufferRenderer outputBufferRenderer;
|
||||
private int outputMode;
|
||||
private boolean waitingForKeys;
|
||||
|
||||
private boolean inputStreamEnded;
|
||||
private boolean outputStreamEnded;
|
||||
|
|
@ -104,10 +114,37 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
public LibvpxVideoRenderer(boolean scaleToFit, long allowedJoiningTimeMs,
|
||||
Handler eventHandler, VideoRendererEventListener eventListener,
|
||||
int maxDroppedFramesToNotify) {
|
||||
this(scaleToFit, allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify,
|
||||
null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scaleToFit Whether video frames should be scaled to fit when rendering.
|
||||
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
|
||||
* can attempt to seamlessly join an ongoing playback.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. 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.
|
||||
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
|
||||
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
|
||||
* @param drmSessionManager For use with encrypted media. May be null if support for encrypted
|
||||
* media is not required.
|
||||
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
|
||||
* For example a media file may start with a short clear region so as to allow playback to
|
||||
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
|
||||
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
|
||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||
*/
|
||||
public LibvpxVideoRenderer(boolean scaleToFit, long allowedJoiningTimeMs,
|
||||
Handler eventHandler, VideoRendererEventListener eventListener,
|
||||
int maxDroppedFramesToNotify, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys) {
|
||||
super(C.TRACK_TYPE_VIDEO);
|
||||
this.scaleToFit = scaleToFit;
|
||||
this.allowedJoiningTimeMs = allowedJoiningTimeMs;
|
||||
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
|
||||
this.drmSessionManager = drmSessionManager;
|
||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||
joiningDeadlineMs = -1;
|
||||
previousWidth = -1;
|
||||
previousHeight = -1;
|
||||
|
|
@ -135,12 +172,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
}
|
||||
|
||||
if (isRendererAvailable()) {
|
||||
drmSession = pendingDrmSession;
|
||||
ExoMediaCrypto mediaCrypto = null;
|
||||
if (drmSession != null) {
|
||||
int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
||||
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
|
||||
mediaCrypto = drmSession.getMediaCrypto();
|
||||
} else {
|
||||
// The drm session isn't open yet.
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (decoder == null) {
|
||||
// If we don't have a decoder yet, we need to instantiate one.
|
||||
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||
TraceUtil.beginSection("createVpxDecoder");
|
||||
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE);
|
||||
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
|
||||
mediaCrypto);
|
||||
decoder.setOutputMode(outputMode);
|
||||
TraceUtil.endSection();
|
||||
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||
|
|
@ -258,7 +310,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
surface.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
|
||||
private boolean feedInputBuffer() throws VpxDecoderException {
|
||||
private boolean feedInputBuffer() throws VpxDecoderException, ExoPlaybackException {
|
||||
if (inputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -270,7 +322,14 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
int result = readSource(formatHolder, inputBuffer);
|
||||
int result;
|
||||
if (waitingForKeys) {
|
||||
// We've already read an encrypted sample into buffer, and are waiting for keys.
|
||||
result = C.RESULT_BUFFER_READ;
|
||||
} else {
|
||||
result = readSource(formatHolder, inputBuffer);
|
||||
}
|
||||
|
||||
if (result == C.RESULT_NOTHING_READ) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -284,6 +343,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
inputBuffer = null;
|
||||
return false;
|
||||
}
|
||||
boolean bufferEncrypted = inputBuffer.isEncrypted();
|
||||
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
|
||||
if (waitingForKeys) {
|
||||
return false;
|
||||
}
|
||||
inputBuffer.flip();
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
decoderCounters.inputBufferCount++;
|
||||
|
|
@ -291,8 +355,21 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
||||
if (drmSession == null) {
|
||||
return false;
|
||||
}
|
||||
int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
}
|
||||
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
|
||||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
waitingForKeys = false;
|
||||
if (outputBuffer != null) {
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
|
|
@ -311,6 +388,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
if (waitingForKeys) {
|
||||
return false;
|
||||
}
|
||||
if (format != null && (isSourceReady() || outputBuffer != null)
|
||||
&& (renderedFirstFrame || !isRendererAvailable())) {
|
||||
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
||||
|
|
@ -365,11 +445,26 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
format = null;
|
||||
waitingForKeys = false;
|
||||
try {
|
||||
releaseDecoder();
|
||||
} finally {
|
||||
decoderCounters.ensureUpdated();
|
||||
eventDispatcher.disabled(decoderCounters);
|
||||
try {
|
||||
if (drmSession != null) {
|
||||
drmSessionManager.releaseSession(drmSession);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
|
||||
drmSessionManager.releaseSession(pendingDrmSession);
|
||||
}
|
||||
} finally {
|
||||
drmSession = null;
|
||||
pendingDrmSession = null;
|
||||
decoderCounters.ensureUpdated();
|
||||
eventDispatcher.disabled(decoderCounters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,10 +473,18 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
decoder.release();
|
||||
decoder = null;
|
||||
decoderCounters.decoderReleaseCount++;
|
||||
waitingForKeys = false;
|
||||
if (drmSession != null && pendingDrmSession != drmSession) {
|
||||
try {
|
||||
drmSessionManager.releaseSession(drmSession);
|
||||
} finally {
|
||||
drmSession = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readFormat() {
|
||||
private boolean readFormat() throws ExoPlaybackException {
|
||||
int result = readSource(formatHolder, null);
|
||||
if (result == C.RESULT_FORMAT_READ) {
|
||||
onInputFormatChanged(formatHolder.format);
|
||||
|
|
@ -390,8 +493,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onInputFormatChanged(Format newFormat) {
|
||||
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
|
||||
Format oldFormat = format;
|
||||
format = newFormat;
|
||||
|
||||
boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null
|
||||
: oldFormat.drmInitData);
|
||||
if (drmInitDataChanged) {
|
||||
if (format.drmInitData != null) {
|
||||
if (drmSessionManager == null) {
|
||||
throw ExoPlaybackException.createForRenderer(
|
||||
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
|
||||
}
|
||||
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData);
|
||||
if (pendingDrmSession == drmSession) {
|
||||
drmSessionManager.releaseSession(pendingDrmSession);
|
||||
}
|
||||
} else {
|
||||
pendingDrmSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
eventDispatcher.inputFormatChanged(format);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,11 @@
|
|||
package com.google.android.exoplayer2.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.decoder.CryptoInfo;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
||||
import com.google.android.exoplayer2.drm.DecryptionException;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
|
|
@ -30,6 +33,11 @@ import java.nio.ByteBuffer;
|
|||
public static final int OUTPUT_MODE_YUV = 0;
|
||||
public static final int OUTPUT_MODE_RGB = 1;
|
||||
|
||||
private static final int NO_ERROR = 0;
|
||||
private static final int DECODE_ERROR = 1;
|
||||
private static final int DRM_ERROR = 2;
|
||||
|
||||
private final ExoMediaCrypto exoMediaCrypto;
|
||||
private final long vpxDecContext;
|
||||
|
||||
private volatile int outputMode;
|
||||
|
|
@ -40,14 +48,20 @@ import java.nio.ByteBuffer;
|
|||
* @param numInputBuffers The number of input buffers.
|
||||
* @param numOutputBuffers The number of output buffers.
|
||||
* @param initialInputBufferSize The initial size of each input buffer.
|
||||
* @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted
|
||||
* content. Maybe null and can be ignored if decoder does not handle encrypted content.
|
||||
* @throws VpxDecoderException Thrown if an exception occurs when initializing the decoder.
|
||||
*/
|
||||
public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize)
|
||||
throws VpxDecoderException {
|
||||
public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
|
||||
ExoMediaCrypto exoMediaCrypto) throws VpxDecoderException {
|
||||
super(new DecoderInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]);
|
||||
if (!VpxLibrary.isAvailable()) {
|
||||
throw new VpxDecoderException("Failed to load decoder native libraries.");
|
||||
}
|
||||
this.exoMediaCrypto = exoMediaCrypto;
|
||||
if (exoMediaCrypto != null && !VpxLibrary.vpxIsSecureDecodeSupported()) {
|
||||
throw new VpxDecoderException("Vpx decoder does not support secure decode.");
|
||||
}
|
||||
vpxDecContext = vpxInit();
|
||||
if (vpxDecContext == 0) {
|
||||
throw new VpxDecoderException("Failed to initialize decoder");
|
||||
|
|
@ -90,9 +104,23 @@ import java.nio.ByteBuffer;
|
|||
boolean reset) {
|
||||
ByteBuffer inputData = inputBuffer.data;
|
||||
int inputSize = inputData.limit();
|
||||
if (vpxDecode(vpxDecContext, inputData, inputSize) != 0) {
|
||||
return new VpxDecoderException("Decode error: " + vpxGetErrorMessage(vpxDecContext));
|
||||
CryptoInfo cryptoInfo = inputBuffer.cryptoInfo;
|
||||
final long result = inputBuffer.isEncrypted()
|
||||
? vpxSecureDecode(vpxDecContext, inputData, inputSize, exoMediaCrypto,
|
||||
cryptoInfo.mode, cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples,
|
||||
cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData)
|
||||
: vpxDecode(vpxDecContext, inputData, inputSize);
|
||||
if (result != NO_ERROR) {
|
||||
if (result == DRM_ERROR) {
|
||||
String message = "Drm error: " + vpxGetErrorMessage(vpxDecContext);
|
||||
DecryptionException cause = new DecryptionException(
|
||||
vpxGetErrorCode(vpxDecContext), message);
|
||||
return new VpxDecoderException(message, cause);
|
||||
} else {
|
||||
return new VpxDecoderException("Decode error: " + vpxGetErrorMessage(vpxDecContext));
|
||||
}
|
||||
}
|
||||
|
||||
outputBuffer.init(inputBuffer.timeUs, outputMode);
|
||||
if (vpxGetFrame(vpxDecContext, outputBuffer) != 0) {
|
||||
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
|
|
@ -109,7 +137,11 @@ import java.nio.ByteBuffer;
|
|||
private native long vpxInit();
|
||||
private native long vpxClose(long context);
|
||||
private native long vpxDecode(long context, ByteBuffer encoded, int length);
|
||||
private native long vpxSecureDecode(long context, ByteBuffer encoded, int length,
|
||||
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
|
||||
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
|
||||
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
|
||||
private native int vpxGetErrorCode(long context);
|
||||
private native String vpxGetErrorMessage(long context);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,11 @@ package com.google.android.exoplayer2.ext.vp9;
|
|||
*/
|
||||
public class VpxDecoderException extends Exception {
|
||||
|
||||
/* package */ VpxDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
/* package */ VpxDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/* package */ VpxDecoderException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,5 +59,5 @@ public final class VpxLibrary {
|
|||
|
||||
private static native String vpxGetVersion();
|
||||
private static native String vpxGetBuildConfig();
|
||||
|
||||
public static native boolean vpxIsSecureDecodeSupported();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ static jmethodID initForRgbFrame;
|
|||
static jmethodID initForYuvFrame;
|
||||
static jfieldID dataField;
|
||||
static jfieldID outputModeField;
|
||||
static int errorCode;
|
||||
|
||||
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env;
|
||||
|
|
@ -72,6 +73,7 @@ DECODER_FUNC(jlong, vpxInit) {
|
|||
vpx_codec_ctx_t* context = new vpx_codec_ctx_t();
|
||||
vpx_codec_dec_cfg_t cfg = {0, 0, 0};
|
||||
cfg.threads = android_getCpuCount();
|
||||
errorCode = 0;
|
||||
if (vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, &cfg, 0)) {
|
||||
LOGE("ERROR: Fail to initialize libvpx decoder.");
|
||||
return 0;
|
||||
|
|
@ -97,13 +99,26 @@ DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
|
|||
reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
|
||||
const vpx_codec_err_t status =
|
||||
vpx_codec_decode(context, buffer, len, NULL, 0);
|
||||
errorCode = 0;
|
||||
if (status != VPX_CODEC_OK) {
|
||||
LOGE("ERROR: vpx_codec_decode() failed, status= %d", status);
|
||||
errorCode = status;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
DECODER_FUNC(jlong, vpxSecureDecode, jlong jContext, jobject encoded, jint len,
|
||||
jobject mediaCrypto, jint inputMode, jbyteArray&, jbyteArray&,
|
||||
jint inputNumSubSamples, jintArray numBytesOfClearData,
|
||||
jintArray numBytesOfEncryptedData) {
|
||||
// Doesn't support
|
||||
// Java client should have checked vpxSupportSecureDecode
|
||||
// and avoid calling this
|
||||
// return -2 (DRM Error)
|
||||
return -2;
|
||||
}
|
||||
|
||||
DECODER_FUNC(jlong, vpxClose, jlong jContext) {
|
||||
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
|
||||
vpx_codec_destroy(context);
|
||||
|
|
@ -181,6 +196,15 @@ DECODER_FUNC(jstring, vpxGetErrorMessage, jlong jContext) {
|
|||
return env->NewStringUTF(vpx_codec_error(context));
|
||||
}
|
||||
|
||||
DECODER_FUNC(jint, vpxGetErrorCode, jlong jContext) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
LIBRARY_FUNC(jstring, vpxIsSecureDecodeSupported) {
|
||||
// Doesn't support
|
||||
return 0;
|
||||
}
|
||||
|
||||
LIBRARY_FUNC(jstring, vpxGetVersion) {
|
||||
return env->NewStringUTF(vpx_codec_version_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,15 @@ public abstract class AudioDecoderException extends Exception {
|
|||
super(detailMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param detailMessage The detail message for this exception.
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link #getCause()} method). (A <tt>null</tt> value is
|
||||
* permitted, and indicates that the cause is nonexistent or
|
||||
* unknown.)
|
||||
*/
|
||||
public AudioDecoderException(String detailMessage, Throwable cause) {
|
||||
super(detailMessage, cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio;
|
|||
import android.media.AudioManager;
|
||||
import android.media.PlaybackParams;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import com.google.android.exoplayer2.BaseRenderer;
|
||||
import com.google.android.exoplayer2.C;
|
||||
|
|
@ -29,17 +30,24 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
|
|||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.decoder.SimpleDecoder;
|
||||
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
|
||||
import com.google.android.exoplayer2.drm.DrmSession;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||
import com.google.android.exoplayer2.util.MediaClock;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* Decodes and renders audio using a {@link SimpleDecoder}.
|
||||
*/
|
||||
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock {
|
||||
|
||||
private final boolean playClearSamplesWithoutKeys;
|
||||
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final FormatHolder formatHolder;
|
||||
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
|
||||
|
||||
private DecoderCounters decoderCounters;
|
||||
private Format inputFormat;
|
||||
|
|
@ -47,11 +55,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
? extends AudioDecoderException> decoder;
|
||||
private DecoderInputBuffer inputBuffer;
|
||||
private SimpleOutputBuffer outputBuffer;
|
||||
private DrmSession<ExoMediaCrypto> drmSession;
|
||||
private DrmSession<ExoMediaCrypto> pendingDrmSession;
|
||||
|
||||
private long currentPositionUs;
|
||||
private boolean allowPositionDiscontinuity;
|
||||
private boolean inputStreamEnded;
|
||||
private boolean outputStreamEnded;
|
||||
private boolean waitingForKeys;
|
||||
|
||||
private final AudioTrack audioTrack;
|
||||
private int audioSessionId;
|
||||
|
|
@ -70,7 +81,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
*/
|
||||
public SimpleDecoderAudioRenderer(Handler eventHandler,
|
||||
AudioRendererEventListener eventListener) {
|
||||
this (eventHandler, eventListener, null, AudioManager.STREAM_MUSIC);
|
||||
this(eventHandler, eventListener, null, AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -84,7 +95,31 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
public SimpleDecoderAudioRenderer(Handler eventHandler,
|
||||
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
|
||||
int streamType) {
|
||||
this(eventHandler, eventListener, audioCapabilities, streamType, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. 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.
|
||||
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
|
||||
* default capabilities (no encoded audio passthrough support) should be assumed.
|
||||
* @param streamType The type of audio stream for the {@link AudioTrack}.
|
||||
* @param drmSessionManager For use with encrypted media. May be null if support for encrypted
|
||||
* media is not required.
|
||||
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
|
||||
* For example a media file may start with a short clear region so as to allow playback to
|
||||
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
|
||||
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
|
||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||
*/
|
||||
public SimpleDecoderAudioRenderer(Handler eventHandler,
|
||||
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
|
||||
int streamType, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
|
||||
boolean playClearSamplesWithoutKeys) {
|
||||
super(C.TRACK_TYPE_AUDIO);
|
||||
this.drmSessionManager = drmSessionManager;
|
||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
audioTrack = new AudioTrack(audioCapabilities, streamType);
|
||||
|
|
@ -108,12 +143,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
return;
|
||||
}
|
||||
|
||||
drmSession = pendingDrmSession;
|
||||
ExoMediaCrypto mediaCrypto = null;
|
||||
if (drmSession != null) {
|
||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
} else if (drmSessionState == DrmSession.STATE_OPENED
|
||||
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
|
||||
mediaCrypto = drmSession.getMediaCrypto();
|
||||
} else {
|
||||
// The drm session isn't open yet.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we don't have a decoder yet, we need to instantiate one.
|
||||
if (decoder == null) {
|
||||
try {
|
||||
long codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||
TraceUtil.beginSection("createAudioDecoder");
|
||||
decoder = createDecoder(inputFormat);
|
||||
decoder = createDecoder(inputFormat, mediaCrypto);
|
||||
TraceUtil.endSection();
|
||||
long codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
|
||||
|
|
@ -141,11 +190,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
* Creates a decoder for the given format.
|
||||
*
|
||||
* @param format The format for which a decoder is required.
|
||||
* @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
|
||||
* Maybe null and can be ignored if decoder does not handle encrypted content.
|
||||
* @return The decoder.
|
||||
* @throws AudioDecoderException If an error occurred creating a suitable decoder.
|
||||
*/
|
||||
protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer,
|
||||
? extends AudioDecoderException> createDecoder(Format format) throws AudioDecoderException;
|
||||
? extends AudioDecoderException> createDecoder(Format format, ExoMediaCrypto mediaCrypto)
|
||||
throws AudioDecoderException;
|
||||
|
||||
/**
|
||||
* Returns the format of audio buffers output by the decoder. Will not be called until the first
|
||||
|
|
@ -228,7 +280,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean feedInputBuffer() throws AudioDecoderException {
|
||||
private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException {
|
||||
if (inputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -240,7 +292,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
}
|
||||
}
|
||||
|
||||
int result = readSource(formatHolder, inputBuffer);
|
||||
int result;
|
||||
if (waitingForKeys) {
|
||||
// We've already read an encrypted sample into buffer, and are waiting for keys.
|
||||
result = C.RESULT_BUFFER_READ;
|
||||
} else {
|
||||
result = readSource(formatHolder, inputBuffer);
|
||||
}
|
||||
|
||||
if (result == C.RESULT_NOTHING_READ) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -254,6 +313,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
inputBuffer = null;
|
||||
return false;
|
||||
}
|
||||
boolean bufferEncrypted = inputBuffer.isEncrypted();
|
||||
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
|
||||
if (waitingForKeys) {
|
||||
return false;
|
||||
}
|
||||
inputBuffer.flip();
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
decoderCounters.inputBufferCount++;
|
||||
|
|
@ -261,8 +325,21 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
|
||||
if (drmSession == null) {
|
||||
return false;
|
||||
}
|
||||
@DrmSession.State int drmSessionState = drmSession.getState();
|
||||
if (drmSessionState == DrmSession.STATE_ERROR) {
|
||||
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
|
||||
}
|
||||
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
|
||||
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
waitingForKeys = false;
|
||||
if (outputBuffer != null) {
|
||||
outputBuffer.release();
|
||||
outputBuffer = null;
|
||||
|
|
@ -278,7 +355,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
@Override
|
||||
public boolean isReady() {
|
||||
return audioTrack.hasPendingData()
|
||||
|| (inputFormat != null && (isSourceReady() || outputBuffer != null));
|
||||
|| (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -339,6 +416,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
outputBuffer = null;
|
||||
inputFormat = null;
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
waitingForKeys = false;
|
||||
try {
|
||||
if (decoder != null) {
|
||||
decoder.release();
|
||||
|
|
@ -347,12 +425,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
}
|
||||
audioTrack.release();
|
||||
} finally {
|
||||
decoderCounters.ensureUpdated();
|
||||
eventDispatcher.disabled(decoderCounters);
|
||||
try {
|
||||
if (drmSession != null) {
|
||||
drmSessionManager.releaseSession(drmSession);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
|
||||
drmSessionManager.releaseSession(pendingDrmSession);
|
||||
}
|
||||
} finally {
|
||||
drmSession = null;
|
||||
pendingDrmSession = null;
|
||||
decoderCounters.ensureUpdated();
|
||||
eventDispatcher.disabled(decoderCounters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readFormat() {
|
||||
private boolean readFormat() throws ExoPlaybackException {
|
||||
int result = readSource(formatHolder, null);
|
||||
if (result == C.RESULT_FORMAT_READ) {
|
||||
onInputFormatChanged(formatHolder.format);
|
||||
|
|
@ -361,8 +453,28 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||
return false;
|
||||
}
|
||||
|
||||
private void onInputFormatChanged(Format newFormat) {
|
||||
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
|
||||
Format oldFormat = inputFormat;
|
||||
inputFormat = newFormat;
|
||||
|
||||
boolean drmInitDataChanged = !Util.areEqual(inputFormat.drmInitData, oldFormat == null ? null
|
||||
: oldFormat.drmInitData);
|
||||
if (drmInitDataChanged) {
|
||||
if (inputFormat.drmInitData != null) {
|
||||
if (drmSessionManager == null) {
|
||||
throw ExoPlaybackException.createForRenderer(
|
||||
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
|
||||
}
|
||||
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(),
|
||||
inputFormat.drmInitData);
|
||||
if (pendingDrmSession == drmSession) {
|
||||
drmSessionManager.releaseSession(pendingDrmSession);
|
||||
}
|
||||
} else {
|
||||
pendingDrmSession = null;
|
||||
}
|
||||
}
|
||||
|
||||
eventDispatcher.inputFormatChanged(newFormat);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.google.android.exoplayer2.drm;
|
||||
|
||||
/**
|
||||
* An exception when doing drm decryption using the In-App Drm
|
||||
*/
|
||||
public class DecryptionException extends Exception {
|
||||
private final int errorCode;
|
||||
|
||||
public DecryptionException(int errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error code
|
||||
*/
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue