fix: tear down nest session on PIP close + Just Leave
Three more leave paths now fully release the speaker session and
listener (and with them AudioRecord + the system mic indicator)
before the Activity destruction queue eats viewModelScope:
- PIP overlay's Leave action — pipReceiver invokes a cleanup
callback set by NestActivityBody, then finish().
- PIP swipe-dismiss — NestActivity.onStop() invokes the same
callback when in PIP and isFinishing. Non-PIP onStop stays a
no-op so backgrounding doesn't tear down.
- Host's Just leave — calls viewModel.leave() before onLeave().
Unlike Close the Room, this still leaves the kind-30312
meeting space open for other users.
The cleanup callback is registered via DisposableEffect in
NestActivityBody so it's auto-cleared when the composable leaves.
This commit is contained in:
+35
@@ -83,12 +83,31 @@ class NestActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
ACTION_PIP_LEAVE -> {
|
||||
// Tear down the speaker session + listener BEFORE
|
||||
// finish() so AudioRecord (and the system mic
|
||||
// indicator) releases promptly. onCleared() alone
|
||||
// runs late in the destroy lifecycle.
|
||||
pipCleanupAction?.invoke()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wired by [NestActivityBody] via a DisposableEffect. Invokes
|
||||
* `viewModel.leave()` so the broadcast handle + listener close
|
||||
* route through `cleanupScope`/GlobalScope and survive Activity
|
||||
* destruction. Called from PIP close paths (the overlay's Leave
|
||||
* action and a swipe-dismiss while in PIP).
|
||||
*/
|
||||
@Volatile
|
||||
private var pipCleanupAction: (() -> Unit)? = null
|
||||
|
||||
fun setPipCleanupAction(action: (() -> Unit)?) {
|
||||
pipCleanupAction = action
|
||||
}
|
||||
|
||||
private val _toggleMuteSignal =
|
||||
MutableSharedFlow<Unit>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
@@ -180,6 +199,22 @@ class NestActivity : AppCompatActivity() {
|
||||
enterPip()
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects a PIP swipe-dismiss. When the user dismisses the floating
|
||||
* window the system finishes the activity, so [isFinishing] is true
|
||||
* by the time onStop runs. Tearing down here (rather than waiting
|
||||
* for onCleared) keeps AudioRecord + the system mic indicator from
|
||||
* lingering through the destruction queue. Non-PIP onStop (Home /
|
||||
* Recents while not in PIP) is a no-op so backgrounding doesn't
|
||||
* teardown.
|
||||
*/
|
||||
override fun onStop() {
|
||||
if (isInPipMode.value && isFinishing) {
|
||||
pipCleanupAction?.invoke()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter Picture-in-Picture if the audio session is live and the
|
||||
* system supports PIP. Called both by [onUserLeaveHint] (Home /
|
||||
|
||||
+15
@@ -31,6 +31,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -39,6 +40,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -274,6 +276,19 @@ private fun NestActivityBody(
|
||||
PipBridge(ui, pipMuteSignal, viewModel, onMuteState, onConnectedChange)
|
||||
NestForegroundServiceLifecycle(ui)
|
||||
|
||||
// Tell NestActivity how to tear the session down when the user
|
||||
// closes PIP. Both close paths in PIP — the overlay's Leave
|
||||
// action and a swipe-dismiss while in PIP — invoke this so the
|
||||
// speaker handle close (releases AudioRecord and clears the
|
||||
// system mic indicator) and the listener close run on
|
||||
// cleanupScope/GlobalScope before the activity destruction
|
||||
// queue eats viewModelScope.
|
||||
val activity = LocalContext.current as? NestActivity
|
||||
DisposableEffect(activity, viewModel) {
|
||||
activity?.setPipCleanupAction { viewModel.leave() }
|
||||
onDispose { activity?.setPipCleanupAction(null) }
|
||||
}
|
||||
|
||||
// Hand-raise is screen-local UI state; presence-publish picks it
|
||||
// up via the heartbeat and emits onstage / muted / publishing
|
||||
// alongside.
|
||||
|
||||
+9
@@ -394,6 +394,15 @@ internal fun NestFullScreen(
|
||||
TextButton(
|
||||
onClick = {
|
||||
showHostLeaveConfirm = false
|
||||
// Tear down the speaker session + listener before
|
||||
// finishing so the AudioRecord (and the system mic
|
||||
// indicator) releases promptly. Same reasoning as
|
||||
// the Close the Room path — onCleared() runs late
|
||||
// in the destroy lifecycle and can leave the mic
|
||||
// held while the activity is queued for destruction.
|
||||
// Unlike Close the Room, this path leaves the
|
||||
// kind-30312 meeting space open for other users.
|
||||
viewModel.leave()
|
||||
onLeave()
|
||||
},
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user