From 34382e9c56b119d785acbc001bec4497d5063694 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 18 Nov 2024 11:50:10 -0800 Subject: [PATCH] Support key system in HTMLMediaElement.canPlayType (#4429) b/378908084 --- cobalt/android/BUILD.gn | 2 + .../assets/html_media_element_extension.js | 8 ++++ .../java/dev/cobalt/coat/CobaltActivity.java | 46 ++++++++++++------- .../javabridge/HTMLMediaElementExtension.java | 44 ++++++++++++++++++ .../android/shared/application_android.cc | 33 +++++++++++++ 5 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 cobalt/android/apk/app/src/app/assets/html_media_element_extension.js create mode 100644 cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java diff --git a/cobalt/android/BUILD.gn b/cobalt/android/BUILD.gn index f438f4dbbc2..75f1aafd48d 100644 --- a/cobalt/android/BUILD.gn +++ b/cobalt/android/BUILD.gn @@ -68,6 +68,7 @@ android_library("cobalt_apk_java") { "apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java", "apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java", "apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptInterface.java", + "apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java", # "apk/app/src/main/java/dev/cobalt/coat/CobaltMediaSession.java", "apk/app/src/main/java/dev/cobalt/coat/CobaltService.java", @@ -118,6 +119,7 @@ android_assets("cobalt_apk_assets") { testonly = true sources = [ "apk/app/src/app/assets/amati_device_inspector.js", + "apk/app/src/app/assets/html_media_element_extension.js", "apk/app/src/app/assets/not_empty.txt", "apk/app/src/app/assets/test/not_empty.txt", "apk/app/src/app/assets/web/cobalt_blue_splash_screen.css", diff --git a/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js b/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js new file mode 100644 index 00000000000..0bc185b69ce --- /dev/null +++ b/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js @@ -0,0 +1,8 @@ +/** + * @license + * Copyright The Cobalt Authors. + * SPDX-License-Identifier: Apache-2.0 + */ + +HTMLMediaElement.prototype.canPlayType = HTMLMediaElementExtension.canPlayType; +console.log("HTMLMediaElement.canPlayType has been overwritten"); diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index c23dfc0e083..55b3be75463 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java @@ -39,6 +39,7 @@ import dev.cobalt.coat.javabridge.AmatiDeviceInspector; import dev.cobalt.coat.javabridge.CobaltJavaScriptAndroidObject; import dev.cobalt.coat.javabridge.CobaltJavaScriptInterface; +import dev.cobalt.coat.javabridge.HTMLMediaElementExtension; import dev.cobalt.media.AudioOutputManager; import dev.cobalt.media.MediaCodecCapabilitiesLogger; import dev.cobalt.media.VideoSurfaceView; @@ -172,11 +173,12 @@ protected void createContent(final Bundle savedInstanceState) { } if (mStartupUrl == null || mStartupUrl.isEmpty()) { String[] args = getStarboardBridge().getArgs(); - mStartupUrl = Arrays.stream(args) - .filter(line -> line.contains(URL_ARG)) - .findAny() - .map(arg -> arg.substring(arg.indexOf(URL_ARG) + URL_ARG.length())) - .orElse(null); + mStartupUrl = + Arrays.stream(args) + .filter(line -> line.contains(URL_ARG)) + .findAny() + .map(arg -> arg.substring(arg.indexOf(URL_ARG) + URL_ARG.length())) + .orElse(null); } if (!TextUtils.isEmpty(mStartupUrl)) { mShellManager.setStartupUrl(Shell.sanitizeUrl(mStartupUrl)); @@ -355,28 +357,33 @@ protected void onCreate(Bundle savedInstanceState) { } /** - * Initializes the Java Bridge to allow communication between Java and JavaScript. - * This method injects Java objects into the WebView and loads corresponding JavaScript code. + * Initializes the Java Bridge to allow communication between Java and JavaScript. This method + * injects Java objects into the WebView and loads corresponding JavaScript code. */ private void initializeJavaBridge() { Log.i(TAG, "initializeJavaBridge"); WebContents webContents = getActiveWebContents(); if (webContents == null) { - // WebContents not initialized yet, post a delayed runnable to check again - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - initializeJavaBridge(); // Recursive call to check again - } - }, JAVA_BRIDGE_INITIALIZATION_DELAY_MILLI_SECONDS); - return; + // WebContents not initialized yet, post a delayed runnable to check again + new Handler(Looper.getMainLooper()) + .postDelayed( + new Runnable() { + @Override + public void run() { + initializeJavaBridge(); // Recursive call to check again + } + }, + JAVA_BRIDGE_INITIALIZATION_DELAY_MILLI_SECONDS); + return; } // --- Initialize the Java Bridge --- // 1. Gather all Java objects that need to be exposed to JavaScript. + // TODO(b/379701165): consider to refine the way to add JavaScript interfaces. javaScriptAndroidObjectList.add(new AmatiDeviceInspector(this)); + javaScriptAndroidObjectList.add(new HTMLMediaElementExtension(this)); // 2. Use JavascriptInjector to inject Java objects into the WebContents. // This makes the annotated methods in these objects accessible from JavaScript. @@ -384,8 +391,13 @@ public void run() { javascriptInjector.setAllowInspection(true); for (CobaltJavaScriptAndroidObject javascriptAndroidObject : javaScriptAndroidObjectList) { - Log.d(TAG, "Add JavaScriptAndroidObject:" + javascriptAndroidObject.getJavaScriptInterfaceName()); - javascriptInjector.addPossiblyUnsafeInterface(javascriptAndroidObject, javascriptAndroidObject.getJavaScriptInterfaceName(), CobaltJavaScriptInterface.class); + Log.d( + TAG, + "Add JavaScriptAndroidObject:" + javascriptAndroidObject.getJavaScriptInterfaceName()); + javascriptInjector.addPossiblyUnsafeInterface( + javascriptAndroidObject, + javascriptAndroidObject.getJavaScriptInterfaceName(), + CobaltJavaScriptInterface.class); } // 3. Load and evaluate JavaScript code that interacts with the injected Java objects. diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java new file mode 100644 index 00000000000..528819d1a9a --- /dev/null +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java @@ -0,0 +1,44 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cobalt.coat.javabridge; + +import android.content.Context; + +/** Implementation of HTMLMediaElement extensions. */ +public class HTMLMediaElementExtension implements CobaltJavaScriptAndroidObject { + + private final Context context; + + public HTMLMediaElementExtension(Context context) { + this.context = context; + } + + @Override + public String getJavaScriptInterfaceName() { + return "HTMLMediaElementExtension"; + } + + @Override + public String getJavaScriptAssetName() { + return "html_media_element_extension.js"; + } + + @CobaltJavaScriptInterface + public String canPlayType(String mimeType, String keySystem) { + return nativeCanPlayType(mimeType, keySystem); + } + + private static native String nativeCanPlayType(String mimeType, String keySystem); +} diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc index e4fcea9d455..5d076a7f268 100644 --- a/starboard/android/shared/application_android.cc +++ b/starboard/android/shared/application_android.cc @@ -38,6 +38,7 @@ #include "starboard/common/time.h" #include "starboard/event.h" #include "starboard/key.h" +#include "starboard/media.h" #include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" namespace starboard { @@ -124,6 +125,38 @@ Java_dev_cobalt_coat_CobaltSystemConfigChangeReceiver_nativeDateTimeConfiguratio tzset(); } +extern "C" SB_EXPORT_PLATFORM jstring +Java_dev_cobalt_coat_javabridge_HTMLMediaElementExtension_nativeCanPlayType( + JniEnvExt* env, + jobject jcaller, + jstring j_mime_type, + jstring j_key_system) { + std::string mime_type, key_system; + if (j_mime_type) { + mime_type = env->GetStringStandardUTFOrAbort(j_mime_type); + } + if (j_key_system) { + key_system = env->GetStringStandardUTFOrAbort(j_key_system); + } + SbMediaSupportType support_type = + SbMediaCanPlayMimeAndKeySystem(mime_type.c_str(), key_system.c_str()); + const char* ret; + switch (support_type) { + case kSbMediaSupportTypeNotSupported: + ret = ""; + break; + case kSbMediaSupportTypeMaybe: + ret = "maybe"; + break; + case kSbMediaSupportTypeProbably: + ret = "probably"; + break; + } + SB_LOG(INFO) << __func__ << " (" << mime_type << ", " << key_system + << ") --> " << ret; + return env->NewStringStandardUTFOrAbort(ret); +} + int ApplicationAndroid::GetOverlayedIntValue(const char* var_name) { ScopedLock lock(overlay_mutex_); if (overlayed_int_variables_.find(var_name) !=