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. @oidy 2014 SearchBar Ex : 80 DL 2017 ( :

    MIXI) Android 2018 KARASTA Android / iOS
  2. 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
  3. 2020 : Large screens & foldables Android 10 Jetpack WindowManager

    : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org
  4. Jetpack Media3 2021 10 Media ExoPlayer Jetpack Media3 PlayerView ExoPlayer

    MediaSession androidx.media3:media3-ui androidx.media3:media3-exoplayer androidx.media3:media3-session ...
  5. Unstable APIs Media3 2022 9 1.0.0-beta02 API Unstable Unstable API

    @OptIn @OptIn(UnstableApi::class) private fun methodUsingUnstableApis() { ... }
  6. Activity PlayerView class PlayerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) val playerView = PlayerView(this) setContentView(playerView) } }
  7. 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() } }
  8. 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 } }
  9. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Player player =

    ... } override fun onDestroy() { super.onDestroy() // Player player.release() }
  10. 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
  11. 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
  12. MediaSessionService PlayerService class PlayerService : MediaSessionService() { ... override fun

    onDestroy() { // player.release() mediaSession.release() } } ExoPlayer MediaSession
  13. 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
  14. 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
  15. 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
  16. MediaItem val mediaSessionCallback = object : MediaSession.Callback { override fun

    onAddMediaItems( mediaSession: MediaSession, controllerInfo: ControllerInfo, mediaItems: List<MediaItem> ) = Futures.immediateFuture(mediaItems.map { mediaItem -> mediaItem.buildUpon() .setUri(mediaItem.requestMetadata.mediaUri) .build() }) } Service
  17. PiP val params = PictureInPictureParams.Builder() // PiP .setAspectRatio(Rational(width, height)) //

    PiP .setActions(actions) .build() // PiP enterPictureInPictureMode(params) 2
  18. 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
  19. PiP 2 button.setOnClickListener { enterPictureInPictureMode(params) } override fun onUserLeaveHint() {

    enterPictureInPictureMode(params) } : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org
  20. 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
  21. PiP UI private val pipListener = Consumer<PictureInPictureModeChangedInfo> { info ->

    if (info.isInPictureInPictureMode) { // PlayerView UI } else { // PlayerView UI } } 3 PiP
  22. PiP UI override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) addOnPictureInPictureModeChangedListener(pipListener) }

    override fun onDestroy() { super.onDestroy() removeOnPictureInPictureModeChangedListener(pipListener) } androidx.activity 3
  23. lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { WindowInfoTracker.getOrCreate(this@PlayerActivity) .windowLayoutInfo(this@PlayerActivity) .mapNotNull { layoutInfo ->

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

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

    layoutInfo.displayFeatures .filterIsInstance<FoldingFeature>() .firstOrNull() } .collect { foldingFeature -> // UI } } } state orientation occlusionType isSeparating
  26. FoldingFeature UI if (foldingFeature.state == FLAT) { // } else

    { if (foldingFeature.orientation == HORIZONTAL) { // foldingFeature.bounds.top } else { // foldingFeature.bounds.left } }
  27. FoldingFeature UI ConstraintLayout PlayerView View UI Compose Composable PlayerView Composable

    Modifier PlayerView val playerView = remember { PlayerView(context) } DisposableEffect( AndroidView(factory = { playerView }) ) { onDispose { mediaController.release() } }
  28. https://github.com/oidy/VideoPlayerSample • Jetpack Media3 • MediaSessionService • Picture-in-Picture • Jetpack

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