Slide 1

Slide 1 text

2022 DroidKaigi 2022 MIXI

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

2022

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

MediaSession Picture-in-Picture ,

Slide 12

Slide 12 text

ExoPlayer & Jetpack Media3

Slide 13

Slide 13 text

ExoPlayer https://github.com/google/ExoPlayer MediaPlayer • HLS, DASH, SmoothStreaming • • UI •

Slide 14

Slide 14 text

ExoPlayer PlayerView ExoPlayer

Slide 15

Slide 15 text

Jetpack Media2 VideoView MediaPlayer ExoPlayer PlayerView ExoPlayer

Slide 16

Slide 16 text

Jetpack Media2 VideoView MediaPlayer MediaSession ExoPlayer PlayerView ExoPlayer

Slide 17

Slide 17 text

ExoPlayer Extensions Connector Jetpack Media2 VideoView MediaPlayer MediaSession ExoPlayer PlayerView ExoPlayer

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

API • • Extension

Slide 23

Slide 23 text

API • • Extension Jetpack Media3

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Activity PlayerView ExoPlayer

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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() } }

Slide 29

Slide 29 text

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 } }

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Activity Player Activity Player : , Activity PlayerView ExoPlayer Activity Service Player

Slide 33

Slide 33 text

Foreground Service Activity PlayerView ExoPlayer Service • ExoPlayer Service • • PlayerView Player

Slide 34

Slide 34 text

Foreground Service Activity PlayerView ExoPlayer Service MediaSessionService • ExoPlayer Service • • PlayerView Player

Slide 35

Slide 35 text

Activity PlayerView MediaSessionService ExoPlayer

Slide 36

Slide 36 text

Activity PlayerView MediaSessionService ExoPlayer MediaSession

Slide 37

Slide 37 text

Activity PlayerView MediaController MediaSessionService ExoPlayer MediaSession Session Token

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Activity PlayerView MediaController MediaSessionService ExoPlayer MediaSession

Slide 40

Slide 40 text

: DefaultMediaNotificationProvider NotificationProvider setMediaNotificationProvider() • • •

Slide 41

Slide 41 text

MediaSessionService PlayerService class PlayerService : MediaSessionService() { }

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

MediaItem Activity PlayerView MediaController MediaSessionService ExoPlayer MediaSession MediaItem setUri()

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Activity PlayerView MediaController MediaSessionService ExoPlayer MediaSession Activity PlayerView ExoPlayer

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

AndroidManifest android:supportsPictureInPicture Activity PiP android:configChanges PiP Activity 1

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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 } } }

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

https://github.com/oidy/VideoPlayerSample • Jetpack Media3 • MediaSessionService • Picture-in-Picture • Jetpack Compose • : Big Buck Bunny, © 2008, Blender Foundation / www.bigbuckbunny.org

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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