$30 off During Our Annual Pro Sale. View Details »

Jetpack Media3로 좋은 콘텐츠 소비 경험 구현하기

workspace
September 12, 2023

Jetpack Media3로 좋은 콘텐츠 소비 경험 구현하기

이 발표는 안드로이드 미디어 경험 구현을 위한 새로운 라이브러리 컬렉션인 Media3를 다룹니다. ExoPlayer와 같은 Android 지원 라이브러리가 Media3로 이관되었는데 Media3는 어떤 의도를 가지고 개발되었는지, 어떻게 개발자의 어려움을 해결하며 어플리케이션에서 활용될 수 있는지 살펴봅니다.

* 발표에서 생략했던 '스트리밍 개요' 섹션이 포함되어있습니다.
* 맨 뒤 참고자료 링크는 다운로드 후 pdf에서 하이퍼링크를 누르시면 동작합니다.

workspace

September 12, 2023
Tweet

More Decks by workspace

Other Decks in Programming

Transcript

  1. Jetpack Media3۽


    જ਷ ௑బஎ ࣗ࠺ ҃೷ ҳഅೞӝ
    ܨӝ޹ (workspace)

    View Slide

  2. ઱ઁܳ ࢶ੿ೠ ੉ਬ

    View Slide

  3. য়ט ೣԋ ঌইࠅ Ѫ਷?
    ❏ झ౟ܻ߁ ѐۿ


    ❏ ExoPlayer (feat. v1ࠗఠ ॵ ࢎۈ੄ ҃೷)


    ❏ Media3 🎉

    View Slide

  4. View Slide

  5. झ౟ܻ߁ ѐۿ

    View Slide

  6. ই઱ ݢ ঢ়զ…
    ❏ ஹೊఠ۽ ৔࢚ ࠁӝ द੘ೠ ୐ ӝর
    ਷ 2000֙ ઺റ߈ࠗఠ


    ❏ Ӓ۞ա ೦࢚ ޙઁܳ ѻѱ غחؘ…

    View Slide

  7. ׼द੄ झ౟ܻ߁
    ❏ ૑Әࠁ׮ ࢚؀੸ਵ۽ ࠗ઒ೠ ੋ೐ۄ


    ❏ झ౟ܻ߁ਊ ೐۽ష௒x, ౵ੌਸ ా૩۽ ׮਍۽٘ ߉
    ই ੤ࢤ.


    ❏ (ૐѢ) ਋௿ܼ റ ׮਍۽٘ ؽ or 1%ۄب উ߉ই
    ૑ݶ ੤ࢤ উغѢա ӵ૗


    ❏ ӒѪਸ nݺ੉…

    View Slide

  8. ݽ߄ੌ द੢җ ೣԋ झ౟ܻ߁ ࣻਃ 📈

    View Slide

  9. 2012-2014 োр ݽ߄ੌ ӝӝ / ࢎਊ੗ ࣻ
    0
    0.45
    0.9
    1.35
    1.8
    2012 2013 2014
    క࠶݁ ୹ೞ۝ झ݃౟ಪ ୹ೞ۝ झ݃౟ಪ ࢎਊ੗ ࣻ
    ୹୊: Gartner (Mar 2014), IDC (Jan 2014), eMarketer (Dec 2013)

    View Slide

  10. 2012-2014 োр ݽ߄ੌ ӝӝ / ࢎਊ੗ ࣻ
    0
    0.45
    0.9
    1.35
    1.8
    2012 2013 2014
    క࠶݁ ୹ೞ۝ झ݃౟ಪ ୹ೞ۝ झ݃౟ಪ ࢎਊ੗ ࣻ
    ୹୊: Gartner (Mar 2014), IDC (Jan 2014), eMarketer (Dec 2013)
    झ౟ܻ߁ਸ ਤೠ Ҵઁ ಴ળ ӝࣿ੄ ١੢

    View Slide

  11. DASH
    ❏ Dynamic Adaptive Streaming over HTTP


    ❏ ׮নೠ ೧࢚ب৬ ࠺౟ۨ੉౟۽ video streamਸ ੋ௏٬


    ❏ ૑োਸ ୭ࣗചೞҊ, উ੿੸ੋ Ҋಿ૕ द୒ ജ҃ਸ ઁҕ


    ❏ ޷٣যী ؀ೠ ݺࣁ - Dash Manifest(mpd)


    ❏ ఋ ೐۽ష௒ : HLS(Apple), Smooth Streaming(Microsoft)

    View Slide

  12. DASH - ૑ো হח द੘
    1080p
    360p
    720p
    144p
    audio
    👍

    View Slide

  13. DASH - উ੿੸ੋ झ౟ܻ߁
    1080p
    360p
    720p
    144p
    audio
    🚇

    View Slide

  14. DASH - ؊ࡂ਷ যڌѱ ઁҕؼө?
    1080p
    360p
    720p
    144p
    audio🇰🇷
    audio🇺🇸

    View Slide

  15. DASH - XXX Music਷ যڌѱ?
    1080p
    360p
    720p
    144p
    audio

    View Slide

  16. ExoPlayer੄ ١੢

    View Slide

  17. ExoPlayer
    ❏ উ٘۽੉٘ܳ ਤೠ ޷٣য ੤ࢤ ۄ੉࠳۞ܻ


    ❏ 2014 Google I/Oীࢲ ١੢


    ❏ DASH, Smooth Streaming١ ࢜۽਍ ಴ળ ૑ਗ


    ❏ frameworkױ੄ ۽૒ਸ applicationױীࢲ ࣻ
    ੿ೡ ࣻ ੓ب۾ ೞৈ ਬোೞѱ ഛ੢
    🔗 https://youtu.be/92fgcUNCHic?t=1108

    View Slide

  18. ExoPlayerо ઁҕೞח Ѫ
    Extension


    cast, media session, media2 (androidx)


    cronet, okhttp, rtmp (data source)


    AV1, VP9, FLAC, Opus (decoder)


    FFMpeg (renderer)
    Library


    core


    dash, hls, smoothstreaming


    ui

    View Slide

  19. View Slide

  20. աীѱ ExoPlayerۆ?


    ❏ ੉गо ցޖ ݆ও਺😇


    ❏ ୊਺ਵ۽ ֤੄ী ଵৈ೧ࠄ য়೑ࣗझ


    ❏ ׮਍۽٘ ࣘب ೱ࢚, DRM ੤ࢤ ߡߢ
    ੐, ߓࣘ ١ ޙઁ ೧Ѿী р੽ ӝৈ


    ❏ ࣘبח וܻ૑݅ Բળ൤ ޙઁ ೧Ѿ👍

    View Slide

  21. Media3, why?

    View Slide

  22. рױೠ ೒ۨ੉য ҳഅ
    Player
    UI
    commands state

    View Slide

  23. рױೠ ೒ۨ੉য ҳഅ
    Player
    UI
    commands state

    View Slide

  24. View Slide

  25. Media Session ҳഅਵ۽ ঳য૑ח Ѫٜ
    Player
    UI
    Media Session

    View Slide

  26. যڃ ۄ੉࠳۞ܻܳ ࢎਊ೧ঠ ೞחо?
    Player
    UI
    Media Session
    ❏ Jetpack Media(MediaCompat),


    ❏ Media2


    ❏ ExoPlayer


    п੗ ݾ੸੉ ੓Ҋ, Ҁ஖ח ҳഅب ࢚׼ೠ


    ׮নೠ Media apiо ઓ੤.

    View Slide

  27. যڃ ۄ੉࠳۞ܻܳ ࢎਊ೧ঠ ೞחо?
    UI
    Player
    Media Session
    ❏ Jetpack Media(MediaCompat),


    ❏ Media2


    ❏ ExoPlayer


    View Slide

  28. যڃ ۄ੉࠳۞ܻܳ ࢎਊ೧ঠ ೞחо?
    Player
    UI
    Media Session
    ❏ Jetpack Media(MediaCompat),


    ❏ Media2


    ❏ ExoPlayer

    View Slide

  29. যڃ ۄ੉࠳۞ܻܳ ࢎਊ೧ঠ ೞחо?
    Player
    UI
    Media Session
    ❏ Jetpack Media(MediaCompat),


    ❏ Media2


    ❏ ExoPlayer
    Ӓ ߆ীب ৈ۞о૑ য۰਑੉…

    View Slide

  30. যڃ ۄ੉࠳۞ܻܳ ࢎਊ೧ঠ ೞחо?
    Player
    UI
    Media Session
    ❏ Jetpack Media(MediaCompat),


    ❏ Media2


    ❏ ExoPlayer
    Media3 👍

    View Slide

  31. Foreground Playback
    Activity
    Player UI
    Media Session

    View Slide

  32. Foreground Playback (previous api)
    Activity
    Player UI
    Media Session
    androidx.media ExoPlayer ExoPlayer

    View Slide

  33. Foreground Playback (previous api)
    Activity
    Player UI
    Media Session
    androidx.media ExoPlayer ExoPlayer
    Connector

    View Slide

  34. https://github.com/google/ExoPlayer/blob/release-v2/extensions/mediasession/src/main/java/com/
    google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java


    package com.google.android.exoplayer2.ext.mediasession;


    public final class MediaSessionConnector {


    // media session command -> player


    @Override


    public void onPlay(…) {


    // …


    player.play();


    }


    // player callback -> media session


    @Override


    public void onPlaybackStateChanged(…) {


    // …


    mediaSession.setState(newState);


    }


    }

    View Slide

  35. https://github.com/google/ExoPlayer/blob/release-v2/extensions/mediasession/src/main/java/com/
    google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java


    package com.google.android.exoplayer2.ext.mediasession;


    public final class MediaSessionConnector {


    // media session command -> player


    @Override


    public void onPlay(…) {


    // …


    player.play();


    }


    // player callback -> media session


    @Override


    public void onPlaybackStateChanged(…) {


    // …


    mediaSession.setState(newState);


    }


    }

    View Slide

  36. https://github.com/google/ExoPlayer/blob/release-v2/extensions/mediasession/src/main/java/com/
    google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java


    package com.google.android.exoplayer2.ext.mediasession;


    public final class MediaSessionConnector {


    // media session command -> player


    @Override


    public void onPlay(…) {


    // …


    player.play();


    }


    // player callback -> media session


    @Override


    public void onPlaybackStateChanged(…) {


    // …


    mediaSession.setState(newState);


    }


    }

    View Slide

  37. Foreground Playback (media3)
    Activity
    Player UI
    Media Session

    View Slide

  38. Foreground Playback (media3)
    Activity
    Player UI
    Media Session
    // androidx.media3.common


    interface Player { … }

    View Slide

  39. Foreground Playback (media3)
    Activity
    ExoPlayer UI
    Media Session
    // androidx.media3.common


    interface Player { … }
    Implements Player
    Takes Player Takes Player
    Connector ೙ਃ হ਺!

    View Slide

  40. // Initialize ExoPlayer as an implementation of Player interface


    val player = ExoPlayer.Builder(context).build()


    View Slide

  41. // Initialize ExoPlayer as an implementation of Player interface


    val player = ExoPlayer.Builder(context).build()


    // Takes the Player instance to initialize MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()


    View Slide

  42. // Initialize ExoPlayer as an implementation of Player interface


    val player = ExoPlayer.Builder(context).build()


    // Takes the Player instance to initialize MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()


    // Takes the Player instance to configure UI


    playerView.player = player


    View Slide

  43. Background Playback
    Service
    Player
    Activity
    UI

    View Slide

  44. Background Playback (previous api)
    Service
    Media


    Session
    Player
    Activity
    UI
    Media
    Controller

    View Slide

  45. Background Playback (previous api)
    Service
    Media


    Session
    Player
    Activity
    UI
    Media
    Controller
    androidx.media ExoPlayer
    ExoPlayer androidx.media

    View Slide

  46. Background Playback (previous api)
    Service
    Media


    Session
    Player
    Activity
    UI
    Media
    Controller
    androidx.media ExoPlayer
    ExoPlayer androidx.media
    Connector Connector

    View Slide

  47. Background Playback (media3)
    Service
    Media


    Session
    Player
    Activity
    UI
    Media
    Controller

    View Slide

  48. Background Playback (media3)
    Service
    Media


    Session
    ExoPlayer
    Activity
    UI
    Media
    Controller
    Implements Player
    Takes Player Takes Player
    Implements Player

    View Slide

  49. // PlayerService


    // 1. Initialize ExoPlayer as an implementation of the Player interface


    val player = ExoPlayer.Builder(context).build()

    View Slide

  50. // PlayerService


    // 1. Initialize ExoPlayer as an implementation of the Player interface


    val player = ExoPlayer.Builder(context).build()


    // 2. Takes the Player instance to initialize the MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()

    View Slide

  51. // PlayerService


    // 1. Initialize ExoPlayer as an implementation of the Player interface


    val player = ExoPlayer.Builder(context).build()


    // 2. Takes the Player instance to initialize the MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()


    // PlayerActivity


    // 1. Create a session token to identify session.


    val sessionToken = SessionToken(context, ComponentName(context, PlayerService::class.java))

    View Slide

  52. // PlayerService


    // 1. Initialize ExoPlayer as an implementation of the Player interface


    val player = ExoPlayer.Builder(context).build()


    // 2. Takes the Player instance to initialize the MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()


    // PlayerActivity


    // 1. Create a session token to identify session.


    val sessionToken = SessionToken(context, ComponentName(context, PlayerService::class.java))


    // 2. Build mediaController which connects to the session asynchronously.


    val mediaControllerFuture = MediaController


    .Builder(context, sessionToken)


    .buildAsync()

    View Slide

  53. // PlayerService


    // 1. Initialize ExoPlayer as an implementation of the Player interface


    val player = ExoPlayer.Builder(context).build()


    // 2. Takes the Player instance to initialize the MediaSession


    val mediaSession = MediaSession.Builder(context, player).build()


    // PlayerActivity


    // 1. Create a session token to identify session.


    val sessionToken = SessionToken(context, ComponentName(context, PlayerService::class.java))


    // 2. Build mediaController which connects to the session asynchronously.


    val mediaControllerFuture = MediaController


    .Builder(context, sessionToken)


    .buildAsync()


    // 3. Takes the Player instance to configure UI


    mediaControllerFuture.addListener(


    { playerView.setPlayer(player = mediaControllerFuture.get())},


    MoreExecutors.directExecutor()


    )


    View Slide

  54. Working with other apps
    MediaBrowserService
    ❏ Exposing media session


    ❏ Exposing content library

    View Slide

  55. Working with other apps
    MediaBrowserService
    ❏ Exposing media session


    ❏ Exposing content library
    MediaSessionService
    ❏ Exposing media session

    View Slide

  56. Working with other apps
    MediaBrowserService
    ❏ Exposing media session


    ❏ Exposing content library
    MediaSessionService
    MediaLibraryService
    ❏ Exposing media session
    ❏ Exposing content library

    View Slide

  57. Exposing media session

    View Slide

  58. Exposing content library

    View Slide

  59. ٘۽੉٘ա੉எ জ + media3

    View Slide

  60. View Slide

  61. TODO
    ❏ Video ҙ۲ model ࣻ੿


    ❏ SessionDetailScreenী video ਬޖী ٮܲ ೒ۨ੉য ૓ੑ੼ ઁҕ


    ❏ ޷٣য ੤ࢤ ҳഅ (PlayerScreen, PlaybackService)

    View Slide

  62. core:playback
    ❏ PlaybackState


    ❏ PlaybackStateManager


    ❏ PlaybackStateListener


    ❏ PlaybackService


    ❏ PlayerController

    View Slide

  63. data class PlaybackState(


    val isPlaying: Boolean = false,


    val hasPrevious: Boolean = false,


    val hasNext: Boolean = false,


    val position: Long = C.TIME_UNSET,


    val duration: Long = C.TIME_UNSET,


    val speed: Float = 1F,


    val aspectRatio: Float = 16F / 9F,


    val title: String? = null,


    val artist: String? = null,


    val artworkUri: Uri? = null,


    )


    View Slide

  64. @Singleton


    class PlaybackStateManager @Inject constructor() {


    private val _playbackState = MutableStateFlow(PlaybackState())


    val flow: StateFlow get() = _playbackState


    var playbackState: PlaybackState


    set(value) {


    _playbackState.value = value


    }


    get() = _playbackState.value


    }


    View Slide

  65. internal class PlaybackStateListener @Inject constructor(…) : Player.Listener {


    private lateinit var player: Player


    override fun onPlaybackStateChanged(…) { updatePlayState() }


    override fun onPlayWhenReadyChanged(…) { … }


    override fun onPositionDiscontinuity(…) { … }


    override fun onIsPlayingChanged(…) { … }


    override fun onPlaybackParametersChanged(…) { … }


    override fun onVideoSizeChanged(…) { … }


    }


    View Slide

  66. internal class PlaybackStateListener @Inject constructor(…) : Player.Listener {


    // …


    private fun updatePlayState() {


    val playbackState = player.playbackState


    playbackStateManager.playbackState = PlaybackState(


    isPlaying = when {


    playbackState == Player.STATE_ENDED || playbackState == Player.STATE_IDLE -> false


    player.playWhenReady -> true


    else -> false


    },


    hasPrevious = player.hasPreviousMediaItem(),


    hasNext = player.hasNextMediaItem(),


    position = player.contentPosition,


    duration = player.duration,


    speed = player.playbackParameters.speed,


    aspectRatio = …,


    title = …,


    artist = …,


    artworkUri = …


    )


    }


    }

    View Slide

  67. internal class PlaybackStateListener @Inject constructor(…) : Player.Listener {


    private lateinit var player: Player


    fun attachTo(player: Player) {


    this.player = player


    player.addListener(this)


    scope.launch {


    playbackStateManager.flow


    .map { it.isPlaying }


    .collectLatest { isPlaying ->


    if (isPlaying) {


    while (true) {


    updatePlayState()


    delay(400.milliseconds)


    }


    }


    }


    }


    }


    }

    View Slide

  68. @AndroidEntryPoint


    class PlaybackService : MediaLibraryService() {


    @Inject


    lateinit var session: MediaLibrarySession


    ……


    override fun onGetSession(


    controllerInfo: MediaSession.ControllerInfo


    ): MediaLibrarySession? { … }


    }

    View Slide

  69. class PlayerController @Inject constructor(…) {





    fun play() = executeAfterPrepare { controller -> controller.play() }


    fun playPause() = executeAfterPrepare { controller ->


    if (controller.isPlaying) { controller.pause() }


    else { controller.play() }


    }


    fun setPosition(positionMs: Long) = executeAfterPrepare { controller -> controller.seekTo(positionMs) }


    fun fastForward() = executeAfterPrepare { controller -> controller.seekForward() }


    fun rewind() = executeAfterPrepare { controller -> controller.seekBack() }


    fun previous() = executeAfterPrepare { controller -> controller.seekToPrevious() }


    fun next() = executeAfterPrepare { controller -> controller.seekToNext() }





    }

    View Slide

  70. feature:player
    ❏ PlayerView


    ❏ PlayerScreen


    ❏ PlayerViewModel

    View Slide

  71. @Composable


    fun PlayerView(


    modifier: Modifier = Modifier


    ) {


    var player: Player? by remember { mutableStateOf(null) }


    var surfaceView: SurfaceView? by remember { mutableStateOf(null) }


    }


    View Slide

  72. @Composable


    fun PlayerView(


    modifier: Modifier = Modifier


    ) {


    var player: Player? by remember { mutableStateOf(null) }


    var surfaceView: SurfaceView? by remember { mutableStateOf(null) }


    AndroidView(


    factory = { SurfaceView(it).apply { surfaceView = this } },


    modifier = modifier


    )


    }


    View Slide

  73. @Composable


    fun PlayerView(


    modifier: Modifier = Modifier


    ) {


    val context = LocalContext.current


    var player: Player? by remember { mutableStateOf(null) }


    var surfaceView: SurfaceView? by remember { mutableStateOf(null) }


    LaunchedEffect(Unit) {


    player = MediaController


    .Builder(


    context,


    SessionToken(context, ComponentName(context, PlaybackService::class.java))


    )


    .buildAsync()


    .await()


    }


    AndroidView(


    factory = { SurfaceView(it).apply { surfaceView = this } },


    modifier = modifier


    )


    }


    View Slide

  74. @Composable


    fun PlayerView(


    modifier: Modifier = Modifier


    ) {


    val context = LocalContext.current


    var player: Player? by remember { mutableStateOf(null) }


    var surfaceView: SurfaceView? by remember { mutableStateOf(null) }


    LaunchedEffect(Unit) {


    player = MediaController


    .Builder(


    context,


    SessionToken(context, ComponentName(context, PlaybackService::class.java))


    )


    .buildAsync()


    .await()


    }


    DisposableEffect(player, surfaceView) {


    if (player?.isCommandAvailable(Player.COMMAND_SET_VIDEO_SURFACE) == true) {


    player?.setVideoSurfaceView(surfaceView)


    }


    onDispose {


    if (player?.isCommandAvailable(Player.COMMAND_SET_VIDEO_SURFACE) == true) {


    player?.clearVideoSurfaceView(surfaceView)


    }


    }


    }


    AndroidView(


    factory = { SurfaceView(it).apply { surfaceView = this } },


    modifier = modifier


    )


    }


    View Slide

  75. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  76. @HiltViewModel


    class PlayerViewModel @Inject constructor(





    private val playbackStateManager: PlaybackStateManager,





    ) : ViewModel() {


    private val _playerUiState =


    MutableStateFlow(PlayerUiState.Loading)


    val playerUiState: StateFlow = _playerUiState


    init {





    viewModelScope.launch {


    playbackStateManager.flow.collect {


    _playerUiState.value = PlayerUiState.Success(


    it.isPlaying,


    it.hasPrevious,


    it.hasNext,


    it.position,


    it.duration,


    it.speed,


    it.aspectRatio


    )


    }


    }


    }


    }

    View Slide

  77. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  78. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  79. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  80. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  81. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    View Slide

  82. sealed interface PlayerUiState {


    object Loading : PlayerUiState


    data class Success(


    val isPlaying: Boolean,


    val hasPrevious: Boolean,


    val hasNext: Boolean,


    val position: Long,


    val duration: Long,


    val speed: Float,


    val aspectRatio: Float


    ) : PlayerUiState


    }


    PlayerView

    View Slide

  83. @HiltViewModel


    class PlayerViewModel @Inject constructor(





    private val playerController: PlayerController,


    ) : ViewModel() {





    fun playPause() {


    playerController.playPause()


    }


    fun prev() {


    playerController.previous()


    }


    fun next() {


    playerController.next()


    }


    fun setPosition(position: Long) {


    playerController.setPosition(position)


    }


    }

    View Slide

  84. @HiltViewModel


    class PlayerViewModel @Inject constructor(





    private val playerController: PlayerController,


    ) : ViewModel() {





    fun playPause() {


    playerController.playPause()


    }


    fun prev() {


    playerController.previous()


    }


    fun next() {


    playerController.next()


    }


    fun setPosition(position: Long) {


    playerController.setPosition(position)


    }


    }

    View Slide

  85. @HiltViewModel


    class PlayerViewModel @Inject constructor(





    private val playerController: PlayerController,


    ) : ViewModel() {





    fun playPause() {


    playerController.playPause()


    }


    fun prev() {


    playerController.previous()


    }


    fun next() {


    playerController.next()


    }


    fun setPosition(position: Long) {


    playerController.setPosition(position)


    }


    }

    View Slide

  86. @HiltViewModel


    class PlayerViewModel @Inject constructor(





    private val playerController: PlayerController,


    ) : ViewModel() {





    fun playPause() {


    playerController.playPause()


    }


    fun prev() {


    playerController.previous()


    }


    fun next() {


    playerController.next()


    }


    fun setPosition(position: Long) {


    playerController.setPosition(position)


    }


    }

    View Slide

  87. View Slide

  88. ӝࠄ ೒ۨ੉য ঌܿ ઁҕ

    View Slide

  89. Media Key event ୊ܻ

    View Slide

  90. োѾػ ٣߄੉झীࢲ ޷٣য ஶ౟܀۞ ઁҕ

    View Slide

  91. MediaLibraryService - Android Auto ૑ਗ (1)


    android:foregroundServiceType="mediaPlayback"


    android:exported="true"


    tools:ignore="ExportedService">

















    View Slide

  92. MediaLibraryService - Android Auto ૑ਗ (2)
    Root Keynote
    Keynote
    Dev Environment
    Architecture
    Session
    Session
    Session
    Session
    Track 01
    Session
    Session

    View Slide

  93. MediaLibraryService - Android Auto ૑ਗ (3)

    View Slide

  94. core:playback module੄ ੤ഝਊ
    ❏ Wear OS জ


    ❏ TV জ


    ❏ Automotive জ


    ਤ 3о૑ জ… ߈ա੺݅ী ׮ ٜ݅ ࣻ ੓঻਺😎

    View Slide

  95. DroidKnights 2023 app for wear os
    feature:wear-session feature:wear-player

    View Slide

  96. DroidKnights 2023 app for tv
    feature:tv-session

    View Slide

  97. DroidKnights 2023 app for tv
    feature:playerח ੤ഝਊ😁

    View Slide

  98. DroidKnights 2023 app for tv : Now Playing

    View Slide

  99. DroidKnights 2023 app for automotive
    ❏ Launcher Activity হ੉ MediaBrowerService݅ ઁҕ೧ب ؽ

    View Slide

  100. DroidKnights 2023 app for automotive

    View Slide

  101. DroidKnights 2023 app for automotive

    View Slide

  102. ਃড

    View Slide

  103. ਃড
    ❏ Media3 ۄ੉࠳۞ܻח ӝઓ ۄ੉࠳۞ܻܳ ݽف ా೤!


    ❏ ࢎਊ੗ ҃೷ਸ ਤ೧ MediaSession੉ ઺ਃ


    ❏ Media*Service ҳഅਸ ా೧ ߔӒۄ਍٘ ੤ࢤ, ӝࠄ ঌܿ ઁҕ, ޷٣য ۄ੉࠳
    ۞ܻ ઁҕਸ औѱ ೡ ࣻ ੓਺.


    ❏ ੤ࢤ ҳഅ ݽٕਸ ੤ഝਊೞৈ wear os, tv, auto ޷٣য জਸ औѱ ѐߊ

    View Slide

  104. ݃૑݄ਵ۽ ઱੄੼ (1)
    ❏ ޙࢲ/೐۽ં౟ మ೒݁਷ ই૒ ߈৔ উؽ…


    ex) media3۽ Android Autoܳ ૑ਗೞ۰ݶ?
    old ޙࢲ / template
    Android Autoܳ ૑ਗೞ۰ݶ
    MediaBrowserServiceCompatਸ
    ҳഅ೧ঠೠ׮.
    new ޙࢲ
    media3 MediaLibraryServiceח
    legacy media API৬ ഐജػ׮.

    View Slide

  105. Resource
    Google I/O 2014 - Building great multi-media experiences on Android (18:29 ~)
    Introducing Jetpack Media3
    Media3 is ready to play!
    Introduction to Jetpack Media3
    Media3 Github
    Android Dev Summit 2021 - What’s next for AndroidX Media and ExoPlayer
    DroidKnight2023 App Github (reference-media3 branch)
    Using Jetpack Compose on Wear OS
    Use Jetpack Compose on Android TV
    Build media apps for cars
    ⌚ 📺 🚗

    View Slide

  106. хࢎ೤פ׮.

    View Slide