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

pass·2024년 6월 17일
0

Android

목록 보기
39/41
post-thumbnail

🔥 ExoPlayerCompose를 활용하여 미니 플레이어 구현하기

동영상 스트리밍 서비스 기능을 포함한 사이드 프로젝트를 구현하면서 미니 플레이어를 구현해보았다.
이번 글에서는 Compose의 큰 장점 중 하나인 애니메이션을 활용하여 미니 플레이어를 구현해본 과정을 정리해보려고 한다.

이전 글에서 작성한 ExoPlayerView를 그대로 사용하여 동영상 플레이어를 구현하였다.
이전 글 : 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-1-With-Compose


🌟 결과

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


🌈 구현 과정

✓ 애니메이션 과정

  1. MainActivity -> MainScreen 위에서 동영상 목록 확인 후 클릭
  2. MainScreen 위에 VideoStreamingPlayer 보이게 전환 -> 비디오 전체 화면 등장
  3. 비디오 전체 화면이 줄어들면서 미니 플레이어로 전환
  4. 'X' 클릭 시 VideoStreamingPlayer 안보이게 전환

✓ 애니메이션 원리

전체 화면 구성을 보면 동영상 플레이어와 아래 내용(제목, 프로필 등)으로 나눌 수 있다.
미니플레이어로 전환 시 실현될 애니메이션은 다음과 같다.

동영상 플레이어

  • 크기 : 가득 찬 가로 길이에서 1/3 가로 길이로 변경
  • 위치 : 현재 위치에서 Bottom Bar 위로 이동

내용

  • 전체 화면 : 전체 화면에서의 내용은 사라짐
  • 미니 플레이어 : 미니 플레이어에서의 내용이 나타남

중요한 점은 동영상 플레이어는 줄어들면서 이동하고, 내용물은 사라지고 나타나게 해야 한다는 점이다.


✓ 애니메이션 정의

    val transition = updateTransition(targetState = isMinimized, label = "Transition")

    // 전체 스크린 dp 정의
    val fullScreenWidthDp = LocalConfiguration.current.screenWidthDp.dp
    val fullScreenHeightDp = LocalConfiguration.current.screenHeightDp.dp

    // 비디오 플레이어의 Y 좌표 정의 - paddingValues : MainScreen의 Scaffold paddingValues
    val videoPlayerOffsetY by transition.animateDp(
        transitionSpec = { spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow) },
        label = "OffsetY"
    ) { state -> if (state) fullScreenHeightDp - paddingValues.calculateBottomPadding() - paddingValues.calculateTopPadding() else 0.dp }

    // 비디오 플레이어의 크기 정의
    val videoPlayerScale by transition.animateFloat(
        transitionSpec = { spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow) },
        label = "Scale"
    ) { state -> if (state) 0.3f else 1f }

✓ 상태 정의

data class Video(
    val isMinimized: Boolean,
    val videoTitle: String,
    val videoUrl: String,
	val userProfileURL: String,
    val userName: String,
)

@Composable
fun VideoStreamingPlayer(
    video: Video,
    paddingValues: PaddingValues,
    onCloseVideoPlayer: () -> Unit
) {
    val context = LocalContext.current
    var isMinimized by remember { mutableStateOf(video.isMinimized) }
    var videoTitle by remember { mutableStateOf(video.videoTitle) }
    var videoUrl by remember { mutableStateOf(video.videoUrl) }
    var userProfileURL by remember { mutableStateOf(video.userProfileURL) }
    var userName by remember { mutableStateOf(video.userName) }

    // 비디오 상태 업데이트 (초기)
    LaunchedEffect(Unit) {
        videoUrl.value = video
    }
}

✓ 화면 정의 + 애니메이션 설정

  1. zIndex : MainScreen보다 우선하여 화면에 보이도록 설정
  2. offSet : 미니 플레이어 애니메이션 전환 시 정의해둔 Y 좌표로 이동하도록 설정
  3. pointerInput : 수직 드래그 제스처를 감지하여 이벤트 적용 (아래 : 미니 플레이어로 전환, 위 : 기본 플레이어로 전환)
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .zIndex(if (!isMinimized) 1f else 0f)
            .background(MaterialTheme.colorScheme.background)
            .offset { IntOffset(0, videoPlayerOffsetY.roundToPx()) }
            .pointerInput(Unit) {
                detectVerticalDragGestures { _, dragAmount ->
                    if (dragAmount > 0) {
                        onChangeMiniPlayer()
                    } else if (dragAmount < 0) {
                        onChangeDefaultPlayer()
                    }
                }
            }
            .clickable { if (isMinimized) onChangeDefaultPlayer() }
    ) {
		// 동영상 플레이어
        ExoPlayerView(
            context = context,
            videoUri = videoUrl,
            modifier = Modifier
                .fillMaxWidth(videoPlayerScale)
                .height(fullScreenWidthDp * videoPlayerScale * 0.66f)
                .background(Color.Black)
                .zIndex(1f)
        )

        if (!isMinimized) {
			// 전체화면일 때, 기본 플레이어 내용 정의
        } else {
            // 미니 플레이어일 때, 내용 정의 'X' 버튼 포함
        }
    }

✓ Player 사용 Screen

val showPlayerState by remember { mutableStateOf<Video?>(null) }

Scaffold(
    topBar = { ... },
    bottomBar = { ... }
) { paddingValues ->
    ...
    if (showPlayerState != null) {
        VideoStreamingPlayer(
            video = showPlayerState,
            paddingValues = paddingValues,
            onCloseVideoPlayer = onCloseVideoPlayer
        )
    }
}

그 밖의 필요한 이벤트

  1. 상황에 맞는 상태 변화 이벤트 (추가 구현 필요)
  2. 뒤로 가기 클릭 시 전체 플레이어일 경우, 미니 플레이어로 전환하도록 설정
    // 뒤로가기 클릭 이벤트
    BackHandler(enabled = !isMinimized.value) {
        // 기본 플레이어 일 때, 미니 플레이어로 전환
        viewModel.processIntent(VideoStreamingIntent.OnChangeMiniPlayer)
        isMinized.value = true
    }

💬 전체 코드

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

VideoStreamingPlayer
https://github.com/JungWooGeon/Education/blob/main/presentation/src/main/java/com/pass/presentation/view/component/VideoStreamingPlayer.kt

MainScreen
https://github.com/JungWooGeon/Education/blob/main/presentation/src/main/java/com/pass/presentation/view/screen/MainScreen.kt

Repository
https://github.com/JungWooGeon/Education

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

0개의 댓글