React Howlerで実装した音楽PlayerをBluetoothの外部デバイスから操作してみた

React版Howlerを使うと比較的簡単にブラウザ上で動作する音楽Playerが実装出来ます。

import React, { useState, useEffect } from "react";
import './App.css';
import ReactHowler from 'react-howler'

function App() {
  const [fileIdx, setFileIdx] = useState(0)
  const [playing, setPlaying] = useState(false)
  const [mp3files, setMp3Files] = useState([{program_name:'DUMMY',file_name:'DUMMY'}])

  useEffect(() => {
    (async() => {
      // ここでdataに実際のmp3の情報を取得する(実装は省略してます)
      setMp3Files(data)
    })()
  }, []);

  function onEnd() {
    setPlaying(false)
  }

  return (
    <div className="App">
      <header className="App-header">
        <div>No.{fileIdx+1}: {mp3files[fileIdx].program_name}</div>
      {mp3files[0].file_name !== "DUMMY" &&
        <button className="App-btn" onClick={() => setPlaying(!playing)}>
          {playing ? 'STOP' : 'PLAY'}
        </button>
      }
      <button className="App-btn" onClick={() => {
          setFileIdx(fileIdx + 1 < mp3files.length ? fileIdx + 1 : 0)
        }
      }>
        CHANGE MUSIC
      </button>
      {playing &&
        <ReactHowler
          src={`https://your-domain.com/${mp3files[fileIdx].file_name}`}
          playing={true}
          onEnd={onEnd}
          html5={true}
        />
      }
      </header>
    </div>
  );
}

export default App;

上記は不完全なコード(各音楽データのファイル名、タイトルを格納するdataに中身をセットするための実装を省略してます。WEB API等から取得する想定。)ですが、React Howlerを使った簡易的な音楽プレイヤーのサンプルコードです。ボタンが2つだけあって、1つ目が再生と停止のトグルボタン、2つ目が次の曲を選曲するためのボタンです。音楽データは別のサーバ(your-domain.com)に置いているものを取得しています。

WEBアプリなので、ブラウザ上のボタンを押すことで再生したり次の曲を選択したりできますが、Bluetoothのイヤホンなどのデバイスで聞いているときにイヤホンについている再生ボタン等で操作したい、と思いWEBアプリだけでできないか探してみましたが、残念ながら見つけられなかった(そもそもできないのかもしれませんが)ので、WebView内にこのページを表示するネイティブなAndroidアプリであればBluetoothのデバイスで操作できる可能性があると考え、実際に試してみました。

package com.example.mp3player

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebChromeClient
import android.view.KeyEvent
import android.content.Intent
import android.support.v4.media.session.MediaSessionCompat

class MainActivity : AppCompatActivity() {

    private lateinit var myWebView: WebView
    private var mediaSession: MediaSessionCompat? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        myWebView = findViewById(R.id.webview)
        myWebView.webChromeClient = WebChromeClient()
        myWebView.settings.javaScriptEnabled = true
        myWebView.loadUrl("https://your-app.com/")

        // MediaSessionの設定
        mediaSession = MediaSessionCompat(this, "Tag").apply {
            setCallback(object : MediaSessionCompat.Callback() {
                override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
                    val keyEvent = mediaButtonIntent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
                    if (keyEvent?.action == KeyEvent.ACTION_UP && keyEvent?.keyCode in arrayOf(KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_MEDIA_PAUSE)) {
                        myWebView.evaluateJavascript("document.getElementsByTagName('button')[0].click();", null)
                        return true
                    }
                    if (keyEvent?.action == KeyEvent.ACTION_UP && keyEvent?.keyCode == KeyEvent.KEYCODE_MEDIA_NEXT) {
                        myWebView.evaluateJavascript("document.getElementsByTagName('button')[1].click();", null)
                        return true
                    }
                    return super.onMediaButtonEvent(mediaButtonIntent)
                }
            })
            isActive = true
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        mediaSession?.run {
            isActive = false
            release()
        }
    }
}

上記はほとんどChatGPTに実装してもらったAndroidアプリのソースコードです。MediaSessionというものを使って、Bluetoothで接続したデバイスのボタンイベントを取得しています。MediaSessionについてはこちらのページにわかりやすく解説されていました。なお、WebChromeClientを使っているのは、これを指定しないとJavaScriptが動かなかったためです。

上記の実装でほぼ期待通り、デバイスのボタンからWEBアプリ上のボタンを押すことができた(再生開始や次の曲への遷移ができた)のですが、一点だけ解決できなかった問題があります。それは一度画面上の再生ボタンを押して(Bluetoothのデバイスを接続した状態で)再生を開始するまではデバイスのボタンを押してもキーイベントが送られない、というものです。おそらく、WEBアプリが起動したときに自動的に再生を開始するようにすればこの問題は解決されるのでは、と思っていますが、こちらにあるように、再生を開始するにはタップやクリック等のユーザーインタラクションが必要、ということでブラウザの仕様上、自動で再生させることは難しそうです。

Comments

Copied title and URL