Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2022年の動画再生アプリの作り方

 2022年の動画再生アプリの作り方

MIXI ENGINEERS

October 05, 2022
Tweet

More Decks by MIXI ENGINEERS

Other Decks in Technology

Transcript

  1. 2022
    DroidKaigi 2022
    MIXI

    View full-size slide

  2. @oidy
    2014
    SearchBar Ex : 80 DL
    2017
    ( : MIXI)
    Android
    2018
    KARASTA Android / iOS

    View full-size slide

  3. 2022
    ExoPlayer Jetpack Media3
    , Picture-in-Picture,
    3
    2
    1

    View full-size slide

  4. 2008 : MediaPlayer
    MediaPlayer
    VideoView
    MediaPlayer View
    SurfaceView
    API Level 1
    Android 1.0
    App
    MediaPlayer
    DataSource

    View full-size slide

  5. 2014 : MediaSession
    Google I/O Android Wear, Auto, TV
    API Level 21
    Android 5.0

    View full-size slide

  6. MediaSession
    App
    Metadata
    ,
    Player MediaSession
    Headset
    Android
    Auto Wear OS
    Android
    TV
    Command
    ,
    Google
    Assistant

    View full-size slide

  7. 2017 : Picture-in-Picture
    Activity
    Android 7.0
    7.0 Android TV
    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org
    API Level 26
    Android 8.0

    View full-size slide

  8. 2020 : Large screens & foldables
    Android 10
    Jetpack WindowManager
    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

    View full-size slide

  9. Material Design 3 guidelines
    https://m3.material.io/foundations/adaptive-design/overview
    UI
    App quality guidelines
    https://developer.android.com/docs/quality-guidelines/large-
    screen-app-quality

    View full-size slide

  10. MediaSession
    Picture-in-Picture
    ,

    View full-size slide

  11. ExoPlayer & Jetpack Media3

    View full-size slide

  12. ExoPlayer
    https://github.com/google/ExoPlayer
    MediaPlayer

    HLS, DASH, SmoothStreaming

    • UI •

    View full-size slide

  13. ExoPlayer
    PlayerView
    ExoPlayer

    View full-size slide

  14. Jetpack Media2
    VideoView
    MediaPlayer
    ExoPlayer
    PlayerView
    ExoPlayer

    View full-size slide

  15. Jetpack Media2
    VideoView
    MediaPlayer
    MediaSession
    ExoPlayer
    PlayerView
    ExoPlayer

    View full-size slide

  16. ExoPlayer Extensions
    Connector
    Jetpack Media2
    VideoView
    MediaPlayer
    MediaSession
    ExoPlayer
    PlayerView
    ExoPlayer

    View full-size slide

  17. Jetpack Media3
    2021 10 Media
    ExoPlayer Jetpack Media3
    PlayerView
    ExoPlayer
    MediaSession
    androidx.media3:media3-ui
    androidx.media3:media3-exoplayer
    androidx.media3:media3-session
    ...

    View full-size slide

  18. Jetpack Media3
    ExoPlayer
    Media3
    com.google.android.exoplayer:exoplayer
    androidx.media3:media3-exoplayer

    View full-size slide

  19. Jetpack Media3
    • OK
    https://developer.android.com/guide/topics/media/media3/exoplayer/mappings
    androidx.media3.exoplayer.ExoPlayer
    androidx.media3.ui.PlayerView
    androidx.media3.common.Player
    com.google.android.exoplayer2.ExoPlayer
    com.google.android.exoplayer2.ui.StyledPlayerView
    com.google.android.exoplayer2.Player

    Migration guide:
    https://developer.android.com/guide/topics/media/media3/getting-started/migration-guide

    View full-size slide

  20. Unstable APIs
    Media3 2022 9 1.0.0-beta02
    API Unstable
    Unstable API @OptIn
    @OptIn(UnstableApi::class)
    private fun methodUsingUnstableApis() {
    ...
    }

    View full-size slide

  21. API

    • Extension

    View full-size slide

  22. API

    • Extension
    Jetpack Media3

    View full-size slide

  23. Activity
    PlayerView
    ExoPlayer

    View full-size slide

  24. Activity
    class PlayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    }
    }

    View full-size slide

  25. Activity
    PlayerView
    class PlayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val playerView = PlayerView(this)
    setContentView(playerView)
    }
    }

    View full-size slide

  26. Activity
    PlayerView
    ExoPlayer
    class PlayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val playerView = PlayerView(this)
    setContentView(playerView)
    val player = ExoPlayer.Builder(this).build()
    }
    }

    View full-size slide

  27. Activity
    PlayerView
    ExoPlayer
    class PlayerActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val playerView = PlayerView(this)
    setContentView(playerView)
    val player = ExoPlayer.Builder(this).build()
    playerView.player = player
    }
    }

    View full-size slide

  28. Activity
    PlayerView
    ExoPlayer
    // Uri
    val mediaItem = MediaItem.fromUri(...)
    player.setMediaItem(mediaItem)
    //
    player.prepare()
    player.play()
    MediaItem

    View full-size slide

  29. override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Player
    player = ...
    }
    override fun onDestroy() {
    super.onDestroy()
    // Player
    player.release()
    }

    View full-size slide

  30. Activity Player
    Activity Player
    : ,
    Activity
    PlayerView
    ExoPlayer
    Activity Service Player

    View full-size slide

  31. Foreground Service
    Activity
    PlayerView
    ExoPlayer
    Service
    • ExoPlayer Service

    • PlayerView Player

    View full-size slide

  32. Foreground Service
    Activity
    PlayerView
    ExoPlayer
    Service
    MediaSessionService
    • ExoPlayer Service

    • PlayerView Player

    View full-size slide

  33. Activity
    PlayerView
    MediaSessionService
    ExoPlayer

    View full-size slide

  34. Activity
    PlayerView
    MediaSessionService
    ExoPlayer
    MediaSession

    View full-size slide

  35. Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession
    Session
    Token

    View full-size slide

  36. Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession
    implements Player
    setMediaItem()
    play()
    pause() ...

    View full-size slide

  37. Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession

    View full-size slide

  38. : DefaultMediaNotificationProvider
    NotificationProvider
    setMediaNotificationProvider()



    View full-size slide

  39. MediaSessionService
    PlayerService
    class PlayerService : MediaSessionService() {
    }

    View full-size slide

  40. MediaSessionService
    PlayerService
    class PlayerService : MediaSessionService() {
    private lateinit var player: ExoPlayer
    private lateinit var mediaSession: MediaSession
    override fun onCreate() {
    super.onCreate()
    player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
    }
    }
    ExoPlayer
    MediaSession

    View full-size slide

  41. MediaSessionService
    PlayerService
    class PlayerService : MediaSessionService() {
    private lateinit var player: ExoPlayer
    private lateinit var mediaSession: MediaSession
    override fun onCreate() {
    super.onCreate()
    player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
    }
    override fun onGetSession(info: ControllerInfo): MediaSession {
    return mediaSession
    }
    }
    ExoPlayer
    MediaSession

    View full-size slide

  42. MediaSessionService
    PlayerService
    class PlayerService : MediaSessionService() {
    ...
    override fun onDestroy() {
    //
    player.release()
    mediaSession.release()
    }
    }
    ExoPlayer
    MediaSession

    View full-size slide

  43. MediaController
    val component = ComponentName(this, PlayerService::class.java)
    val token = SessionToken(this, component)
    Activity
    PlayerView
    MediaController

    View full-size slide

  44. MediaController
    val component = ComponentName(this, PlayerService::class.java)
    val token = SessionToken(this, component)
    val controllerFuture = MediaController.Builder(this, token)
    .buildAsync()
    controllerFuture.addListener({
    val mediaController = controllerFuture.get()
    }, MoreExecutors.directExecutor())
    Activity
    PlayerView
    MediaController

    View full-size slide

  45. MediaController
    val component = ComponentName(this, PlayerService::class.java)
    val token = SessionToken(this, component)
    val controllerFuture = MediaController.Builder(this, token)
    .buildAsync()
    controllerFuture.addListener({
    val mediaController = controllerFuture.get()
    playerView.player = mediaController
    }, MoreExecutors.directExecutor())
    Activity
    PlayerView
    MediaController

    View full-size slide

  46. MediaItem
    Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession
    MediaItem
    setUri()

    View full-size slide

  47. MediaItem
    Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession
    MediaItem
    setUri()
    Uri MediaItem
    ( )

    View full-size slide

  48. MediaItem
    val requestMetadata = MediaItem.RequestMetadata.Builder()
    .setMediaUri(uri)
    .build()
    val mediaItem = MediaItem.Builder()
    .setRequestMetadata(requestMetadata)
    .build()
    mediaController.setMediaItem(mediaItem)
    Activity
    MediaController
    MediaItem
    setMediaUri()
    RequestMetadata
    1
    2
    3
    1
    2
    3

    View full-size slide

  49. MediaItem
    val mediaSessionCallback = object : MediaSession.Callback {
    override fun onAddMediaItems(
    mediaSession: MediaSession,
    controllerInfo: ControllerInfo,
    mediaItems: List
    ) = Futures.immediateFuture(mediaItems.map { mediaItem ->
    mediaItem.buildUpon()
    .setUri(mediaItem.requestMetadata.mediaUri)
    .build()
    })
    }
    Service

    View full-size slide

  50. MediaItem
    val mediaSessionCallback = ...
    mediaSession = MediaSession.Builder(this, player)
    .setCallback(mediaSessionCallback)
    .build()
    Service

    View full-size slide

  51. Activity
    PlayerView
    MediaController
    MediaSessionService
    ExoPlayer
    MediaSession
    Activity
    PlayerView
    ExoPlayer

    View full-size slide

  52. Pitcure-in-Picture (PiP)
    AndroidManifest Activity
    Activity PiP
    Activity PiP UI
    3
    2
    1

    View full-size slide

  53. AndroidManifest
    android:supportsPictureInPicture
    Activity PiP
    android:configChanges
    PiP Activity
    android:name=".PlayerActivity"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
    1

    View full-size slide

  54. PiP
    val params = PictureInPictureParams.Builder()
    // PiP
    .setAspectRatio(Rational(width, height))
    // PiP
    .setActions(actions)
    .build()
    // PiP
    enterPictureInPictureMode(params)
    2

    View full-size slide

  55. PiP
    val params = PictureInPictureParams.Builder()
    // PiP
    .setAspectRatio(Rational(width, height))
    // PiP
    .setActions(actions)
    .build()
    // PiP
    enterPictureInPictureMode(params)
    2
    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

    View full-size slide

  56. PiP
    2
    button.setOnClickListener {
    enterPictureInPictureMode(params)
    }
    override fun onUserLeaveHint() {
    enterPictureInPictureMode(params)
    }
    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

    View full-size slide

  57. PiP
    if (packageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
    enterPictureInPictureMode(params)
    }
    2
    PiP

    View full-size slide

  58. PiP
    if (packageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
    try {
    enterPictureInPictureMode(params)
    } catch (e: IllegalStateException) {
    // Device doesn't support PiP mode.
    }
    }
    2
    FEATURE_PICTURE_IN_PICTURE

    View full-size slide

  59. PiP UI
    private val pipListener = Consumer { info ->
    if (info.isInPictureInPictureMode) {
    // PlayerView UI
    } else {
    // PlayerView UI
    }
    }
    3
    PiP

    View full-size slide

  60. PiP UI
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    addOnPictureInPictureModeChangedListener(pipListener)
    }
    override fun onDestroy() {
    super.onDestroy()
    removeOnPictureInPictureModeChangedListener(pipListener)
    }
    androidx.activity
    3

    View full-size slide

  61. : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

    View full-size slide

  62. lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    WindowInfoTracker.getOrCreate(this@PlayerActivity)
    .windowLayoutInfo(this@PlayerActivity)
    .mapNotNull { layoutInfo ->
    layoutInfo.displayFeatures
    .filterIsInstance()
    .firstOrNull()
    }
    .collect { foldingFeature ->
    // UI
    }
    }
    }

    View full-size slide

  63. lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    WindowInfoTracker.getOrCreate(this@PlayerActivity)
    .windowLayoutInfo(this@PlayerActivity)
    .mapNotNull { layoutInfo ->
    layoutInfo.displayFeatures
    .filterIsInstance()
    .firstOrNull()
    }
    .collect { foldingFeature ->
    // UI
    }
    }
    }
    WindowLayoutInfo Flow

    View full-size slide

  64. lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    WindowInfoTracker.getOrCreate(this@PlayerActivity)
    .windowLayoutInfo(this@PlayerActivity)
    .mapNotNull { layoutInfo ->
    layoutInfo.displayFeatures
    .filterIsInstance()
    .firstOrNull()
    }
    .collect { foldingFeature ->
    // UI
    }
    }
    }
    WindowLayoutInfo
    FoldingFeature

    View full-size slide

  65. lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
    WindowInfoTracker.getOrCreate(this@PlayerActivity)
    .windowLayoutInfo(this@PlayerActivity)
    .mapNotNull { layoutInfo ->
    layoutInfo.displayFeatures
    .filterIsInstance()
    .firstOrNull()
    }
    .collect { foldingFeature ->
    // UI
    }
    }
    }
    state
    orientation
    occlusionType
    isSeparating

    View full-size slide

  66. FoldingFeature UI
    if (foldingFeature.state == FLAT) {
    //
    } else {
    if (foldingFeature.orientation == HORIZONTAL) {
    // foldingFeature.bounds.top
    } else {
    // foldingFeature.bounds.left
    }
    }

    View full-size slide

  67. FoldingFeature UI
    ConstraintLayout PlayerView
    View UI
    Compose
    Composable PlayerView
    Composable Modifier PlayerView

    View full-size slide

  68. FoldingFeature UI
    ConstraintLayout PlayerView
    View UI
    Compose
    Composable PlayerView
    Composable Modifier PlayerView
    val playerView = remember {
    PlayerView(context)
    }
    DisposableEffect(
    AndroidView(factory = { playerView })
    ) {
    onDispose {
    mediaController.release()
    }
    }

    View full-size slide

  69. https://github.com/oidy/VideoPlayerSample
    • Jetpack Media3
    • MediaSessionService
    • Picture-in-Picture
    • Jetpack Compose

    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

    View full-size slide

  70. https://github.com/androidx/media/tree/release/demos
    Media3
    Jetpack Media3
    https://github.com/android/compose-samples/tree/main/Jetcaster
    UI
    Jetpack Compose

    View full-size slide

  71. Android
    2022
    Jetpack Media3
    Picture-in-Picture
    Activity
    Jetpack WindowManager
    2022

    View full-size slide