이번에 회사에서 VOD 서비스를 준비하고 있기에 안드로이드 VOD개발을 맡게 되었다.

사실 이전부터 exoplayer를 사용해서 개발을 하고 싶었지만 그래도 요구사항에 맞는지 확인하기 위해 요구사항과 안드로이드에서 사용할 수 있는 라이브러리를 정리 했고 결과적으로 exoplayer를 사용하기로 했다.

일단 회사에서 요구사항을 정리하면

  1. AWS 스트리밍 서비스를 이용할 것
  2. HLS로 스트리밍 할 것
  3. 자막이 사용 가능 할 것
  4. SignedCookie를 이용할 것

크게 4가지로 나눠졌고 나머지 기술적인 부분은 포지션별로 결정하기로 했다.

사실 미디어 타입의 경우 DASH를 생각했지만 ios에서 10분 이상 플레이 할 경우 HLS를 사용해야하기 때문에 HLS를 적용했다. 물론 둘다 적용하는게 제일 좋긴하지만 이건 페이즈2로 미루기로 했다.

사실 전체적인 부분이나 설정은 exoplayer 가이드를 따라서 만들어도 충분하지만 몇가지 프로젝트에 맞게 수정되어야 하는부분들이 존재했다.

우선 뷰 부분이 조금 커스텀이 필요했다. 기본 뷰도 괜찮지만 우린 좀 더 유튜브의 레이아웃을 따라 만들기를 원했고 그렇게 하기 위해서 Exoplayer에 있는 StyledPlayerView와 controlView를 상속 받는 커스텀 뷰를 만들어 각각 원하는 이미지, 원하는 제스쳐 그리고 원하는 애니메이션을 적용하였다.

class MacaStyledPlayerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    @AttrRes defStyleAttr: Int = 0
) : StyledPlayerView(context, attrs, defStyleAttr) {
// custom Logic....
}

class MacaStyledPlayerControlView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    @AttrRes defStyleAttr: Int = 0
) : StyledPlayerControlView(context, attrs, defStyleAttr) {
// custom Logic....
}

그리고 커스텀 된 뷰를 xml 레이아웃에서 호출해서 사용하면 된다. 여기서 모두 동일한 커스텀 레이아웃을 보여주기 때문에 캡슐화를 위해 따로 xml 파일에 만들고 해당 파일을 include해서 불러와 사용했다.

이제 뷰는 만들어졌고 실제적으로 플레이어를 작동하고 seek 기능을 하는 로직은 관심사 분리를 위해 Util로 따로 만들었다.

object PlayerUtil {
    fun createMediaSource(
        context: Context,
        url: String? = null,
        cookieResolver: VodSignedCookieResolver? = null,
        tracking: String? = null,
        onRetry: ((Exception) -> Unit)? = null
    ): MediaSource {
			val httpDataSourceFactory = DefaultHttpDataSource.Factory()
                .setUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0")
                .setAllowCrossProtocolRedirects(true)
            val resolving = ResolvingDataSource.Factory(httpDataSourceFactory) { spec ->
                cookieResolver.resolve(spec, tracking)
            }
            val mediaItem = MediaItem.fromUri(Uri.parse(""))
            HlsMediaSource.Factory(resolving)
                .setLoadErrorHandlingPolicy(VodCookieErrorHandlingPolicy(onRetry))
                .createMediaSource(mediaItem)
		}
		// some other logics
}

여기서 다시 요구사항을 생각하면 HLS를 이용해야 하는걸 기억 할 것이다. 그래서 MediaSource를 Exoplayer에서 제공하는 HLSMediaSource로 가져오고 뒤에 나올 signedCookie를 Factory로 넘겨 MidiaSource를 url에서 불러오도록 한다.

우선 signedCookie를 설명하기 전에 서버와의 소통에 대해 설명이 있어야한다.

클라이언트에서 보고자 하는 vod의 id를 파라미터로 쿠키 요청을 보내고 서버는 해당 id에 맞는 .m3u8이 담긴 url과 cloudFront에서 제공해주는 cookie값을 준다. 그럼 해당 m3u8을 플레이 해주고 플레이에 따라 다음 url을 계속 호출하는 방식으로 소통을 한다.

그럼 여기서 쿠키는 지속적으로 url을 요청하면서 쿠키를 체크하고 만약 쿠키가 만료가 되면 에러로 떨어져서 예외 처리된 부분으로 받아 다시 쿠키를 발급받아 지속적으로 재생을 할 수 있도록 해야 한다.

그래서 쿠키를 발급받고 재호출하는 방식을 따로 클래스로 만들어서 자동으로 계속 순환 할 수 있도록 개발을 했다.