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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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

Avatar for MIXI ENGINEERS

MIXI ENGINEERS PRO

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