1616
1717package io.getstream.video.android.compose.lifecycle
1818
19+ import android.app.Activity
20+ import android.app.Application
21+ import android.os.Bundle
1922import android.os.Handler
2023import android.os.Looper
2124import androidx.compose.runtime.Composable
2225import androidx.compose.runtime.DisposableEffect
23- import androidx.compose.runtime.LaunchedEffect
24- import androidx.compose.runtime.getValue
25- import androidx.compose.runtime.mutableStateOf
26- import androidx.compose.runtime.remember
27- import androidx.compose.runtime.setValue
2826import androidx.compose.ui.platform.LocalContext
2927import androidx.compose.ui.platform.LocalLifecycleOwner
30- import androidx.lifecycle.Lifecycle
31- import androidx.lifecycle.LifecycleEventObserver
28+ import androidx.lifecycle.LifecycleOwner
3229import io.getstream.log.StreamLog
3330import io.getstream.video.android.compose.pip.enterPictureInPicture
31+ import io.getstream.video.android.compose.pip.findActivity
3432import io.getstream.video.android.compose.pip.isInPictureInPictureMode
3533import io.getstream.video.android.core.Call
36- import kotlinx.coroutines.delay
3734
3835/* *
3936 * Register a call media lifecycle that controls camera and microphone depending on lifecycles.
@@ -43,56 +40,66 @@ import kotlinx.coroutines.delay
4340 * - camera/microphone will be enabled if the lifecycle is onResumed, and not on the PIP mode.
4441 *
4542 * @param call The call includes states and will be rendered with participants.
46- * @param pipEnteringDuration The duration requires to be engaged in Picture-In-Picture mode.
4743 */
4844@Composable
4945public fun MediaPiPLifecycle (
5046 call : Call ,
5147 enableInPictureInPicture : Boolean = false,
52- pipEnteringDuration : Long = 250,
5348) {
5449 val context = LocalContext .current
55- val lifecycle = LocalLifecycleOwner .current.lifecycle
56- var latestLifecycleEvent by remember { mutableStateOf( Lifecycle . Event . ON_ANY ) }
57- DisposableEffect (lifecycle) {
58- val observer = LifecycleEventObserver { _, event ->
59- latestLifecycleEvent = event
60- }
61- lifecycle.addObserver(observer)
62- onDispose {
63- lifecycle.removeObserver(observer )
64- }
65- }
50+ val lifecycleOwner : LifecycleOwner = LocalLifecycleOwner .current
51+ val currentActivity : Activity ? = LocalContext .current.findActivity()
52+
53+ DisposableEffect (key1 = lifecycleOwner) {
54+ // TODO: There's not way to onUserLeaveHint in Compose for now. The only thing
55+ // that works so far is to listen to ActivityLifecycleCallbacks and enable PiP in
56+ // onPause.
57+ // https://developer.android.com/reference/android/app/Activity#onUserLeaveHint()
58+ val application = (context.applicationContext as ? Application )
59+ val callbackListener = object : Application . ActivityLifecycleCallbacks {
60+ override fun onActivityCreated ( activity : Activity , savedInstanceState : Bundle ? ) { }
6661
67- if (latestLifecycleEvent == Lifecycle .Event .ON_PAUSE ) {
68- LaunchedEffect (latestLifecycleEvent) {
69- delay(pipEnteringDuration)
70- val isInPictureInPicture = context.isInPictureInPictureMode
71- if (! isInPictureInPicture && ! enableInPictureInPicture) {
72- call.camera.pause(fromUser = false )
73- call.microphone.pause(fromUser = false )
74- } else if (! isInPictureInPicture) {
75- Handler (Looper .getMainLooper()).post {
76- try {
77- // TODO: There's not way to onUserLeaveHint in Compose for now.
78- // https://developer.android.com/reference/android/app/Activity#onUserLeaveHint()
79- enterPictureInPicture(context = context, call = call)
80- } catch (e: Exception ) {
81- StreamLog .d(" MediaPiPLifecycle" ) { e.stackTraceToString() }
62+ override fun onActivityStarted (activity : Activity ) { }
63+
64+ override fun onActivityResumed (activity : Activity ) {
65+ if (activity == currentActivity) {
66+ val isInPictureInPicture = context.isInPictureInPictureMode
67+ if (! isInPictureInPicture && ! enableInPictureInPicture) {
68+ call.camera.resume(fromUser = false )
69+ call.microphone.resume(fromUser = false )
8270 }
8371 }
8472 }
85- }
86- }
8773
88- if (latestLifecycleEvent == Lifecycle .Event .ON_RESUME ) {
89- LaunchedEffect (latestLifecycleEvent) {
90- delay(pipEnteringDuration)
91- val isInPictureInPicture = context.isInPictureInPictureMode
92- if (! isInPictureInPicture && ! enableInPictureInPicture) {
93- call.camera.resume(fromUser = false )
94- call.microphone.resume(fromUser = false )
74+ override fun onActivityPaused (activity : Activity ) {
75+ if (activity == currentActivity) {
76+ val isInPictureInPicture = context.isInPictureInPictureMode
77+ if (! isInPictureInPicture && ! enableInPictureInPicture) {
78+ call.camera.pause(fromUser = false )
79+ call.microphone.pause(fromUser = false )
80+ } else if (! isInPictureInPicture) {
81+ Handler (Looper .getMainLooper()).post {
82+ try {
83+ enterPictureInPicture(context = context, call = call)
84+ } catch (e: Exception ) {
85+ StreamLog .d(" MediaPiPLifecycle" ) { e.stackTraceToString() }
86+ }
87+ }
88+ }
89+ }
9590 }
91+
92+ override fun onActivityStopped (activity : Activity ) { }
93+
94+ override fun onActivitySaveInstanceState (activity : Activity , outState : Bundle ) { }
95+
96+ override fun onActivityDestroyed (activity : Activity ) { }
97+ }
98+
99+ application?.registerActivityLifecycleCallbacks(callbackListener)
100+
101+ onDispose {
102+ application?.unregisterActivityLifecycleCallbacks(callbackListener)
96103 }
97104 }
98105}
0 commit comments