일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- exoplayer cache
- FastAPI
- ChatGPT
- kotlin collection
- ListAdapter DiffUtil
- kotlin list
- map
- android ktor
- ktor api call
- list map
- 스피너
- AWS EC2
- 유튜브
- Python
- Zsh
- llm
- ListAdapter
- doc2vec
- build with ai
- ktor client
- DiffUtil.ItemCallback
- video caching
- android
- getChangePayload
- ExoPlayer
- 안드로이드
- android custom view
- android exoplayer
- 시행착오
- 유튜브 요약
- Today
- Total
버튼 수집상
[안드로이드] ExoPlayer 비디오 캐싱하기 본문
배경
무한재생되는 30초 내외 분량의 비디오에서 트래픽이 너무 쌓인다고 캐싱이 제대로 되고 있는지 확인 요청이 들어왔다.
ExoPlayer는 캐싱 처리를 따로 해줘야 하는데, 기존 코드에서 설정하고 있지 않았다.
확인하기
디버거를 연결한 다음 안드로이드 스튜디오 하단의 App Inspection > Network Inspector 를 켰다.
최초의 요청 이후로도 비디오가 반복되며 재생될 때마다 통신이 오고감을 알 수 있다.
빨간 박스가 그려진 데이터 사이즈는 영상에 따라 달랐다.
기존 코드는 아래와 같다.
// player 초기화
private fun initPlayer() {
// 싱글턴 오브젝트
ExoPlayerInfo.instance.apply {
player = SimpleExoPlayer.Builder(this@MainActivity).build()
}
}
// api에서 받아오는 비디오 링크
val playUrl : String?
// player 상태값 리스너
val playbackStateListener = object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) { /*...*/ }
override fun onPlayerError(error: PlaybackException) { /*...*/ }
}
// 뷰에 player 설정
private fun preparePlayer() {
with(ExoPlayerInfo.instance) {
player?.let {
binding.playerView.player = it // 싱글턴 오브젝트 player를 뷰에 할당
it.volume = .0f
it.setMediaItem(MediaItem.fromUri(playUrl ?: "")) // MediaItem 설정
it.playWhenReady = true
it.repeatMode = Player.REPEAT_MODE_ONE
it.addListener(playbackStateListener)
it.prepare()
}
}
}
샘플 찾아보기
MediaItem은 ExoPlayer 코드 내부에서 MediaSource로 바뀐다.
공식문서에 cache를 검색하니 CacheDataSource.Factory 라는 클래스로 MediaSource를 초기화하라고 나왔다.
샘플소스를 찾아보는데 공통적으로 등장하는 몇 몇 클래스를 찾을 수가 없었다.
일단 라이브러리 버전을 올렸다.
2.12.0 -> 2.16.0 (최신버전은 2.19.0)
implementation 'com.google.android.exoplayer:exoplayer-core:2.16.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.16.0'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.16.0'
그리고 기존에 사용하던 SimpleExoPlayer가 deprecated되어 ExoPlayer로 교체했다.
소스적용
우선 SimpleCache 객체를 초기화한다.
val exoCacheDir = File("${BaseApp.context.cacheDir}/exo")
val evictor = LeastRecentlyUsedCacheEvictor(500 * 1024 * 1024)
val cache = SimpleCache(exoCacheDir, evictor, StandaloneDatabaseProvider(this@MainActivity))
캐시 디렉토리는 ApplicationContext에서 가져온다.
캐시 사이즈는 500MB로 잡았는데 사실 기준을 잘 모르겠다.
chatGPT에 물어보니 bitRate 2Mbps 30초 내외 길이 동영상이면 7에서 7.5MB 정도면 된다는데 확실하지 않다.
val httpDataSourceFactory = DefaultHttpDataSource.Factory().apply {
// request header 등 설정
setUserAgent(CommonConst.extraUserAgent)
}
val upstreamDataSourceFactory = DefaultDataSource.Factory(this@MainActivity, httpDataSourceFactory)
DefaultHttpDataSource.Factory에서는 http 통신에 필요한 설정을 넣는다.
이제 CacheDataSource.Factory를 초기화한다.
val cacheFactory = CacheDataSource.Factory()
.setCache(cache)
.setCacheWriteDataSinkFactory(CacheDataSink.Factory().setCache(cache))
.setCacheReadDataSourceFactory(FileDataSource.Factory())
.setUpstreamDataSourceFactory(upstreamDataSourceFactory)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
.setEventListener(object: CacheDataSource.EventListener {
override fun onCachedBytesRead(cacheSizeBytes: Long, cachedBytesRead: Long) {
Log.v("exoPlayer", "onCachedBytesRead. cacheSizeBytes:$cacheSizeBytes, cachedBytesRead:$cachedBytesRead")
}
override fun onCacheIgnored(reason: Int) {
Log.v("exoPlayer", "onCacheIgnored. reason:$reason")
}
})
ExoPlayerInfo.instance.apply {
// player = SimpleExoPlayer.Builder(this@MainActivity).build()
player = ExoPlayer.Builder(this@MainActivity).build()
cacheDataSourceFactory = cacheFactory // 싱글턴 오브젝트에 필드 추가
}
이제 url을 받아오고 CacheSourceDataFactory를 이용해서 MediaSource로 만들어주겠다.
private fun preparePlayer() {
val mediaItem = MediaItem.fromUri(playUrl ?: "")
val mediaSource by lazy {
val dataSourceFactory = ExoPlayerInfo.instance.cacheDataSourceFactory // 싱글턴 오브젝트 필드
ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(mediaItem)
}
with(ExoPlayerInfo.instance) {
player?.let {
binding.playerView.player = it
it.volume = .0f
// it.setMediaItem(MediaItem.fromUri(playUrl ?: ""))
it.setMediaSource(mediaSource, true)
it.playWhenReady = true
it.repeatMode = Player.REPEAT_MODE_ONE
it.addListener(playbackStateListener)
it.prepare()
}
}
}
위 코드에서 ProgressiveMediaSource 대신 HlsMediaSource를 사용했었는데,
Input does not start with the #EXTM3U header 라는 에러가 계속 났었다.
https://github.com/google/ExoPlayer/issues/5424
HLS Streaming - Input does not start with the #EXTM3U header · Issue #5424 · google/ExoPlayer
Seached Issues - #4141 , #4150 ,#4066 I am using HLS encrypted Stream for exoplayer. URL - https://mediadev.camtrack.com/dev/temp/bunny.m3u8?Expires=2072197799&Signature=PqNjmCoIRo~lSurGLVtY6FLBNGQ...
github.com
ProgressiveMediaSource로 바꾸면 위와 같은 에러가 나지 않는다.
다시 확인하기
CacheDataSource.Factory 를 설정하니 더이상 반복재생될때마다 통신을 주고받지 않았다.
어떤 사이드이펙트가 있는지, 어떤 부분을 누락했는지 놓친 게 있다면 후속글로 다루겠다.
참고
Pre-caching adaptive video stream in a playlist with ExoPlayer API
Recently, I have been working on improving video playlist performance for my company Android project. The playlist is a video list that can…
medium.com
https://exoplayer.dev/customization.html
Customization - ExoPlayer
exoplayer.dev
https://tourspace.tistory.com/527
[ExoPlayer] HTTP GET, POST, Cache 및 JSON body 적용
ExoPlayer는 다양한 방법으로 재생을 지원합니다. 단말에 저장하고 있는 동영상 파일이라던가, 앱의 resource로 가지고 있는 파일, 서버에 존재하여 uri로 (http로 다운받아) 재생하기, 게다가 DRM 걸린
tourspace.tistory.com
https://proandroiddev.com/lets-dive-into-exo-player-part-iv-caching-video-7ac2dc430dbf
Lets Dive into Exo-Player (Part IV): Caching Video
Streaming is the continuous transmission of audio or video files from a server to a client (the process that makes watching videos online…
proandroiddev.com
'TIL - 안드로이드' 카테고리의 다른 글
[안드로이드] 상용 앱을 운영하면서 느낀 점 (0) | 2023.08.18 |
---|---|
[안드로이드] 커스텀 뷰 Custom View 만드는 법 (0) | 2023.08.11 |
[안드로이드] 그림자에 색깔 있는 카드뷰 만들기-1 Custom CardView with shadowColor attribute (0) | 2023.08.01 |
[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 3 (0) | 2023.07.28 |
[안드로이드] 중복체크 되는 리스트 Preference 저장하고 테스트하기 (0) | 2023.07.20 |