0Pricing
Android Academy · 강의

오디오와 동영상 재생

Android에서 간단한 미디어 재생을 구현합니다.

오디오와 동영상 재생은(는) CoddyKit의 무료 Android Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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를 사용해 뷰를 삽입하고 플레이어를 연결합니다.

이렇게 하면 재생/일시정지, 탐색 막대, 전체 화면 기능을 별도의 구현 없이 사용할 수 있습니다.

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로 감싸세요. 그러면 시스템 제어, 잠금 화면, 블루투스 버튼과 연동됩니다.

구현할 내용이 더 많지만 핵심은 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/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Android Academy 강의 전체를 잠금 해제할 수 있습니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“오디오와 동영상 재생”에서 뭘 배우나요?

Android에서 간단한 미디어 재생을 구현합니다. 브라우저에서 직접 실행하는 실습 코드로 Android Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Android Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Android Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“오디오와 동영상 재생” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Android Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Android Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Coil로 이미지 로드
  2. 자리표시자와 오류 상태
  3. 캐싱과 성능
  4. 오디오와 동영상 재생
← Android Academy(으)로 돌아가기