【Android】webViewの動画をシークさせる

# WebViewで動画をシークさせたい

Youtube等の動画サイトにはシークバーやスキップボタンがあり、再生位置の移動ができるような仕様になっていることが多いです。

普通はデフォルトの機能を使えばそれで十分だと思いますが、時にはシークボタンを自作して動画の再生位置を操作したい.....なんて思うことがあるかもしれません。
本記事では、WebView内で動画のシークボタンを実装するサンプルを紹介しようと思います

## 基本機能

- WebView内で動画が再生されている時、独自で実装したシークボタンをタップすることで、ユーザーが動画の再生位置を調整できる

## シークのためのJavaScript

webViewで再生位置を操作するにはJavaScriptが必要です。
まず、WebView内で動画を操作するためのJavaScriptをkotlinで作成します。以下のコードは、`<video>` または `<audio>` 要素を対象に、現在の再生位置から指定された時間だけ進めたり戻したりするためのスクリプトです。

```kotlin
object Script {
    fun seekToTime(seekTime: Double) =
        """
        (function() {
            const media = document.querySelector('video, audio');
            if (!media) {
                console.log('No media element found');
                return;
            }
            const currentTime = media.currentTime;
            media.currentTime = $seekTime + currentTime;
        })();
        """
}
```

## webViewの実装
javascriptの実装が終わったら、webViewを実装していきます。
今回はwebViewの下にシークボタンを配置します。
シークボタンは`SeekButton`で実装しています。
（サンプルなのでデザインは適当です......）

```kotlin
@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun BrowserWebView(url: String) {
    val isInspecting = LocalInspectionMode.current
    var webView: WebView? by remember { mutableStateOf(null) }
    Column {
        AndroidView(
            modifier = Modifier
                .weight(1f)
                .clipToBounds(),
            factory = {
                WebView(it).apply {
                    if (!isInspecting) {
                        settings.javaScriptEnabled = true
                        settings.domStorageEnabled = true
                    }
                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                    webViewClient = WebViewClient()
                    webChromeClient = WebChromeClient()
                    loadUrl(url)
                }
            },
            update = { newWebView ->
                if (isInspecting) return@AndroidView
                webView = newWebView
            }
        )
        
        // シークボタン
        Row(
            modifier = Modifier
                .height(80.dp)
                .fillMaxWidth()
                .background(Color.LightGray)
                .padding(horizontal = 10.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            // 1分戻す
            SeekButton(
                text = "<< 1m",
                seekTime = -60.0,
                webView = webView
            )
            // 10秒戻す
            SeekButton(
                text = "<< 10s",
                seekTime = -10.0,
                webView = webView
            )
            // 10秒進む
            SeekButton(
                text = ">> 10s",
                seekTime = 10.0,
                webView = webView
            )
            // 1分進む
            SeekButton(
                text = ">> 1m",
                seekTime = 60.0,
                webView = webView
            )
        }
    }
}

@Composable
private fun SeekButton(
    text: String,
    modifier: Modifier = Modifier,
    seekTime: Double,
    webView: WebView?,
) {
    Text(
        text = text,
        modifier = modifier
            .size(height = 40.dp, width = 70.dp)
            .background(Color.White, shape = RoundedCornerShape(20.dp))
            .padding(vertical = 10.dp)
            .clickable(
                // タップ時のリップルをなくす
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) {
                webView?.evaluateJavascript(Script.seekToTime(seekTime), null)
            },
        textAlign = TextAlign.Center,
    )
}
```

これでYoutubeなどで動画を再生して、各ボタンを押すとシークします。

ちなみに、需要があるかわかりませんが、動画再生直後にあらかじめ指定した時間へ移動させることもできます。
`WebViewClient`の`doUpdateVisitedHistory()`をoverrideして、javascriptを実行することでできます。
（`onPageFinished()`だとYoutubeではうまく動作しません。）

```kotlin
...
 webViewClient = object: WebViewClient() {
    override fun doUpdateVisitedHistory(
        view: WebView?,
        currentUrl: String?,
        isReload: Boolean,
    ) {
        super.doUpdateVisitedHistory(view, currentUrl, isReload)
        // 任意の時間をセットしておく
        view?.evaluateJavascript(Script.seekToTime(30.0), null)
    }
...
```

上のコードだと、動画を再生した直後に30秒のところに再生位置が移動します。