Media3 (ExoPlayer) + Compose: 비디오 및 오디오 플레이어 구현

발행: (2026년 3월 2일 오전 10:18 GMT+9)
5 분 소요
원문: Dev.to

I’m happy to translate the article for you, but I need the full text you’d like translated. Could you please paste the content (excluding the source line you already provided) here? Once I have it, I’ll translate it into Korean while preserving all formatting, markdown, and code blocks.

ExoPlayer.Builder 설정

ExoPlayer 인스턴스를 생성하고 구성합니다:

import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector

val exoPlayer = ExoPlayer.Builder(context)
    .setTrackSelector(DefaultTrackSelector(context))
    .build()

MediaItems 로드하기

재생할 미디어 아이템을 하나 또는 여러 개 추가합니다:

import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes

// 단일 비디오
val mediaItem = MediaItem.Builder()
    .setUri("https://example.com/video.mp4")
    .setMimeType(MimeTypes.APPLICATION_MP4)
    .build()
exoPlayer.setMediaItem(mediaItem)

// 여러 아이템이 포함된 재생 목록
val playlist = listOf(
    MediaItem.fromUri("https://example.com/video1.mp4"),
    MediaItem.fromUri("https://example.com/video2.mp4"),
    MediaItem.fromUri("https://example.com/audio.mp3")
)
exoPlayer.setMediaItems(playlist)
exoPlayer.prepare()

AndroidView에서 PlayerView

AndroidView를 사용하여 Compose에 PlayerView를 임베드합니다:

import androidx.media3.ui.PlayerView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

AndroidView(
    factory = { context ->
        PlayerView(context).apply {
            player = exoPlayer
            useController = true
            controllerShowTimeoutMs = 5000
            controllerHideTimeoutMs = 3000
        }
    },
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp)
)

Compose에서 사용자 정의 오디오 플레이어 UI

비디오 없이 오디오용 사용자 정의 플레이어 UI를 구축합니다:

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment

val currentPosition by exoPlayer.currentPosition.collectAsState()
val duration by exoPlayer.duration.collectAsState()
val isPlaying by exoPlayer.isPlaying.collectAsState()

Column(
    modifier = Modifier
        .padding(16.dp)
        .fillMaxWidth(),
    verticalArrangement = Arrangement.spacedBy(12.dp)
) {
    // Play/Pause Button
    Button(onClick = {
        if (isPlaying) exoPlayer.pause() else exoPlayer.play()
    }) {
        Text(if (isPlaying) "⏸ Pause" else "▶ Play")
    }

    // Progress Slider
    Slider(
        value = currentPosition.toFloat(),
        onValueChange = { exoPlayer.seekTo(it.toLong()) },
        valueRange = 0f..duration.toFloat(),
        modifier = Modifier.fillMaxWidth()
    )

    // Time Display
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(formatTime(currentPosition))
        Text(formatTime(duration))
    }
}

fun formatTime(ms: Long): String {
    val seconds = (ms / 1000) % 60
    val minutes = (ms / 60000) % 60
    return "%02d:%02d".format(minutes, seconds)
}

라이프사이클 인식 재생

앱이 백그라운드로 전환될 때 재생을 일시 정지합니다:

import androidx.compose.runtime.*
import androidx.lifecycle.*

DisposableEffect(Unit) {
    val lifecycleObserver = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_RESUME -> exoPlayer.play()
            Lifecycle.Event.ON_PAUSE -> exoPlayer.pause()
            else -> {}
        }
    }

    val lifecycle = LocalLifecycleOwner.current.lifecycle
    lifecycle.addObserver(lifecycleObserver)

    onDispose {
        lifecycle.removeObserver(lifecycleObserver)
    }
}

DisposableEffect를 사용한 적절한 리소스 해제

Compose 컴포넌트가 폐기될 때 플레이어를 해제합니다:

DisposableEffect(Unit) {
    onDispose {
        exoPlayer.release()
    }
}

재생 목록 관리

재생 목록 내에서 재생 흐름을 제어합니다:

// Move to next item
Button(onClick = { exoPlayer.seekToNextMediaItem() }) {
    Text("Next Track")
}

// Move to previous item
Button(onClick = { exoPlayer.seekToPreviousMediaItem() }) {
    Text("Previous Track")
}

// Repeat modes
IconButton(onClick = {
    exoPlayer.repeatMode = when (exoPlayer.repeatMode) {
        Player.REPEAT_MODE_OFF -> Player.REPEAT_MODE_ALL
        Player.REPEAT_MODE_ALL -> Player.REPEAT_MODE_ONE
        else -> Player.REPEAT_MODE_OFF
    }
}) {
    Text("Repeat: ${exoPlayer.repeatMode}")
}

모범 사례

  • 플레이어 리소스를 해제하려면 항상 DisposableEffect를 사용하세요.
  • 배터리를 절약하기 위해 앱이 백그라운드로 전환될 때 재생을 일시 중지하세요.
  • 적절한 상태 관리를 위해 라이프사이클 옵저버를 사용하세요.
  • 네트워크 오류를 처리하고 로컬 재생으로 대체할 수 있도록 하세요.
  • 다양한 미디어 포맷(MP4, WebM, DASH, HLS)으로 테스트하세요.
  • 사용자에게 재생 제어와 진행 표시를 제공하세요.

Media3는 오디오 및 비디오 재생을 위한 견고한 기반을 제공합니다. Compose의 선언형 UI와 결합하면 최소한의 보일러플레이트로 현대적인 미디어 경험을 구축할 수 있습니다.

더 많은 Android 개발 패턴을 탐색하세요: 8 Android App Templates →

0 조회
Back to Blog

관련 글

더 보기 »