ExoPlayer 삽질

Arakene·2024년 6월 30일

구조

  • ExoPlayer instance
  • MediaItem
  • Rendering View

ExoPlayer instance

val player = ExoPlayer.Builder(context).build()

을 통해 생성가능

player instance는 반드시 application's main thread 에서만 접근을 해야함Exoplayer의 UI component or IAM extensions을 사용하려면 반드시 메인스레드에서 사용해야함

Rendering View

Exoplayer 라이브러리는 미디어 재생을 위해 미리 만들어둔 UI component들이 있음
주로 PlayerView를 사용하게됨 해당 뷰는 PlayerControlView, SubtitleView, Surface를 상속받아 만들어졌음 아쉽게도 Compose를 위한 컴포넌트는 없고 오로지 layout xml에서만 사용이 가능하다. 따라서 Compose에서 사용하려면 AndroidView를 사용해야한다.
PlayerView는 사용이 강제된 것이 아니라서 PlayerControlView를 사용해도된다. 만약 오디오만 재생되는 케이스라면 해당 뷰를 사용하는것을 더 추천하고 있다.

Use of ExoPlayer's pre-built UI components is optional. For video apps that implement their own UI, the target SurfaceView, TextureView, SurfaceHolder or Surface can be set using ExoPlayer's setVideoSurfaceView, setVideoTextureView, setVideoSurfaceHolder, and setVideoSurface methods respectively. ExoPlayer's addTextOutput method can be used to receive captions that should be rendered during playback.

Media Item

영상을 재생할 데이터를 뜻한다.
URI를 알고 있는 경우

val mediaItem = MediaItem.fromUri(videoUri)

MediaItem.Builder를 통해 간단하게 생성할 수 있다.

val mediaItem = MediaItem.Builder().setMediaId(mediaId).setTag(myAppData).setUri(videoUri).build()

위 예시처럼 메타 아이디, 메타정보를 추가할 수 있다. 여기서 추가된 태그와 같은 정보들은 repeat모드에 의해 다시 재생될때 아니면 다음 아이템으로 넘어갈 때
Listener.onMediaItemTransition(MediaItem, @MediaItemTransitionReason) 콜백에서 확인할 수 있다.

만약 DRM에 의해 보호받는 컨텐츠를 재생하려면 반드시 DRM properties를 적용해줘야한다. 이때 UUID는 필수값이다.

val mediaItem =
  MediaItem.Builder()
    .setUri(videoUri)
    .setDrmConfiguration(
      MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
        .setLicenseUri(licenseUri)
        .setMultiSession(true)
        .setLicenseRequestHeaders(httpRequestHeaders)
        .build()
    )
    .build()

Sideloading subtitle tracks

자막기능이다.
MediaItem.Subtitle 인스턴스를 아이템에 추가해주면된다.

val subtitle =
 SubtitleConfiguration.Builder(subtitleUri)
   .setMimeType(mimeType) // The correct MIME type (required).
   .setLanguage(language) // The subtitle language (optional).
   .setSelectionFlags(selectionFlags) // Selection flags for the track (optional).
   .build()
val mediaItem =
 MediaItem.Builder().setUri(videoUri).setSubtitleConfigurations(listOf(subtitle)).build()

DefaultMediaSourceFactory는 각 자막트랙의 SingleSampleMediaSource 를 컨텐트 미디어 소스에 합치기 위해 MergingMediaSource를 기본적으로 사용한다. DefaultMediaSourceFactory는 multi-period DASH는 지원하지 않으니 주의

Clipping a media stream

클립기능

val mediaItem =
 MediaItem.Builder()
   .setUri(videoUri)
   .setClippingConfiguration(
     MediaItem.ClippingConfiguration.Builder()
       .setStartPositionMs(startPositionMs)
       .setEndPositionMs(endPositionMs)
       .build()
   )
   .build()

DefaultMediaSourceFactory는 클립하기위해서 ClippingMediaSource를 사용함 더 추가적인 클립관련 프로퍼티가 있음 문서확인

광고도 삽입 가능

val mediaItem =
  MediaItem.Builder()
    .setUri(videoUri)
    .setAdsConfiguration(MediaItem.AdsConfiguration.Builder(adTagUri).build())

Media Source

재생가능한 컨텐츠들은 모두 MediaItem으로 표현되지만 실 사용을 위해서는 반드시 MediaSource가 필요하다.
기본적으로는 DefaultMediaSourceFactory가 사용되며 아래에 있는 종류들을 생성할 수 있다.

  • DashMediaSource for DASH.
  • SsMediaSource for SmoothStreaming.
  • HlsMediaSource for HLS.
  • ProgressiveMediaSource for regular media files.
    • MP4, FMP4, M4A, MP3 등등 흔히 아는 동영상 파일 재생 시 사용
  • RtspMediaSource for RTSP.

다른 블로그 글들에서는 인스턴스 재사용을 하지 말라는데 MediaSource에 들어가보면

Instances can be re-used, but only for one ExoPlayer instance simultaneously

와 같이 주석으로 설명해주고 있다. 아마 버전이 올라오면서 변경된 듯 하다

Custom

MediaSource는 커스텀이 가능하다. Player를 생성할 때 팩토리를 주입하는 방식으로 커스텀을 한다.

  val mediaSourceFactory: MediaSource.Factory =
    DefaultMediaSourceFactory(context)
      .setDataSourceFactory(cacheDataSourceFactory)
      .setLocalAdInsertionComponents(adsLoaderProvider, playerView)
  val player = ExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build()

더 다양한 기능들은 javadoc참고

ExoPlayer는 media items를 받는게아닌 media sources를 받도록 되어있다.
이렇게 설계되어있기 때문에 player 내부의 MediaSource.Factory를 따로 설정하지 않아도 player 인스턴스에 바로 media source 를 추가해줄 수 있다.

// Set a list of media sources as initial playlist.
exoPlayer.setMediaSources(listOfMediaSources)
// Add a single media source.
exoPlayer.addMediaSource(anotherMediaSource)

// Can be combined with the media item API.
exoPlayer.addMediaItem(/* index= */ 3, MediaItem.fromUri(videoUri))

exoPlayer.prepare()
exoPlayer.play()

Advanced

Exoplayer는 다양한 MediaSource impl를 제공하는데 여러 MediaSource를 결합하고 변경할 수 있다.
주로 사용되는건

  • ClippingMediaSource
    • 영상 클립을 하게해준다. 만약 해당 미디어 소스만 사용하는경우에는 MediaItem.ClippingConfiguration의 사용을 권장한다.
  • FilteringMediaSource
    • 특정 타입으로 트랙을 필터링하게해준다. 예를 들어 비디오와 오디오 모두 있는 영상만 필터링할 수 있다. 만약 필터기능만 사용한다면 track selection parameters사용을 권장한다.
  • MergingMediaSource
    • 여러 미디어 소스를 병렬적으로 재생하기 위해 사용한다. 주로 광고를 이용할 때 사용한다. 만약 side-loaded subtitle 추가에만 사용된다면 MediaItem.SubtitleConfiguration사용을 권장한다.
  • ConcatenatingMediaSource2
    • 여러 미디어 소스들을 연속적으로 재생하고 싶은 경우 사용한다. 하나의 Timeline.window가 노출되며 하나의 영상을 재생하는 듯한 느낌을 준다. (유튜브 플레이 리스트 같은 느낌) 만약 하나의 영상처럼 보일 필요가 없는데 다수의 영상을 연속적으로 재생하고 싶은거라면 Player.addMediaItem와 같은 playlist API를 추천한다.
  • SilenceMediaSource
  • AdsMediaSource
  • ServerSideAdInsertionMediaSource
    가 있다.

    Track Selection

    Media source에 다양한 트랙이 있는 경우 어떤 트랙이 있는지, 어떤걸 재생할 지 결정할 수 있다
profile
안녕하세요 삽질하는걸 좋아하는 4년차 안드로이드 개발자입니다.

0개의 댓글