Android ExoPlayer 동영상 플레이어 구현하기 (With Compose) (3)

pass·2024년 6월 27일
0

Android

목록 보기
40/41

🔥 ExoPlayerCompose를 활용하여 전체 화면 플레이어 구현하기

이전 글에서 기본적인 동영상 플레이어와 미니 플레이어로 전환하는 것까지 구현하였다.
이번 글에서는 동영상 플레이어에서 전체 화면 버튼을 클릭했을 경우, 전체 가로 화면으로 전환되면서 동영상을 스트리밍할 수 있도록 구현해보는 과정을 정리해보려고 한다.

이전 글 : https://velog.io/@pass/Android-ExoPlayer-%EB%8F%99%EC%98%81%EC%83%81-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-With-Compose-2


🌟 결과

전체 플레이어 구현의 최종 결과는 다음과 같다.


🌈 구현 과정

✓ 화면 전환 원리

Android는 화면 전환을 하게 되면 Activity가 소멸되고 다시 생성되는 과정을 거친다.
이 때 데이터도 함께 사라지게 되는데, 이를 해결하기 위해 onSaveInstanceState를 사용하거나 ViewModel을 사용할 수 있다.
이 프로젝트에서는 MVI를 설계하기 위해 AAC ViewModel을 사용하였기 때문에 ViewModel에 상태 정보로 동영상의 재생 위치를 저장하도록 하였다.


✓ 전체 화면 버튼 활성화 (ExoPlayerView)

    // Exoplayer with AndroidView
    AndroidView(
        factory = { ctx ->
            PlayerView(ctx).apply {
                player = exoPlayer
                resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
                setFullscreenButtonClickListener {
                    if (currentIsChange.value) {
                        // 변경 가능한 설정일 때, 현재 시점 정보와 함께 전체 화면으로 전환
                        onChangeIsFullScreen((player as ExoPlayer).currentPosition)
                    }
                }
            }
        },
        modifier = modifier
    )
  • PlayerView 에서 setFullscreenButtonClickListener를 활성화한다.
  • currentPosition 으로 현재 재생되고 있는 위치를 조회하여 저장한다.

✓ 동영상 재생 위치 백업 (ExoPlayerView)

동영상 전체 화면 버튼을 클릭하여 화면 전환을 할 경우에는 위 코드로 해당 시점이 기록됨과 함께 조회할 수 있다.
하지만, Android 에는 '뒤로 가기'버튼이 존재한다!
전체 화면으로 전환 후 사용자가 '뒤로 가기'버튼을 통해 원래 화면으로 돌아갈 경우에는 클릭 시점의 재생 위치가 기록되지 않는다. 즉, 가장 최근에 저장된 상태로 돌아간다.

이를 조금이나마 해결하기 위해 사용자가 SeekBar나 빨리 감기 등 임의로 재생 위치를 조작하였을 경우 백업을 할 수 있도록 코드를 추가하였다.

    exoPlayer.addListener(object : Player.Listener {
        override fun onEvents(player: Player, events: Player.Events) {
            if (events.contains(Player.EVENT_POSITION_DISCONTINUITY) || events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
                updatePlaybackPosition?.invoke(player.currentPosition)
            }
        }
    })

✓ 동영상 재생 시점 적용 (ExoPlayerView)

화면 전환이 완료되면 재생 시점을 저장해둔 상태로 변경해야 한다.
아래와 같이 LaunchEffect에서 MediaSource를 새로 생성할 때마다 동영상 시점을 적용할 수 있도록 구성하였다.

LaunchedEffect(mediaSource) {
	// ...
    exoPlayer.seekTo(playBackPosition)
}

✓ 화면별 구성

이제 전체화면과 기본 화면일 경우를 나누어서 화면을 구성해야한다.
아래에는 전체 화면일 때의 코드와 기본 화면일 때 추가해야 할 코드를 작성하였다.
(구현 안된 변수와 함수들은 전체 코드를 참조)

		if (isFullScreenState) {
            // 전체 화면으로 전환
            (context as Activity).requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

            // 상태 바 숨기기
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                context.window.setDecorFitsSystemWindows(false)
                context.window.insetsController?.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
                context.window.insetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
            } else {
                context.window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_FULLSCREEN
                        or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
            }

            // 동영상 플레이어
            ExoPlayerView(
                context = context,
                videoUri = videoUrl,
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Black),
                isChange = !isMinimized,
                playBackPosition = playBackPosition,
                onChangeIsFullScreen = onChangeIsFullScreen,
                updatePlaybackPosition = updatePlaybackPosition
            )
        } else {
            // 전체 화면 해제
            (context as Activity).requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED

            // 상태 바 보이기
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                context.window.insetsController?.show(WindowInsets.Type.statusBars())
                context.window.setDecorFitsSystemWindows(true)
            } else {
                context.window.decorView.systemUiVisibility = View.SYSTEM_UI_LAYOUT_FLAGS
            }

			// ...
		}

전체 화면 상태일 경우

  1. 화면을 회전하여 전체 화면으로 전환한다.
  2. 상태 바와 바텀 바를 숨겨 동영상만 보이도록 적용한다. (API 버전 나누어 코드 설정)
  3. 새로운 상태와 함께 동영상 플레이어를 표시한다.

기본 화면 상태일 경우

  1. 화면을 회전하여 원래 화면으로 전환한다.
  2. 상태 바와 바텀 바가 보이도록 적용한다. (API 버전 나누어 코드 설정)
  3. 새로운 상태와 함께 원래 UI를 표시한다.

✓ 뒤로 가기 클릭 시 화면 전환 적용

기존 뒤로가기 이벤트에 추가로 전체 플레이어 -> 기본 플레이어로 전환하는 로직을 추가한다.

    // 뒤로가기 클릭 이벤트
    BackHandler(enabled = !videoStreamingState.isMinimized) {
        if (videoStreamingState.isFullScreen) {
            // 전체 플레이어 일 때, 기본 플레이어로 전환
            viewModel.processIntent(VideoStreamingIntent.OnChangeIsFullScreen(null))
        } else {
            // 기본 플레이어 일 때, 미니 플레이어로 전환
            viewModel.processIntent(VideoStreamingIntent.OnChangeMiniPlayer)
        }
    }

💬 전체 코드

전체 코드는 길고 복잡할 수 있어 현재 글에는 작성하지 않았고, github 주소를 첨부하려고 한다.
실제 코드에서는 Clean Architecture + ViewModel + MVI + Orbit 을 사용하였기 때문에 위의 코드와는 조금씩 다를 수 있다.

  • MainScreen : 기본 화면으로 VideoStreamingPlayer 컴포넌트를 화면의 구성요소로 표시
    (MainScreen 위에서 기본, 미니, 전체 화면 플레이어로 전환)

  • MainScreen 과 VideoStreamingPlayer 는 별도의 ViewModel, State를 가짐

⚡️ Github

VideoStreamingPlayer.kt
VideoStreamingViewModel.kt
VideoStreamingIntent.kt
VideoStreamingState.kt
MainScreen.kt
MainViewModel
MainIntent

profile
안드로이드 개발자 지망생

0개의 댓글