본문 바로가기
안드로이드 프로젝트/유튜브 음정 조절 어플리케이션

[Android] #12 mediaSession 공부 및 코드 리팩토링 - 1

by joh9911 2023. 6. 29.

 

비디오를 재생하고 관리하는 부분에 있어서, 제 코드는 엉망진창 그 자체입니다.

 

지금도 어떤 원리로 제 코드가 돌아가는지 모르겠습니다.. 그냥 작동은 잘 되네요.

 

 

 

Service 코드를 리팩토링 하던 중, 다음과 같은 사실을 발견할 수 있었습니다.

 

동영상 재생 중
동영상 재생 중

 

 

휴대폰 내 미디어 출력
휴대폰 내 미디어 출력

 

분명 음악을 재생 중이며 Notification까지 뜨지만,

 

기기 내 미디어 출력에서는 현재 재생 중인 곡이 뜨지 않는다는 것을 알게 되었습니다. 다른 앱은 잘 떠요.

 

 

다른 앱 잘됨
다른 앱 잘됨

 

공식 문서에 미디어 플레이어 앱의 흐름에 관련한 문서가 있는데,

 

공부해 보면서 코드를 리팩토링 해보려고 합니다.

 

 

 

이전까지의 코드 구조

 

억지 구현이다 보니 구조라고 말하기도 좀 그러네요..

 

<VideoService>

override fun onCreate() {
    super.onCreate()
    // 미디어 세션 초기화
    mediaSession = MediaSessionCompat(this, "PlayerService")
    
    // 오디오 포커스 주기
    setAudioFocus()
    
    // 이어폰 연결이 해제될 때 오디오 꺼지게 하기 위한 브로드캐스트 리시버 등록
    registerReceiver(mediaReceiver, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY))
    
    // exoPlayer 초기화
    val trackSelector = DefaultTrackSelector(this).apply {
        setParameters(buildUponParameters().setMaxVideoSizeSd())
    }
    exoPlayer = ExoPlayer.Builder(this)
        .setTrackSelector(trackSelector)
        .setSeekForwardIncrementMs(10000)
        .setSeekBackIncrementMs(10000)
        .build()
        
    // exoPlayer 상태에 따른 콜백 리스터 등록
    exoPlayer.addListener(object: Player.Listener{
        override fun onPlaybackStateChanged(playbackState: Int) {
            when (playbackState){
                Player.STATE_READY -> {
                    if (exoPlayer.isPlaying){
                        requestAudioFocus()
                    }
                    playerServiceListener?.onStateReady()
                    startNotification()
                }
                Player.STATE_ENDED -> {
                    if (exoPlayer.mediaItemCount == 0) 
                        return

                    playerServiceListener?.onStateEnded()
                    startNotification()
                }
                Player.STATE_BUFFERING ->{
                }
                Player.STATE_IDLE -> {
                }
            }
        }
    })
}

 

미디어 재생에 관한 부분들만 옮겨봤습니다.

 

onCreate에서 미디어 세션을 초기화해 주며, exoPlayer를 빌드합니다.

 

중간에 보면 exoPlayer의 playback 상태에 따라 startNotification() 매서드가 실행이 되는 것을 볼 수 있습니다.

 

해당 메서드 내에서, mediaSession의 상태를 수정해 주며 notification을 띄워줍니다.

 

 

코드를 처음 작성했을 때, 저는 미디어 세션이 무슨 역할을 하는지 몰랐습니다.

 

단지 notification의 재생바를 구현하려는 과정에서 사용하게 되었습니다.

 

// notification 에 재생바 표시

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(
            if (exoPlayer.isPlaying) PlaybackStateCompat.STATE_PLAYING
            else PlaybackStateCompat.STATE_PAUSED,
            exoPlayer.currentPosition,
            1f,
            SystemClock.elapsedRealtime()
        )
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO
        )
        .build()
)

 

기존 앱의 재생 흐름입니다.

 

1. 서비스의 onCreate에서 ExoPlayer 빌드.

 

2. Activity와 서비스 바인드 후, Activity의 exoPlayer 객체와 서비스의 exoPlayer 객체를 연결.

 

3. 동영상 재생 프레그먼트가 실행될 때, activity의 exoPlayer 객체와 프레그먼트의 playerView를 연결.

 

4. 동영상 재생 프레그먼트의 ui를 통해 서비스의 exoPlayer로 정보 전달 및 재생 제어.

 

 

고쳐야 할 부분

 

1. 서비스

 

누군가 왜 서비스를 사용하고 있냐고 물어본다면, 저는 대답할 수 없습니다.

 

그냥 백그라운드 작업은 다 서비스로 해야하는 줄 알아서 했습니다.

 

 

공식 문서에서는, 오디오 앱에서는 플레이어와 미디어 세션이 MediaBrowserService 내에서 구현되는 것을 권장한다고 합니다.

 

위에 올렸던 미디어 출력도, MediaBrowserService를 구현해야만 제어할 수 있는 것이었습니다.

 

해당 문서에 따라서 코드를 리팩토링 할 것입니다.

 

 

2. 미디어 세션 관련 초기화 방식

 

미디어 세션 관련 1
미디어 세션 관련 2

 

저는 onCreate에서 mediaSession 하나만 초기화를 해줬으며,

 

PlayBackStateCompat과 MediaMetadataCompat 인스턴스는 notification을 만들 때마다 빌드하여 넣어주고 있었습니다.

 

callBack 또한 그때마다 넣어주고 있었네요.

 

// createNotificaion() 매서드 내 코드


val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()
    .setShowActionsInCompactView(0,2,4)
    .setMediaSession(mediaSession.sessionToken)

    val metadataBuilder = MediaMetadataCompat.Builder().apply {
        putString(MediaMetadata.METADATA_KEY_TITLE, currentVideoData?.title)
        putString(MediaMetadata.METADATA_KEY_ARTIST, currentVideoData?.channelTitle)
        putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, currentVideoData?.thumbnail)
        putString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI, currentVideoData?.thumbnail)
        putLong(MediaMetadata.METADATA_KEY_DURATION,exoPlayer.duration)
    }
    mediaSession.setMetadata(metadataBuilder.build())

mediaSession.setPlaybackState(
    PlaybackStateCompat.Builder()
        .setState(
            if (exoPlayer.isPlaying) PlaybackStateCompat.STATE_PLAYING
            else PlaybackStateCompat.STATE_PAUSED,
            exoPlayer.currentPosition,
            1f,
            SystemClock.elapsedRealtime()
        )
        .setActions(PlaybackStateCompat.ACTION_SEEK_TO
        )
        .build()
)
mediaSession.setCallback(object: MediaSessionCompat.Callback(){
	/..
}

 

ExoPlayer의 재생 상태가 바뀔 때마다 createNotificaion() 매서드가 실행이 되기 때문에,

 

그때마다 빌드가 되었을 겁니다. 

 

MediaBrowserService를 구현할 때, 그대로 코드를 복붙하지 않고

 

흐름을 이해하며 코드를 수정해가며 구현해 볼 것입니다.

댓글