Skip to content

Commit 81ebbdb

Browse files
Merge pull request #13 from DimaDemchenko/feat/custom-javascript-interface
Feat: ability to add custom javascript interface
2 parents 89ad48a + c023efb commit 81ebbdb

File tree

3 files changed

+51
-12
lines changed

3 files changed

+51
-12
lines changed

p2pml/src/main/java/com/novage/p2pml/P2PMediaLoader.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import kotlinx.coroutines.CompletableDeferred
1717
import kotlinx.coroutines.CoroutineScope
1818
import kotlinx.coroutines.Dispatchers
1919
import kotlinx.coroutines.Job
20+
import kotlinx.coroutines.launch
2021
import kotlinx.coroutines.runBlocking
2122

2223
/**
@@ -37,6 +38,7 @@ import kotlinx.coroutines.runBlocking
3738
class P2PMediaLoader private constructor(
3839
private val coreConfigJson: String,
3940
private val serverPort: Int,
41+
private val customJavaScriptInterfaces: List<Pair<String, Any>>,
4042
private val customEngineImplementationPath: String?,
4143
) {
4244
private val engineStateManager = P2PStateManager()
@@ -78,6 +80,7 @@ class P2PMediaLoader private constructor(
7880
scope!!,
7981
engineStateManager,
8082
playbackCalculator,
83+
customJavaScriptInterfaces,
8184
onPageLoadFinished = { onWebViewLoaded() },
8285
)
8386

@@ -175,9 +178,11 @@ class P2PMediaLoader private constructor(
175178
manifestParser.reset()
176179
}
177180

178-
private suspend fun onWebViewLoaded() {
179-
webViewManager?.initCoreEngine(coreConfigJson)
180-
webViewLoadCompletion?.complete(Unit)
181+
private fun onWebViewLoaded() {
182+
scope?.launch {
183+
webViewManager?.initCoreEngine(coreConfigJson)
184+
webViewLoadCompletion?.complete(Unit)
185+
}
181186
}
182187

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

202208
/**
203209
* 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)
@@ -221,13 +227,29 @@ class P2PMediaLoader private constructor(
221227
this.customEngineImplementationPath = path
222228
}
223229

230+
/**
231+
* Adds a custom JavaScript interface to the WebView.
232+
* The feature has to be used with custom engine implementation.
233+
* methods has to be annotated with @JavascriptInterface.
234+
*
235+
* @param name Interface name
236+
* @param obj Object with methods annotated with @JavascriptInterface
237+
*/
238+
fun addCustomJavaScriptInterface(
239+
name: String,
240+
obj: Any,
241+
) = apply {
242+
customJavaScriptInterfaces.add(Pair(name, obj))
243+
}
244+
224245
/**
225246
* @return A new [P2PMediaLoader] instance.
226247
*/
227248
fun build(): P2PMediaLoader =
228249
P2PMediaLoader(
229250
coreConfig,
230251
serverPort,
252+
customJavaScriptInterfaces,
231253
customEngineImplementationPath,
232254
)
233255
}
Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
package com.novage.p2pml.webview
22

33
import android.webkit.JavascriptInterface
4-
import kotlinx.coroutines.CoroutineScope
5-
import kotlinx.coroutines.launch
64

75
internal class JavaScriptInterface(
8-
private val coroutineScope: CoroutineScope,
9-
private val onFullyLoadedCallback: suspend () -> Unit,
6+
private val onFullyLoadedCallback: () -> Unit,
107
) {
118
@JavascriptInterface
129
fun onWebViewLoaded() {
13-
coroutineScope.launch {
14-
onFullyLoadedCallback()
15-
}
10+
onFullyLoadedCallback()
1611
}
1712
}

p2pml/src/main/java/com/novage/p2pml/webview/WebViewManager.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
44
import android.content.Context
55
import android.util.Log
66
import android.view.View
7+
import android.webkit.JavascriptInterface
78
import android.webkit.WebView
89
import androidx.annotation.OptIn
910
import androidx.media3.common.util.UnstableApi
@@ -22,13 +23,15 @@ import kotlinx.coroutines.withContext
2223
import kotlinx.serialization.encodeToString
2324
import kotlinx.serialization.json.Json
2425

26+
@SuppressLint("JavascriptInterface")
2527
@OptIn(UnstableApi::class)
2628
internal class WebViewManager(
2729
context: Context,
2830
private val coroutineScope: CoroutineScope,
2931
private val engineStateManager: P2PStateManager,
3032
private val playbackCalculator: ExoPlayerPlaybackCalculator,
31-
onPageLoadFinished: suspend () -> Unit,
33+
customJavaScriptInterfaces: List<Pair<String, Any>>,
34+
private val onPageLoadFinished: () -> Unit,
3235
) {
3336
@SuppressLint("SetJavaScriptEnabled")
3437
private val webView =
@@ -38,14 +41,33 @@ internal class WebViewManager(
3841
webViewClient = WebViewClientCompat()
3942
visibility = View.GONE
4043
addJavascriptInterface(
41-
JavaScriptInterface(coroutineScope, onPageLoadFinished),
44+
JavaScriptInterface(onPageLoadFinished),
4245
"Android",
4346
)
4447
}
4548
private val webMessageProtocol = WebMessageProtocol(webView, coroutineScope)
4649

4750
private var playbackInfoJob: Job? = null
4851

52+
init {
53+
customJavaScriptInterfaces.forEach { (name, obj) ->
54+
val isValid = validateJavaScriptInterface(obj)
55+
if (!isValid) {
56+
Log.e(
57+
"WebViewManager",
58+
"Object $obj does not have any methods annotated with @JavascriptInterface",
59+
)
60+
return@forEach
61+
}
62+
webView.addJavascriptInterface(obj, name)
63+
}
64+
}
65+
66+
private fun validateJavaScriptInterface(obj: Any): Boolean {
67+
val methods = obj::class.java.methods
68+
return methods.any { it.isAnnotationPresent(JavascriptInterface::class.java) }
69+
}
70+
4971
private fun startPlaybackInfoUpdate() {
5072
if (playbackInfoJob !== null) return
5173

0 commit comments

Comments
 (0)