Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/AUTOROLL
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5f4930b006c0b13e4610d565da281c05a08a42cf
00d5d7bd741b67377b635a199fb366a14cdce7c3
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,10 @@ protected void onActivityDestroy(Activity activity) {
if (mApplicationStopped) {
// We can't restart the starboard app, so kill the process for a clean start next time.
Log.i(TAG, "Activity destroyed after shutdown; killing app.");
BaseStarboardBridgeJni.get().closeNativeStarboard(mNativeApp);
mTtsHelper.shutdown();
mAdvertisingId.shutdown();
BaseStarboardBridgeJni.get().closeNativeStarboard(mNativeApp);
System.exit(0);
} else {
Log.i(TAG, "Activity destroyed without shutdown; app suspended in background.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
import dev.cobalt.util.SynchronizedHolder;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Locale;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;
Expand Down Expand Up @@ -88,17 +88,14 @@ private boolean shouldSkipVideoFrame(long presentationTimeUs, boolean isDecodeOn
return true;
}
// |mOperatingRate| won't be 0, but to be cautious we still add a check here.
if (!mSkipVideoFramesOver60Fps
|| mOperatingRate <= kMaxAcceptedOperatingRate
|| mOperatingRate == 0) {
if (!mSkipVideoFramesOver60Fps || mOperatingRate <= kMaxAcceptedOperatingRate || mOperatingRate == 0) {
return false;
}
// Deterministically downsample to 60fps by picking one frame per 1/60s interval.
// Some visual jitter may occur briefly when the playback rate changes.
double frameIntervalUs = 1_000_000.0 / mOperatingRate;
if (Math.floor(presentationTimeUs * kMaxAcceptedOperatingRate / 1_000_000.0)
== Math.floor(
(presentationTimeUs - frameIntervalUs) * kMaxAcceptedOperatingRate / 1_000_000.0)) {
== Math.floor((presentationTimeUs - frameIntervalUs) * kMaxAcceptedOperatingRate / 1_000_000.0)) {
return true;
}
return false;
Expand Down Expand Up @@ -127,17 +124,17 @@ int sizeOfActiveOutputBuffers() {
return mActiveOutputBuffers.get();
}

/** Wraps a {@link MediaFormat} object to expose its properties to native code */
// Copied from Chromium's MediaCodecBridge.java
// https://source.chromium.org/chromium/chromium/src/+/main:media/base/android/java/src/org/chromium/media/MediaCodecBridge.java;l=294-350;drc=6ac17d9d1b844a695209e865137466925fa1214f
// Here are changes made.
// - Exposes formatHasCropValues() to the native layer, which needs to call it.
// - Removes the methods that Cobalt do not use (e.g. colorStandrd).
// - Add @Nonnul annotation to mFormat. since it is not null.
// - Add safety checks for width() and height() to prevent a crash. Cobalt's native code
// accesses the format immediately in the onOutputFormatChanged callback, which can be
// before the dimension keys are available.
private static class MediaFormatWrapper {
/** Wraps a {@link MediaFormat} object to expose its properties to native code */
// Copied from Chromium's MediaCodecBridge.java
// https://source.chromium.org/chromium/chromium/src/+/main:media/base/android/java/src/org/chromium/media/MediaCodecBridge.java;l=294-350;drc=6ac17d9d1b844a695209e865137466925fa1214f
// Here are changes made.
// - Exposes formatHasCropValues() to the native layer, which needs to call it.
// - Removes the methods that Cobalt do not use (e.g. colorStandrd).
// - Add @Nonnul annotation to mFormat. since it is not null.
// - Add safety checks for width() and height() to prevent a crash. Cobalt's native code
// accesses the format immediately in the onOutputFormatChanged callback, which can be
// before the dimension keys are available.
private static class MediaFormatWrapper {
@NonNull private final MediaFormat mFormat;

private MediaFormatWrapper(MediaFormat format) {
Expand All @@ -147,9 +144,9 @@ private MediaFormatWrapper(MediaFormat format) {
@CalledByNative("MediaFormatWrapper")
private boolean formatHasCropValues() {
return mFormat.containsKey(KEY_CROP_RIGHT)
&& mFormat.containsKey(KEY_CROP_LEFT)
&& mFormat.containsKey(KEY_CROP_BOTTOM)
&& mFormat.containsKey(KEY_CROP_TOP);
&& mFormat.containsKey(KEY_CROP_LEFT)
&& mFormat.containsKey(KEY_CROP_BOTTOM)
&& mFormat.containsKey(KEY_CROP_TOP);
}

@CalledByNative("MediaFormatWrapper")
Expand Down Expand Up @@ -720,12 +717,11 @@ private int flush() {
mFrameRateEstimator.reset();
}
if (mEnableIgnoreCallbacksDuringFlushing) {
mMainHandler.post(
() -> {
synchronized (mNativeBridgeLock) {
mIsFlushing = false;
}
});
mMainHandler.post(() -> {
synchronized (mNativeBridgeLock) {
mIsFlushing = false;
}
});
}
}
return MediaCodecStatus.OK;
Expand Down Expand Up @@ -774,8 +770,8 @@ private MediaFormatWrapper getOutputFormat() {
MediaFormat format = null;
try {
format = mMediaCodec.get().getOutputFormat();
// Catches `RuntimeException` to handle any undocumented exceptions.
// See http://b/445694177#comment4 for details.
// Catches `RuntimeException` to handle any undocumented exceptions.
// See http://b/445694177#comment4 for details.
} catch (RuntimeException e) {
Log.e(TAG, "Failed to get output format", e);
return null;
Expand All @@ -785,7 +781,7 @@ private MediaFormatWrapper getOutputFormat() {
// If the format is null, we crash the app for dev builds to enforce the API contract.
// On release builds, we log the error and return null.
if (format == null) {
assert (false);
assert(false);
Log.e(TAG, "MediaCodec.getOutputFormat() returned null");
return null;
}
Expand Down Expand Up @@ -1091,9 +1087,7 @@ public void onFirstTunnelFrameReady(MediaCodec codec) {
}
};
if (mEnableIgnoreCallbacksDuringFlushing) {
mMediaCodec
.get()
.setOnFirstTunnelFrameReadyListener(mMainHandler, mFirstTunnelFrameReadyListener);
mMediaCodec.get().setOnFirstTunnelFrameReadyListener(mMainHandler, mFirstTunnelFrameReadyListener);
} else {
mMediaCodec.get().setOnFirstTunnelFrameReadyListener(null, mFirstTunnelFrameReadyListener);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,11 @@ public static OperationResult operationFailed(String errorMessage) {
}

public static OperationResult operationFailed(String errorMessage, Throwable e) {
return operationFailed(
String.format("%s StackTrace: %s", errorMessage, Log.getStackTraceString(e)));
return operationFailed(String.format("%s StackTrace: %s", errorMessage, Log.getStackTraceString(e)));
}

public static OperationResult notProvisioned(Throwable e) {
return new OperationResult(
DrmOperationStatus.NOT_PROVISIONED,
String.format("Device is not provisioned. StackTrace: %s", Log.getStackTraceString(e)));
return new OperationResult(DrmOperationStatus.NOT_PROVISIONED, String.format("Device is not provisioned. StackTrace: %s", Log.getStackTraceString(e)));
}

@CalledByNative("OperationResult")
Expand All @@ -156,17 +153,15 @@ public boolean isSuccess() {
* @param nativeMediaDrmBridge The native owner of this class.
*/
@CalledByNative
static MediaDrmBridge create(
String keySystem, boolean enableAppProvisioning, long nativeMediaDrmBridge) {
static MediaDrmBridge create(String keySystem, boolean enableAppProvisioning, long nativeMediaDrmBridge) {
UUID cryptoScheme = WIDEVINE_UUID;
if (!MediaDrm.isCryptoSchemeSupported(cryptoScheme)) {
return null;
}

MediaDrmBridge mediaDrmBridge = null;
try {
mediaDrmBridge =
new MediaDrmBridge(keySystem, cryptoScheme, enableAppProvisioning, nativeMediaDrmBridge);
mediaDrmBridge = new MediaDrmBridge(keySystem, cryptoScheme, enableAppProvisioning, nativeMediaDrmBridge);
Log.d(TAG, "MediaDrmBridge successfully created.");
} catch (UnsupportedSchemeException e) {
Log.e(TAG, "Unsupported DRM scheme", e);
Expand Down Expand Up @@ -273,8 +268,7 @@ OperationResult createSessionWithAppProvisioning(int ticket, byte[] initData, St
assert mEnableAppProvisioning;
if (mMediaDrm == null) {
Log.e(TAG, "createSessionWithAppProvisioning() called when MediaDrm is null.");
return OperationResult.operationFailed(
"createSessionWithAppProvisioning() called when MediaDrm is null.");
return OperationResult.operationFailed("createSessionWithAppProvisioning() called when MediaDrm is null.");
}

OperationResult result = createMediaCryptoSessionWithAppProvisioning();
Expand Down Expand Up @@ -333,12 +327,14 @@ OperationResult updateSession(int ticket, byte[] sessionId, byte[] response) {
Log.d(TAG, "updateSession()");
if (mMediaDrm == null) {
Log.e(TAG, "updateSession() called when MediaDrm is null.");
return OperationResult.operationFailed("Null MediaDrm object when calling updateSession().");
return OperationResult.operationFailed(
"Null MediaDrm object when calling updateSession().");
}

if (!sessionExists(sessionId)) {
Log.e(TAG, "updateSession tried to update a session that does not exist.");
return OperationResult.operationFailed("Failed to update session because it does not exist.");
return OperationResult.operationFailed(
"Failed to update session because it does not exist.");
}

try {
Expand Down Expand Up @@ -429,8 +425,7 @@ MediaCrypto getMediaCrypto() {
return mMediaCrypto;
}

private MediaDrmBridge(
String keySystem, UUID schemeUUID, boolean enableAppProvisioning, long nativeMediaDrmBridge)
private MediaDrmBridge(String keySystem, UUID schemeUUID, boolean enableAppProvisioning, long nativeMediaDrmBridge)
throws android.media.UnsupportedSchemeException {
mSchemeUUID = schemeUUID;
mMediaDrm = new MediaDrm(schemeUUID);
Expand Down Expand Up @@ -518,15 +513,12 @@ public void onKeyStatusChange(
byte[] sessionId,
List<MediaDrm.KeyStatus> keyInformation,
boolean hasNewUsableKey) {
MediaDrmBridgeJni.get()
.onKeyStatusChange(
mNativeMediaDrmBridge,
sessionId,
keyInformation.stream()
.map(
keyStatus ->
new KeyStatus(keyStatus.getKeyId(), keyStatus.getStatusCode()))
.toArray(KeyStatus[]::new));
MediaDrmBridgeJni.get().onKeyStatusChange(
mNativeMediaDrmBridge,
sessionId,
keyInformation.stream()
.map(keyStatus -> new KeyStatus(keyStatus.getKeyId(), keyStatus.getStatusCode()))
.toArray(KeyStatus[]::new));
}
},
null);
Expand Down Expand Up @@ -568,10 +560,7 @@ private void handleKeyRequiredEventWithAppProvisioning(byte[] sessionId, byte[]
}
ByteBuffer sessionIdByteBuffer = ByteBuffer.wrap(sessionId);
if (!mSessionIds.containsKey(sessionIdByteBuffer)) {
Log.e(
TAG,
"HandleKeyRequiredEventWithAppProvisioning failed: invalid session id="
+ bytesToString(sessionId));
Log.e(TAG, "HandleKeyRequiredEventWithAppProvisioning failed: invalid session id=" + bytesToString(sessionId));
return;
}

Expand Down Expand Up @@ -599,8 +588,8 @@ private void onSessionMessage(

int requestType = request.getRequestType();

MediaDrmBridgeJni.get()
.onSessionMessage(mNativeMediaDrmBridge, ticket, sessionId, requestType, request.getData());
MediaDrmBridgeJni.get().onSessionMessage(
mNativeMediaDrmBridge, ticket, sessionId, requestType, request.getData());
}

/**
Expand Down Expand Up @@ -692,6 +681,8 @@ private byte[] openSession() throws android.media.NotProvisionedException {
// Throw NotProvisionedException so that we can attemptProvisioning().
throw e;
} catch (MediaDrmException e) {
// Other MediaDrmExceptions (e.g. ResourceBusyException) are not
// recoverable.
Log.e(TAG, "Cannot open a new session", e);
release();
return null;
Expand Down Expand Up @@ -968,7 +959,11 @@ private int getStatusCode() {
@NativeMethods
interface Natives {
void onSessionMessage(
long nativeMediaDrmBridge, int ticket, byte[] sessionId, int requestType, byte[] message);
long nativeMediaDrmBridge,
int ticket,
byte[] sessionId,
int requestType,
byte[] message);

void onKeyStatusChange(
long nativeMediaDrmBridge,
Expand Down
Loading
Loading