2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Android】各メディア系ライブラリ用途別まとめ

Last updated at Posted at 2024-11-16

SoundPool

効果音に主に使います。

オーディオ属性のビルダーでサウンド用途やコンテンツタイプを指定します。
SoundPoolのビルダーでは上記で作成したオーディオ属性や、同時に流せる効果音の最大数などを指定します。
予めrawなどのディレクトリに入れておいた音声データのリソースをSoundPoolのload()で読み込みます。

SoundPoolScreen.kt
        val context = LocalContext.current
        // オーディオ属性の定義
        val audioAttributes = AudioAttributes.Builder()
            // 用途の指定
            .setUsage(AudioAttributes.USAGE_MEDIA)
            // コンテンツのタイプを指定
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            .build()
        val soundPool = SoundPool.Builder()
            .setAudioAttributes(audioAttributes)
            // 同時に流せる最大数
            .setMaxStreams(3)
            .build()
        // 引数は順にコンテキスト、リソースID、優先度
        val pianoId = soundPool.load(context, R.raw.piano, 1)

下記は実際に流す画面サンプルです。
メモリ不足にならないように、DisposableEffectで画面破棄された時にSoundPoolインスタンスのrelease()をしています。
実際に流すにはplay()関数を使っています。

SoundPoolScreen.kt
    Column {
        // メモリ不足にならないようにrelease()やunload()を行う
        DisposableEffect(LocalLifecycleOwner.current) {
            onDispose { soundPool.release() }
        }

        Button(onClick = {
            // 引数は順にID、左音量、右音量、優先度、ループ、再生速度
            soundPool.play(pianoId, 1.0f, 1.0f, 1, 0, 1.0f)
        }) {
            Text(text = "Piano")
        }
    }

MediaPlayer

BGM等音楽再生に主に使います。

MediaPlayer.create()でContextと音楽リソースを指定してインスタンスを作ります。
こちらでも画面破棄時にはrelease()でメモリを解放しています。

MediaPlayerScreen.kt
        val context = LocalContext.current
        val lifecycleOwner = LocalLifecycleOwner.current
        val mediaPlayer = MediaPlayer.create(context, R.raw.acoustic)
        DisposableEffect(lifecycleOwner) {
            onDispose {
                mediaPlayer?.release()
            }
        }

以下は音楽再生のサンプルです。
mediaPlayer.start()で実際に音楽を鳴らします。
isPlayingで音楽再生中かどうかのフラグを持って、2番目のボタンに一時停止の役割を持たせています。
pause()は一時停止で、stop()は完全に停止です。
prepare()でまた始めから再生できるようにリソースを準備します。

MediaPlayerScreen.kt
    Column {
        var isPlaying by remember {
            mutableStateOf(false)
        }
        Button(
            onClick = {
                mediaPlayer?.start()
                isPlaying = true
            }) {
            Text(text = "Acoustic")
        }
        Button(
            onClick = {
                if (isPlaying) {
                    mediaPlayer.pause()
                    isPlaying = false
                } else {
                    mediaPlayer?.start()
                    isPlaying = true
                }
            }) {
            Text(
                text = if (isPlaying) "Pause"
                else "Play"
            )
        }
        Button(
            onClick = {
                mediaPlayer?.stop()
                isPlaying = false
                mediaPlayer.prepare()
            }) {
            Text(text = "Stop")
        }
    }

ExoPlayer

動画再生に主に使います。

こちらを参考にさせていただきました。

動画のURLやビルダーを準備します。
seekTo()で初期の再生位置を指定できます。

ExoPlayerScreen.kt
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val url =
        "https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4"
    val exoPlayer = remember(context) {
        ExoPlayer.Builder(context).build().apply {
            setMediaItem(MediaItem.fromUri(Uri.parse(url)))
            // 再生準備させる
            prepare()
            // 再生位置の指定
            seekTo(0L)
        }
    }

諸々のフラグ管理のため動画の再生状態をenumで作っています。
シークバーの表示に使うため、currentTimeという変数を作っておきました。

ExoPlayerScreen.kt
    enum class VideoState {
        LOADING,
        PAUSE,
        START,
        ENDED,
    }

    // 再生状態
    var videoState by remember {
        mutableStateOf(VideoState.LOADING)
    }
    // シークバー用動画の進捗度
    var currentTime by remember {
        mutableLongStateOf(0L)
    }
    // 動画の状態によってUIパーツの状態・表示を切り替える
    exoPlayer.addListener(object : Player.Listener {
        // 状態を通知する
        override fun onPlaybackStateChanged(playbackState: Int) {
            super.onPlaybackStateChanged(playbackState)
            // 再生準備完了時に自動的に再生開始するかどうか
            val playWhenReady = exoPlayer.playWhenReady
            val newState = when {
                playbackState == Player.STATE_ENDED && playWhenReady -> VideoState.ENDED
                playbackState == Player.STATE_BUFFERING || playbackState == Player.STATE_IDLE -> VideoState.LOADING
                playbackState == Player.STATE_READY && playWhenReady -> VideoState.START
                playbackState == Player.STATE_READY -> VideoState.PAUSE
                else -> null
            }
            if (newState != null && newState != videoState) {
                videoState = newState
            }
        }
    })

    // 動画再生時のシークバー連動
    if (videoState == VideoState.START) {
        LaunchedEffect(Unit) {
            repeat(Int.MAX_VALUE) {
                delay(1000)
                // 動画時間を取得してcurrentTimeに反映
                currentTime = exoPlayer.currentPosition
            }
        }
    }

    DisposableEffect(lifecycleOwner) {
        // バックグラウンドにいったら停止
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_PAUSE) {
                exoPlayer.pause()
                videoState = VideoState.PAUSE
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)

        // 画面の破棄時にリリース
        onDispose {
            exoPlayer.run {
                playWhenReady = false
                clearVideoSurface()
                release()
            }
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

下記が実際のUIになります。
実際の表示にはAndroidViewを使用しています。
exoPlayer.play()で再生、pause()で一時停止します。
Sliderを使用してシークバーも作成しています。

ExoPlayerScreen.kt
    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        AndroidView(
            modifier = Modifier
                .fillMaxWidth()
                .wrapContentHeight(),
            factory = { context ->
                PlayerView(context).apply {
                    // デフォルトのUIパーツを表示するかどうか
                    useController = false
                    player = exoPlayer
                }
            }
        )
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .height(42.dp)
                .background(Color.Black)
                .padding(start = 8.dp)
                .align(Alignment.BottomCenter),
            verticalAlignment = Alignment.CenterVertically
        ) {
            // 再生・停止ボタン
            if (videoState == VideoState.START) {
                Button(onClick = {
                    exoPlayer.pause()
                    videoState = VideoState.PAUSE
                }) {
                    Image(
                        painter = painterResource(R.drawable.pause),
                        contentDescription = null
                    )
                }
            } else {
                Button(onClick = {
                    exoPlayer.play()
                    videoState = VideoState.START
                }) {
                    Image(
                        painter = painterResource(R.drawable.play),
                        contentDescription = null
                    )
                }
            }

            // 動画の時間
            Text(
                text = SimpleDateFormat("mm:ss", Locale.JAPAN).format(Date(currentTime))
            )

            // シークバー
            Slider(
                value = currentTime.toFloat(),
                // シークバー操作を動画の進捗に反映させる
                onValueChange = { timeMs ->
                    val time = timeMs.toLong()
                    exoPlayer.seekTo(time)
                    currentTime = time
                },
                // 動画の長さまで
                valueRange = 0f..exoPlayer.duration.coerceAtLeast(0L).toFloat(),
                colors = SliderDefaults.colors()
            )
        }
    }
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?