はじめに (対象読者・この記事でわかること)
この記事は、AndroidアプリにYouTube動画再生機能を組み込んでいるが、「動画の読み込みだけでバックグラウンドの音楽プレイヤーが停止してしまう」という問題に悩んでいる開発者向けです。特にJavaで実装しているプロジェクトが対象です。
この記事を読むことで、YouTube Player APIの初期化時にAUDIOFOCUS_GAINを要求しないよう制御し、ユーザーの明示的な再生操作まで音楽を止めない実装方法がわかります。また、Android 8以降のAudioFocusRequestによる後方互換性の確保方法も習得できます。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 - Android StudioでのJava開発経験 - YouTube Player APIの基本的なセットアップ方法 - ServiceやMediaSessionの基礎概念
YouTube Player APIとAudioFocusの関係性
YouTube Player APIをAndroidアプリに組み込むと、YouTubePlayerViewやYouTubePlayerFragmentの初期化時に自動的にAUDIOFOCUS_GAINが要求されます。これはYouTube公式アプリと同じ挙動を模倣しているためです。
このAudioFocusの要求により、システムは現在再生中の他のアプリ(Spotify、Podcastアプリ等)に一時停止を指示します。結果として、動画がまだ再生されていない状態でも、バックグラウンド音楽が停止してしまうわけです。
本記事では、この「初期化時の自動AudioFocus要求」を抑制し、ユーザーが明示的に再生ボタンを押したときのみ音楽を停止する実装を解説します。
バックグラウンド音楽を停止しないJava実装手順
以下では、YouTube Player APIの内部でAudioFocusを制御する方法を説明します。なお、YouTube Player APIはクローズドな実装のため、完全な制御はできませんが、いくつかの回避策があります。
ステップ1:YouTubePlayerの初期化時にmuteを有効にする
最初の回避策として、YouTubePlayerを初期化する際に静音状態で開始することで、AudioFocusの要求を抑制できます。
Javapublic class YouTubeActivity extends AppCompatActivity implements YouTubePlayer.OnInitializedListener { private YouTubePlayerView youTubePlayerView; private YouTubePlayer youTubePlayer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_youtube); youTubePlayerView = findViewById(R.id.youtube_player); youTubePlayerView.initialize("YOUR_API_KEY", this); } @Override public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer player, boolean wasRestored) { this.youTubePlayer = player; // 重要: 初期状態でミュートにすることでAudioFocusを回避 player.setPlayerStyle(YouTubePlayer.PlayerStyle.MINIMAL); player.setShowFullscreenButton(false); if (!wasRestored) { // 音量を0で動画を読み込む player.setVolume(0); player.cueVideo("VIDEO_ID"); } // プレイヤーの状態変更リスナーを設定 player.setPlayerStateChangeListener(new YouTubePlayer.PlayerStateChangeListener() { @Override public void onLoading() { } @Override public void onLoaded(String videoId) { // 動画が読み込まれたら音量を復元 new Handler().postDelayed(() -> { if (youTubePlayer != null) { youTubePlayer.setVolume(100); } }, 500); } @Override public void onAdStarted() { } @Override public void onVideoStarted() { } @Override public void onVideoEnded() { } @Override public void onError(YouTubePlayer.ErrorReason errorReason) { } }); } @Override public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubePlayerInitializationResult errorReason) { // エラーハンドリング } }
ステップ2:独自のAudioFocus管理を実装する
より高度な制御が必要な場合は、YouTubePlayerの代わりに独自のAudioFocus管理クラスを実装します。
Javaimport android.content.Context; import android.media.AudioFocusRequest; import android.media.AudioManager; import android.os.Build; import androidx.annotation.RequiresApi; public class AudioFocusManager implements AudioManager.OnAudioFocusChangeListener { private final AudioManager audioManager; private AudioFocusRequest focusRequest; private boolean isAudioFocusRequested = false; public AudioFocusManager(Context context) { audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } public boolean requestAudioFocus() { if (isAudioFocusRequested) { return true; } int result; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { result = requestAudioFocusForOreo(); } else { result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); } isAudioFocusRequested = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); return isAudioFocusRequested; } @RequiresApi(api = Build.VERSION_CODES.O) private int requestAudioFocusForOreo() { focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setOnAudioFocusChangeListener(this) .setAcceptsDelayedFocusGain(true) .setWillPauseWhenDucked(true) .build(); return audioManager.requestAudioFocus(focusRequest); } public void abandonAudioFocus() { if (!isAudioFocusRequested) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) { audioManager.abandonAudioFocusRequest(focusRequest); } else { audioManager.abandonAudioFocus(this); } isAudioFocusRequested = false; } @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_LOSS: // 永続的なフォーカスロス abandonAudioFocus(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: // 一時的なフォーカスロス break; case AudioManager.AUDIOFOCUS_GAIN: // フォーカス獲得 break; } } }
ハマった点:YouTubePlayerが勝手にAudioFocusを奪う
実装中に最も悩まされたのが、YouTubePlayerが内部的にAUDIOFOCUS_GAINを必ず要求してしまう点です。これはYouTube Player APIの仕様で、公式に無効化するAPIが提供されていません。
特にAndroid 8以降では、AudioFocusの管理が厳格になり、単にsetVolume(0)としても、一瞬ではありますがAudioFocusが奪われてしまいます。
解決策:WebViewベースのカスタムプレーヤーへの移行
最終的な解決策として、YouTube Player APIをやめて、WebView内でYouTube IFrame Player APIを使用する方法を採用しました。
Javapublic class CustomYouTubeWebView extends WebView { private AudioFocusManager audioFocusManager; private boolean shouldPlayAudio = false; public CustomYouTubeWebView(Context context) { super(context); init(context); } private void init(Context context) { audioFocusManager = new AudioFocusManager(context); WebSettings settings = getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); // JavaScriptインターフェースを追加 addJavascriptInterface(new YouTubeJavaScriptInterface(), "Android"); // HTMLを読み込む loadDataWithBaseURL("https://www.youtube.com", getYouTubeHTML(), "text/html", "utf-8", null); } private String getYouTubeHTML() { return "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <style>\n" + " body { margin: 0; }\n" + " #player { width: 100%; height: 100%; }\n" + " </style>\n" + "</head>\n" + "<body>\n" + " <div id=\"player\"></div>\n" + " <script>\n" + " var player;\n" + " function onYouTubeIframeAPIReady() {\n" + " player = new YT.Player('player', {\n" + " videoId: 'VIDEO_ID',\n" + " playerVars: {\n" + " 'playsinline': 1,\n" + " 'controls': 1,\n" + " 'rel': 0\n" + " },\n" + " events: {\n" + " 'onReady': onPlayerReady,\n" + " 'onStateChange': onPlayerStateChange\n" + " }\n" + " });\n" + " }\n" + " \n" + " function onPlayerReady(event) {\n" + " // 自動再生を防止\n" + " event.target.mute();\n" + " }\n" + " \n" + " function onPlayerStateChange(event) {\n" + " if (event.data == YT.PlayerState.PLAYING) {\n" + " Android.onVideoPlaying();\n" + " }\n" + " }\n" + " </script>\n" + " <script src=\"https://www.youtube.com/iframe_api\"></script>\n" + "</body>\n" + "</html>"; } private class YouTubeJavaScriptInterface { @JavascriptInterface public void onVideoPlaying() { // ここで初めてAudioFocusを要求する if (!shouldPlayAudio) { shouldPlayAudio = true; audioFocusManager.requestAudioFocus(); // WebView内のプレイヤーをアンミュート post(() -> loadUrl("javascript:player.unMute();")); } } } public void pauseVideo() { post(() -> loadUrl("javascript:player.pauseVideo();")); audioFocusManager.abandonAudioFocus(); shouldPlayAudio = false; } }
まとめ
本記事では、AndroidでYouTube動画を読み込む際のバックグラウンド音楽停止問題と、それをJavaで解決する3つの方法を紹介しました。
- 即効策:
setVolume(0)によるミュート状態での初期化 - 中級編: 独自のAudioFocusManager実装
- 完全解決: WebView + IFrame APIへの移行
特にWebViewベースの実装により、AudioFocusの完全な制御が可能になり、ユーザーエクスペリエンスが大幅に向上しました。今後は、ExoPlayerを使った独自のYouTube再生ライブラリの開発や、MediaSessionとの連携についても検討していきます。
参考資料
- YouTube Player API for Android - Official Docs
- Managing Audio Focus - Android Developers
- YouTube IFrame Player API
- Handling Changes in Audio Output - Android Developers
