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