Skip to content

Commit

Permalink
Merge pull request #13 from DimaDemchenko/feat/custom-javascript-inte…
Browse files Browse the repository at this point in the history
…rface

Feat: ability to add custom javascript interface
  • Loading branch information
DimaDemchenko authored Dec 24, 2024
2 parents 89ad48a + c023efb commit 81ebbdb
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 12 deletions.
28 changes: 25 additions & 3 deletions p2pml/src/main/java/com/novage/p2pml/P2PMediaLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

/**
Expand All @@ -37,6 +38,7 @@ import kotlinx.coroutines.runBlocking
class P2PMediaLoader private constructor(
private val coreConfigJson: String,
private val serverPort: Int,
private val customJavaScriptInterfaces: List<Pair<String, Any>>,
private val customEngineImplementationPath: String?,
) {
private val engineStateManager = P2PStateManager()
Expand Down Expand Up @@ -78,6 +80,7 @@ class P2PMediaLoader private constructor(
scope!!,
engineStateManager,
playbackCalculator,
customJavaScriptInterfaces,
onPageLoadFinished = { onWebViewLoaded() },
)

Expand Down Expand Up @@ -175,9 +178,11 @@ class P2PMediaLoader private constructor(
manifestParser.reset()
}

private suspend fun onWebViewLoaded() {
webViewManager?.initCoreEngine(coreConfigJson)
webViewLoadCompletion?.complete(Unit)
private fun onWebViewLoaded() {
scope?.launch {
webViewManager?.initCoreEngine(coreConfigJson)
webViewLoadCompletion?.complete(Unit)
}
}

private fun onServerStarted() {
Expand All @@ -198,6 +203,7 @@ class P2PMediaLoader private constructor(
private var coreConfig: String = ""
private var serverPort: Int = Constants.DEFAULT_SERVER_PORT
private var customEngineImplementationPath: String? = null
private var customJavaScriptInterfaces: MutableList<Pair<String, Any>> = mutableListOf()

/**
* Sets core P2P configurations. See [P2PML Core Config](https://novage.github.io/p2p-media-loader/docs/v2.1.0/types/p2p_media_loader_core.CoreConfig.html)
Expand All @@ -221,13 +227,29 @@ class P2PMediaLoader private constructor(
this.customEngineImplementationPath = path
}

/**
* Adds a custom JavaScript interface to the WebView.
* The feature has to be used with custom engine implementation.
* methods has to be annotated with @JavascriptInterface.
*
* @param name Interface name
* @param obj Object with methods annotated with @JavascriptInterface
*/
fun addCustomJavaScriptInterface(
name: String,
obj: Any,
) = apply {
customJavaScriptInterfaces.add(Pair(name, obj))
}

/**
* @return A new [P2PMediaLoader] instance.
*/
fun build(): P2PMediaLoader =
P2PMediaLoader(
coreConfig,
serverPort,
customJavaScriptInterfaces,
customEngineImplementationPath,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package com.novage.p2pml.webview

import android.webkit.JavascriptInterface
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

internal class JavaScriptInterface(
private val coroutineScope: CoroutineScope,
private val onFullyLoadedCallback: suspend () -> Unit,
private val onFullyLoadedCallback: () -> Unit,
) {
@JavascriptInterface
fun onWebViewLoaded() {
coroutineScope.launch {
onFullyLoadedCallback()
}
onFullyLoadedCallback()
}
}
26 changes: 24 additions & 2 deletions p2pml/src/main/java/com/novage/p2pml/webview/WebViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
Expand All @@ -22,13 +23,15 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@SuppressLint("JavascriptInterface")
@OptIn(UnstableApi::class)
internal class WebViewManager(
context: Context,
private val coroutineScope: CoroutineScope,
private val engineStateManager: P2PStateManager,
private val playbackCalculator: ExoPlayerPlaybackCalculator,
onPageLoadFinished: suspend () -> Unit,
customJavaScriptInterfaces: List<Pair<String, Any>>,
private val onPageLoadFinished: () -> Unit,
) {
@SuppressLint("SetJavaScriptEnabled")
private val webView =
Expand All @@ -38,14 +41,33 @@ internal class WebViewManager(
webViewClient = WebViewClientCompat()
visibility = View.GONE
addJavascriptInterface(
JavaScriptInterface(coroutineScope, onPageLoadFinished),
JavaScriptInterface(onPageLoadFinished),
"Android",
)
}
private val webMessageProtocol = WebMessageProtocol(webView, coroutineScope)

private var playbackInfoJob: Job? = null

init {
customJavaScriptInterfaces.forEach { (name, obj) ->
val isValid = validateJavaScriptInterface(obj)
if (!isValid) {
Log.e(
"WebViewManager",
"Object $obj does not have any methods annotated with @JavascriptInterface",
)
return@forEach
}
webView.addJavascriptInterface(obj, name)
}
}

private fun validateJavaScriptInterface(obj: Any): Boolean {
val methods = obj::class.java.methods
return methods.any { it.isAnnotationPresent(JavascriptInterface::class.java) }
}

private fun startPlaybackInfoUpdate() {
if (playbackInfoJob !== null) return

Expand Down

0 comments on commit 81ebbdb

Please sign in to comment.