音声と動画の再生
Androidでシンプルなメディア再生を実装します。
「音声と動画の再生」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。
画像の先へ:メディア再生
Coilは画像を美しく表示できますが、アプリでは通知音、ポッドキャスト、バックグラウンドの動画、動画フィードなど、音声や動画も再生します。Androidにはメディア専用のAPIがあります。
現在推奨されているライブラリはMedia3 (ExoPlayer)です。古いMediaPlayerよりも、ストリーミング、形式、バッファリング、再生コントロールをはるかに適切に処理できます。このレッスンでは、Composeアプリで簡単な音声と動画を再生します。
Media3の依存関係を追加する
Media3は複数のモジュールに分かれています。ほとんどのアプリでは、プレーヤーエンジンのmedia3-exoplayerと、すぐに使えるプレーヤービューのmedia3-uiを使用します。Composeで動画を表示する場合は、AndroidView経由でPlayerViewを使うこともできます。
// build.gradle.kts (module level)
dependencies {
implementation("androidx.media3:media3-exoplayer:1.5.0")
implementation("androidx.media3:media3-ui:1.5.0")
}ExoPlayerを作成する
ExoPlayerはContextから構築します。URLまたはローカルファイルを指定したMediaItemを渡し、prepare()を呼び出して再生を開始します。独自のスレッドで動作するため、UIをブロックしません。
重要なのは、プレーヤーがシステムリソースを保持するため、解放が必要なことです。詳しくは後ほど説明します。
import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
fun createPlayer(context: Context, url: String): ExoPlayer {
val player = ExoPlayer.Builder(context).build()
player.setMediaItem(MediaItem.fromUri(url))
player.prepare()
player.playWhenReady = true
return player
}音声を再生する
音声のみを再生する場合も設定は同じで、MediaItemに音声URL(mp3、aacなど)を指定するだけです。表示するビューはないため、プレーヤーオブジェクトを通じてplay()、pause()、seekTo()を呼び出して制御します。
これだけでポッドキャストや音楽プレーヤーを構築できます。
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
fun playPodcast(player: ExoPlayer, audioUrl: String) {
player.setMediaItem(MediaItem.fromUri(audioUrl))
player.prepare()
player.play()
}
fun togglePause(player: ExoPlayer) {
if (player.isPlaying) player.pause() else player.play()
}Composeでプレーヤーを記憶する
Composeでは、プレーヤーを一度だけ作成し、再コンポジションをまたいで保持する必要があります。rememberを使うと、再コンポジションのたびに新しいプレーヤーが作成されるのを防げます。
リソースのリークを防ぐため、適切なクリーンアップ(次のシーン)と組み合わせてください。
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun rememberPlayer(url: String): ExoPlayer {
val context = LocalContext.current
return remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(url))
prepare()
}
}
}プレーヤーを解放する
プレーヤーはコーデック、バッファ、オーディオフォーカスを保持します。解放し忘れるとメモリリークが発生し、画面がなくなった後も再生が続くことがあります。Composeでは、コンポーザブルがコンポジションを離れるときにDisposableEffectがクリーンアップを実行します。
そこで必ずplayer.release()を呼び出してください。
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun ManagedPlayer(player: ExoPlayer) {
DisposableEffect(player) {
onDispose {
player.release()
}
}
// ... use player here
}PlayerViewで動画を表示する
Media3には、コントロール付きのすぐに使えるPlayerViewが用意されています。Composeにはネイティブの動画用コンポーザブルがないため、AndroidViewを使ってViewを埋め込み、プレーヤーを接続します。
これだけで、再生と一時停止、シークバー、全画面表示を利用できます。
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@Composable
fun VideoPlayer(player: ExoPlayer, modifier: Modifier = Modifier) {
AndroidView(
factory = { context ->
PlayerView(context).apply {
this.player = player
}
},
modifier = modifier
)
}動画画面を完成させる
ここまでの内容をまとめましょう。プレーヤーを作成して記憶し、PlayerViewに接続し、破棄時に解放します。これは再利用できる自己完結型の動画画面です。
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
@Composable
fun VideoScreen(videoUrl: String) {
val context = LocalContext.current
val player = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUrl))
prepare()
playWhenReady = true
}
}
DisposableEffect(Unit) { onDispose { player.release() } }
AndroidView(
factory = { PlayerView(it).apply { this.player = player } },
modifier = Modifier.fillMaxWidth().height(220.dp)
)
}再生状態を追跡する
UI(再生・一時停止アイコンや進行状況バーなど)を更新するには、Player.Listenerを使ってプレーヤーを監視します。バッファリング中、準備完了、再生終了などの状態変化に加え、再生がアクティブかどうかも通知されます。
画面が表示されたときにリスナーを追加し、破棄時に削除してください。
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
fun observe(player: ExoPlayer, onPlaying: (Boolean) -> Unit): Player.Listener {
val listener = object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
onPlaying(isPlaying)
}
override fun onPlaybackStateChanged(state: Int) {
when (state) {
Player.STATE_BUFFERING -> { /* show spinner */ }
Player.STATE_READY -> { /* ready */ }
Player.STATE_ENDED -> { /* finished */ }
}
}
}
player.addListener(listener)
return listener
}アプリがバックグラウンドに移行したら一時停止する
インライン動画(音楽アプリではない場合)では通常、ユーザーが画面を離れたら一時停止し、戻ってきたら再開したいものです。Lifecycleを監視し、ON_PAUSEで一時停止することで、再生をComposeのライフサイクルに連動させられます。
これにより、ユーザーに見えない動画の再生を防ぎ、バッテリーを節約できます。
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.media3.exoplayer.ExoPlayer
@Composable
fun PauseWithLifecycle(player: ExoPlayer) {
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_PAUSE -> player.pause()
Lifecycle.Event.ON_RESUME -> player.play()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
}MediaSessionでバックグラウンド音声を再生する
アプリがバックグラウンドに移行しても音声(音楽やポッドキャスト)の再生を続けたい場合は、再生をMediaSessionServiceでラップします。これにより、システムコントロール、ロック画面、Bluetoothボタンと連携できます。
より複雑な構成になりますが、基本的な考え方はMediaSessionを通じてプレーヤーを公開することです。
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
class PlaybackService : MediaSessionService() {
private var session: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
session = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = session
override fun onDestroy() {
session?.run { player.release(); release() }
session = null
super.onDestroy()
}
}クイックチェック
ExoPlayerを保持するCompose画面で、画面がコンポジションを離れるときにプレーヤーを解放するには、どのCompose APIを使うべきですか?
まとめ
音声と動画の再生機能を追加しました。
- MediaPlayerの現代的な代替となるMedia3 ExoPlayerを使います。
- プレーヤーを構築し、
MediaItemを設定して、prepare()を呼び出し、その後play()を呼び出します。 - Composeではプレーヤーを
rememberし、DisposableEffectのonDisposeで解放します。 AndroidViewでPlayerViewを埋め込んで動画を表示します。Player.Listenerで状態を追跡し、バックグラウンド音声にはMediaSessionServiceを使います。
これで、Coilを使った画像とメディアの読み込みコースは完了です。画像を滑らかに読み込み、最新のAndroidの方法でメディアを再生できるようになりました。
よくある質問
「音声と動画の再生」レッスンは無料ですか?
はい。「音声と動画の再生」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。
「音声と動画の再生」で何を学びますか?
Androidでシンプルなメディア再生を実装します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Android Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「音声と動画の再生」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAndroid Academyレッスンでコードを書いて実行できますか?
はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。