はじめに
先日の記事でwebViewでの動画のシークを取り上げましたが、その続き。
doUpdateVisitedHistory()
を使って動画の再生開始位置を指定する方法を載せました。
これを利用して秒数を入力して、動画開始時の再生位置を自由に操作できるようにしようとしました。
作成コード(問題がある例)
@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun BrowserWebView(url: String, seekTime: Double = 0.0) {
val isInspecting = LocalInspectionMode.current
var webView: WebView? by remember { mutableStateOf(null) }
Column {
AndroidView(
modifier = Modifier.clipToBounds().weight(1f),
factory = {
WebView(it).apply {
if (!isInspecting) {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
}
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = object : WebViewClient() {
override fun doUpdateVisitedHistory(
view: WebView?,
currentUrl: String?,
isReload: Boolean,
) {
super.doUpdateVisitedHistory(view, currentUrl, isReload)
view?.evaluateJavascript(Script.seekToTime(seekTime), null)
}
}
webChromeClient = WebChromeClient()
loadUrl(url)
}
},
update = { newWebView ->
if (isInspecting) return@AndroidView
webView = newWebView
}
)
}
}
// SeekButton, Script.seekToTimeは前回の記事を参照のこと(省略)
@Composable
@Preview
private fun BrowserWebViewScreen() {
var seekTime by remember { mutableDoubleStateOf(0.0) }
var value: String by remember { mutableStateOf("") }
Column {
// 秒数を入力する欄
// サンプルです
TextField(
value = value,
onValueChange = { value = it },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
placeholder = { Text("動画開始時に遷移させたい秒数を入力") },
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text,
),
keyboardActions = KeyboardActions(
onDone = {
seekTime = value.toDoubleOrNull() ?: 0.0
Log.d("testComment", "seekTime: $seekTime")
}
),
singleLine = true,
)
BrowserWebView(url = "https://www.youtube.com/", seekTime = seekTime)
}
}
しかし、これだとうまく動作せず、秒数を指定しても動画は最初から再生されてしまいます。
問題の原因
doUpdateVisitedHistory
メソッドの evaluateJavascript
で渡している seekTime
は BrowserWebView
の初回コンポーズ時の値で固定されていて、再コンポーズが発生しても更新されません。
解決策:rememberUpdatedState
を使用する
ではどうするか。
Jetpack Compose では rememberUpdatedState
を使用すると、外部から渡された値を適切に更新できます。
修正後のコード
@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun BrowserWebView(url: String, seekTime: Double = 0.0) {
val isInspecting = LocalInspectionMode.current
var webView: WebView? by remember { mutableStateOf(null) }
val updateSeekTime by rememberUpdatedState(seekTime) // 外からもらってきた値をrememberする
Column {
AndroidView(
modifier = Modifier.clipToBounds().weight(1f),
factory = {
WebView(it).apply {
if (!isInspecting) {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
}
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = object : WebViewClient() {
override fun doUpdateVisitedHistory(
view: WebView?,
currentUrl: String?,
isReload: Boolean,
) {
super.doUpdateVisitedHistory(view, currentUrl, isReload)
view?.evaluateJavascript(Script.seekToTime(updateSeekTime), null)
}
}
... // 以下省略
rememberUpdatedState(seekTime)
を使用すると、seekTime
の最新の値を doUpdateVisitedHistory
で参照できるようになります。
これにより、外部から seekTime
を変更した際に、適切に再生開始位置が更新されるようになります。
まとめ
Jetpack Compose の WebViewClient
内の関数をoverrideするときは、rememberUpdatedState
を使用する必要がある場合もある、ということを覚えておきたいですね。
もちろんそれ以外でもrememberUpdatedState
が有用なケースはあると思うので気をつけていきたいです。