ExoPlayer 란?
Exoplayer 는 안드로이드에서 자주 사용되는 Media 라이브러리인데 상세한 설명은 안드로이드 개발자 공식을 참고하자
출처 : https://developer.android.com/guide/topics/media/exoplayer?hl=ko
ExoPlayer | Android 개발자 | Android Developers
ExoPlayer 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. ExoPlayer는 Android 프레임워크에 속하지 않고 Android SDK에서 별도로 배포되는 오픈소스 프로젝트입니다.
developer.android.com
기본적으로 우리가 흔히 아는 동영상의 컨테이너 포맷(MP4, AVI, WMV 등등..) 은 거의 지원한다고 볼 수 있고
설령 지원하지 않는 포맷이 있어도 FFMPEG 에 원하는 포맷을 넣고 빌드하면 정말 범용성이 높다.
FFmepg 는 추후 별도로 글을 작성할 것이고 오늘은 Exoplayer 로 동영상을 재생하는 간단한 코드만 볼 것이다.
implementation 'com.google.android.exoplayer:exoplayer-common:2.19.1'
implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
글을 쓰는 2023년 기준으로 Exoplayer 2 의 최신 버전은 2.19.1 이다.
참고사항이라 하면 공식적으로 Exoplayer 2 자체는 Deprecated 되었다.. (Media 3 에 통합되었으니 업데이트 받고싶으면 마이그레이션 해라! 라고 하던데 아직 2022년에 Exoplayer 1.5xx 버전을 써서 잘만 구현해내던 개발자도 본 나로써는.. 모르겠다)
이제 액티비티가 실행되면 동영상이 재생되는 형태로 구현해 볼 것이다.
일단 동영상이 재생될 View 는 PlayerView 라는 Exoplayer 에서 지원하는 View 인데 이것을 레이아웃에 원하는 위치에 배치해보자.
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/player_view"
android:layout_width="@dimen/player_width"
android:layout_height="@dimen/player_height"
app:use_controller="false"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
/>
이런 식으로 화면에 보여질 레이아웃에 PlayerView 를 배치해주고
class MainActivity : AppCompatActivity() {
private var mPlayerView: StyledPlayerView? = null
private var mPlayer: ExoPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPlayerView = findViewById(R.id.player_view)
}
override fun onStart() {
super.onStart()
initPlayer()
}
override fun onStop() {
super.onStop()
releasePlayer()
}
private fun initPlayer(){
val factory = DefaultRenderersFactory(this)
factory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
val parameters = DefaultTrackSelector.ParametersBuilder()
.build()
val trackSelector = DefaultTrackSelector(this, parameters)
/*
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(10000,
20000,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
.setTargetBufferBytes(DEFAULT_BUFFER_SIZE*DEFAULT_BUFFER_SIZE)
.build()
*/
/*
val bandwidthMeter = DefaultBandwidthMeter.Builder(this).build()
*/
val builder = ExoPlayer.Builder(this)
mPlayer = builder.setTrackSelector(trackSelector)
.setRenderersFactory(factory)
.build()
mPlayer?.addListener(mListener)
mPlayerView?.player = mPlayer
}
private fun releasePlayer(){
mPlayer?.stop()
mPlayer?.release()
mPlayer = null
}
private val mListener = object : Player.Listener{
override fun onPlayerError(error: PlaybackException) {
}
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when (playbackState) {
Player.STATE_READY -> {
}
Player.STATE_BUFFERING -> {
}
Player.STATE_ENDED -> {
}
Player.STATE_IDLE -> {
}
}
}
}
}
주석 처리해둔 부분은 initPlayer 가 사실 프로젝트에서 긁어온 코드인데 저 부분은 통상적으론 없어도 큰 지장이 없는 부분이어서 세부 값은 Default 로 바꾼 채로 주석처리 해두었다.
설명을 추가하자면
- LoadControl
데이터(스트림) 을 로드하고 버퍼링하는 부분을 제어하는 인터페이스인데 그냥 DefaultLoadControl 로 써도 큰 지장 없다.
나 같은 경우는 테스트 케이스 중에 서버 자체적인 오디오 코덱 이슈랑 맞물려서 스트림(데이터)이 타겟 버퍼만큼 들어오질 않아 버퍼링이 무한으로 걸리던 상황이 있었어서 타겟 버퍼를 조금 내려주었다. - BandwidthMeter
네트워크에서 받은 데이터의 양이나 속도를 기반으로 네트워크의 대역폭을 알 수 있고 재생 품질을 조정할 수 있다.
이전에 그냥 Default 로 두고 넘어갔다가 위에 LoadControl 쪽에서 있던 그 이슈때 이 쪽도 인터페이스를 별도로 구현하고 구현한 김에 UI 에 동영상 품질 느낌으로 띄웠었다. - Listener
현재 재생 상태가 변할 때 라던지, 스트림이 끊기거나 잘못되서 에러가 발생하면 Listener 에 state 가 Int 형태로 전달된다. 에러가 났을 때 다시 재접속 시도를 한다던가.. 소스 자체에 문제가 생겼을 때 유저에게 노티를 한다던가..
유용한 기능이다.
아무튼 위에 작성해둔 코드대로 작성하면 PlayerView 에 Exoplayer 가 붙는다.
이렇게 붙여둔 플레이어에 Media 를 MediaSource 라는 형태로 바꿔서 올려주고 실행시키면 동영상이 재생된다.
아래 코드는 url 을 기준으로 작성되었다.
private fun setupMediaSource(streamUrl: String) {
val factory = DefaultHttpDataSource.Factory()
val upstreamFactory: DataSource.Factory =
DefaultDataSource.Factory(this, factory)
val mediaItem = MediaItem.fromUri(Uri.parse(streamUrl))
val progressiveFactory = ProgressiveMediaSource.Factory(upstreamFactory)
val mediaSource = progressiveFactory.createMediaSource(mediaItem)
/*
// 만약 동영상의 프로토콜이 HLS 면 위의 두 줄 대신 아래 코드를 사용하자.
val m3u8Factory = HlsMediaSource.Factory(upstreamFactory)
val mediaSource = m3u8Factory.createMediaSource(mediaItem)
*/
mPlayer?.setMediaSource(mediaSource)
mPlayer?.playWhenReady = true
mPlayer?.prepare()
}
ExoPlayer 2 를 기준으로 ProgressiveMediaSource 에서 꽤 많은 포맷을 지원하기 때문에 구현하기 굉장히 편하다.
ProgressiveMediaSource 에서 지원이 안되는 m3u8 같은 HLS 프로토콜 역시 HlsMediaSource 클래스를 지원해주기 때문에 쉽게 구현 가능하다.
위의 메서드 setupMediaSource 를 동영상을 재생시키는 트리거에 붙여주면 된다.
추가로, File 이면
MediaItem.fromUri(Uri.parse(streamUrl))
이 부분을
MediaItem.fromUri(Uri.fromFile(file))
이렇게 바꿔주면 된다.
'Android > Kotlin' 카테고리의 다른 글
Android 배워보자 Compose - (1) (0) | 2024.01.10 |
---|---|
Android Room Database 사용하기 (1) | 2024.01.08 |
Android Jackson Library 를 이용한 JSON Parsing (1) | 2024.01.05 |
Android 파일 다운로드 받기 (0) | 2024.01.05 |
Android Custom Dialog 만들기 (2) | 2024.01.04 |