Committed by
GitHub
Unpublish the screen sharing track on stop and introduce ScreenCaptureParams (#626)
* Unpublish the screen sharing track on stop * Introduce ScreenCaptureParams * Add changeset * Lint fixes
正在显示
6 个修改的文件
包含
69 行增加
和
16 行删除
.changeset/sharp-apes-invite.md
0 → 100644
| @@ -30,6 +30,7 @@ import io.livekit.android.audio.ScreenAudioCapturer | @@ -30,6 +30,7 @@ import io.livekit.android.audio.ScreenAudioCapturer | ||
| 30 | import io.livekit.android.room.track.LocalAudioTrack | 30 | import io.livekit.android.room.track.LocalAudioTrack |
| 31 | import io.livekit.android.room.track.LocalVideoTrack | 31 | import io.livekit.android.room.track.LocalVideoTrack |
| 32 | import io.livekit.android.room.track.Track | 32 | import io.livekit.android.room.track.Track |
| 33 | +import io.livekit.android.room.track.screencapture.ScreenCaptureParams | ||
| 33 | import io.livekit.android.sample.service.ForegroundService | 34 | import io.livekit.android.sample.service.ForegroundService |
| 34 | import kotlinx.coroutines.Dispatchers | 35 | import kotlinx.coroutines.Dispatchers |
| 35 | import kotlinx.coroutines.launch | 36 | import kotlinx.coroutines.launch |
| @@ -59,7 +60,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | @@ -59,7 +60,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | ||
| 59 | return@launch | 60 | return@launch |
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | - room.localParticipant.setScreenShareEnabled(true, data) | 63 | + room.localParticipant.setScreenShareEnabled(true, ScreenCaptureParams(data)) |
| 63 | 64 | ||
| 64 | // Optionally disable the mic for screenshare audio only | 65 | // Optionally disable the mic for screenshare audio only |
| 65 | // val javaAudioDeviceModule = (room.lkObjects.audioDeviceModule as? JavaAudioDeviceModule) | 66 | // val javaAudioDeviceModule = (room.lkObjects.audioDeviceModule as? JavaAudioDeviceModule) |
| @@ -50,6 +50,7 @@ import io.livekit.android.room.track.TrackPublication | @@ -50,6 +50,7 @@ import io.livekit.android.room.track.TrackPublication | ||
| 50 | import io.livekit.android.room.track.VideoCaptureParameter | 50 | import io.livekit.android.room.track.VideoCaptureParameter |
| 51 | import io.livekit.android.room.track.VideoCodec | 51 | import io.livekit.android.room.track.VideoCodec |
| 52 | import io.livekit.android.room.track.VideoEncoding | 52 | import io.livekit.android.room.track.VideoEncoding |
| 53 | +import io.livekit.android.room.track.screencapture.ScreenCaptureParams | ||
| 53 | import io.livekit.android.room.util.EncodingUtils | 54 | import io.livekit.android.room.util.EncodingUtils |
| 54 | import io.livekit.android.rpc.RpcError | 55 | import io.livekit.android.rpc.RpcError |
| 55 | import io.livekit.android.util.LKLog | 56 | import io.livekit.android.util.LKLog |
| @@ -222,6 +223,7 @@ internal constructor( | @@ -222,6 +223,7 @@ internal constructor( | ||
| 222 | mediaProjectionPermissionResultData: Intent, | 223 | mediaProjectionPermissionResultData: Intent, |
| 223 | options: LocalVideoTrackOptions = screenShareTrackCaptureDefaults.copy(), | 224 | options: LocalVideoTrackOptions = screenShareTrackCaptureDefaults.copy(), |
| 224 | videoProcessor: VideoProcessor? = null, | 225 | videoProcessor: VideoProcessor? = null, |
| 226 | + onStop: (Track) -> Unit, | ||
| 225 | ): LocalScreencastVideoTrack { | 227 | ): LocalScreencastVideoTrack { |
| 226 | val screencastOptions = options.copy(isScreencast = true) | 228 | val screencastOptions = options.copy(isScreencast = true) |
| 227 | return LocalScreencastVideoTrack.createTrack( | 229 | return LocalScreencastVideoTrack.createTrack( |
| @@ -233,6 +235,7 @@ internal constructor( | @@ -233,6 +235,7 @@ internal constructor( | ||
| 233 | eglBase, | 235 | eglBase, |
| 234 | screencastVideoTrackFactory, | 236 | screencastVideoTrackFactory, |
| 235 | videoProcessor, | 237 | videoProcessor, |
| 238 | + onStop, | ||
| 236 | ) | 239 | ) |
| 237 | } | 240 | } |
| 238 | 241 | ||
| @@ -293,15 +296,15 @@ internal constructor( | @@ -293,15 +296,15 @@ internal constructor( | ||
| 293 | @Throws(TrackException.PublishException::class) | 296 | @Throws(TrackException.PublishException::class) |
| 294 | suspend fun setScreenShareEnabled( | 297 | suspend fun setScreenShareEnabled( |
| 295 | enabled: Boolean, | 298 | enabled: Boolean, |
| 296 | - mediaProjectionPermissionResultData: Intent? = null, | 299 | + screenCaptureParams: ScreenCaptureParams? = null, |
| 297 | ) { | 300 | ) { |
| 298 | - setTrackEnabled(Track.Source.SCREEN_SHARE, enabled, mediaProjectionPermissionResultData) | 301 | + setTrackEnabled(Track.Source.SCREEN_SHARE, enabled, screenCaptureParams) |
| 299 | } | 302 | } |
| 300 | 303 | ||
| 301 | private suspend fun setTrackEnabled( | 304 | private suspend fun setTrackEnabled( |
| 302 | source: Track.Source, | 305 | source: Track.Source, |
| 303 | enabled: Boolean, | 306 | enabled: Boolean, |
| 304 | - mediaProjectionPermissionResultData: Intent? = null, | 307 | + screenCaptureParams: ScreenCaptureParams? = null, |
| 305 | ) { | 308 | ) { |
| 306 | val pubLock = sourcePubLocks[source]!! | 309 | val pubLock = sourcePubLocks[source]!! |
| 307 | pubLock.withLock { | 310 | pubLock.withLock { |
| @@ -327,12 +330,15 @@ internal constructor( | @@ -327,12 +330,15 @@ internal constructor( | ||
| 327 | } | 330 | } |
| 328 | 331 | ||
| 329 | Track.Source.SCREEN_SHARE -> { | 332 | Track.Source.SCREEN_SHARE -> { |
| 330 | - if (mediaProjectionPermissionResultData == null) { | ||
| 331 | - throw IllegalArgumentException("Media Projection permission result data is required to create a screen share track.") | 333 | + if (screenCaptureParams == null) { |
| 334 | + throw IllegalArgumentException("Media Projection params is required to create a screen share track.") | ||
| 332 | } | 335 | } |
| 333 | val track = | 336 | val track = |
| 334 | - createScreencastTrack(mediaProjectionPermissionResultData = mediaProjectionPermissionResultData) | ||
| 335 | - track.startForegroundService(null, null) | 337 | + createScreencastTrack(mediaProjectionPermissionResultData = screenCaptureParams.mediaProjectionPermissionResultData) { |
| 338 | + unpublishTrack(it) | ||
| 339 | + screenCaptureParams.onStop?.invoke() | ||
| 340 | + } | ||
| 341 | + track.startForegroundService(screenCaptureParams.notificationId, screenCaptureParams.notification) | ||
| 336 | track.startCapture() | 342 | track.startCapture() |
| 337 | publishVideoTrack(track, options = VideoTrackPublishOptions(null, screenShareTrackPublishDefaults)) | 343 | publishVideoTrack(track, options = VideoTrackPublishOptions(null, screenShareTrackPublishDefaults)) |
| 338 | } | 344 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -151,7 +151,10 @@ constructor( | @@ -151,7 +151,10 @@ constructor( | ||
| 151 | private val serviceConnection = ScreenCaptureConnection(context) | 151 | private val serviceConnection = ScreenCaptureConnection(context) |
| 152 | 152 | ||
| 153 | init { | 153 | init { |
| 154 | - mediaProjectionCallback.onStopCallback = { stop() } | 154 | + mediaProjectionCallback.apply { |
| 155 | + track = this@LocalScreencastVideoTrack | ||
| 156 | + addOnStopCallback { stop() } | ||
| 157 | + } | ||
| 155 | } | 158 | } |
| 156 | 159 | ||
| 157 | /** | 160 | /** |
| @@ -203,11 +206,18 @@ constructor( | @@ -203,11 +206,18 @@ constructor( | ||
| 203 | /** | 206 | /** |
| 204 | * Needed to deal with circular dependency. | 207 | * Needed to deal with circular dependency. |
| 205 | */ | 208 | */ |
| 206 | - class MediaProjectionCallback : MediaProjection.Callback() { | ||
| 207 | - var onStopCallback: (() -> Unit)? = null | 209 | + class MediaProjectionCallback() : MediaProjection.Callback() { |
| 210 | + | ||
| 211 | + var track: Track? = null | ||
| 212 | + | ||
| 213 | + private val onStopCallbacks = mutableListOf<(Track) -> Unit>() | ||
| 214 | + | ||
| 215 | + fun addOnStopCallback(callback: (Track) -> Unit) { | ||
| 216 | + onStopCallbacks.add(callback) | ||
| 217 | + } | ||
| 208 | 218 | ||
| 209 | override fun onStop() { | 219 | override fun onStop() { |
| 210 | - onStopCallback?.invoke() | 220 | + onStopCallbacks.forEach { it.invoke(track!!) } |
| 211 | } | 221 | } |
| 212 | } | 222 | } |
| 213 | 223 | ||
| @@ -221,10 +231,13 @@ constructor( | @@ -221,10 +231,13 @@ constructor( | ||
| 221 | rootEglBase: EglBase, | 231 | rootEglBase: EglBase, |
| 222 | screencastVideoTrackFactory: Factory, | 232 | screencastVideoTrackFactory: Factory, |
| 223 | videoProcessor: VideoProcessor?, | 233 | videoProcessor: VideoProcessor?, |
| 234 | + onStop: (Track) -> Unit, | ||
| 224 | ): LocalScreencastVideoTrack { | 235 | ): LocalScreencastVideoTrack { |
| 225 | val source = peerConnectionFactory.createVideoSource(options.isScreencast) | 236 | val source = peerConnectionFactory.createVideoSource(options.isScreencast) |
| 226 | source.setVideoProcessor(videoProcessor) | 237 | source.setVideoProcessor(videoProcessor) |
| 227 | - val callback = MediaProjectionCallback() | 238 | + val callback = MediaProjectionCallback().apply { |
| 239 | + addOnStopCallback(onStop) | ||
| 240 | + } | ||
| 228 | val capturer = createScreenCapturer(mediaProjectionPermissionResultData, callback) | 241 | val capturer = createScreenCapturer(mediaProjectionPermissionResultData, callback) |
| 229 | capturer.initialize( | 242 | capturer.initialize( |
| 230 | SurfaceTextureHelper.create("ScreenVideoCaptureThread", rootEglBase.eglBaseContext), | 243 | SurfaceTextureHelper.create("ScreenVideoCaptureThread", rootEglBase.eglBaseContext), |
livekit-android-sdk/src/main/java/io/livekit/android/room/track/screencapture/ScreenCaptureParams.kt
0 → 100644
| 1 | +/* | ||
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.room.track.screencapture | ||
| 18 | + | ||
| 19 | +import android.app.Notification | ||
| 20 | +import android.content.Intent | ||
| 21 | + | ||
| 22 | +class ScreenCaptureParams( | ||
| 23 | + val mediaProjectionPermissionResultData: Intent, | ||
| 24 | + val notificationId: Int? = null, | ||
| 25 | + val notification: Notification? = null, | ||
| 26 | + val onStop: (() -> Unit)? = null | ||
| 27 | +) |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -46,6 +46,7 @@ import io.livekit.android.room.track.CameraPosition | @@ -46,6 +46,7 @@ import io.livekit.android.room.track.CameraPosition | ||
| 46 | import io.livekit.android.room.track.LocalScreencastVideoTrack | 46 | import io.livekit.android.room.track.LocalScreencastVideoTrack |
| 47 | import io.livekit.android.room.track.LocalVideoTrack | 47 | import io.livekit.android.room.track.LocalVideoTrack |
| 48 | import io.livekit.android.room.track.Track | 48 | import io.livekit.android.room.track.Track |
| 49 | +import io.livekit.android.room.track.screencapture.ScreenCaptureParams | ||
| 49 | import io.livekit.android.room.track.video.CameraCapturerUtils | 50 | import io.livekit.android.room.track.video.CameraCapturerUtils |
| 50 | import io.livekit.android.sample.model.StressTest | 51 | import io.livekit.android.sample.model.StressTest |
| 51 | import io.livekit.android.sample.service.ForegroundService | 52 | import io.livekit.android.sample.service.ForegroundService |
| @@ -308,7 +309,7 @@ class CallViewModel( | @@ -308,7 +309,7 @@ class CallViewModel( | ||
| 308 | fun startScreenCapture(mediaProjectionPermissionResultData: Intent) { | 309 | fun startScreenCapture(mediaProjectionPermissionResultData: Intent) { |
| 309 | val localParticipant = room.localParticipant | 310 | val localParticipant = room.localParticipant |
| 310 | viewModelScope.launch { | 311 | viewModelScope.launch { |
| 311 | - localParticipant.setScreenShareEnabled(true, mediaProjectionPermissionResultData) | 312 | + localParticipant.setScreenShareEnabled(true, ScreenCaptureParams(mediaProjectionPermissionResultData)) |
| 312 | val screencastTrack = localParticipant.getTrackPublication(Track.Source.SCREEN_SHARE)?.track as? LocalScreencastVideoTrack | 313 | val screencastTrack = localParticipant.getTrackPublication(Track.Source.SCREEN_SHARE)?.track as? LocalScreencastVideoTrack |
| 313 | this@CallViewModel.localScreencastTrack = screencastTrack | 314 | this@CallViewModel.localScreencastTrack = screencastTrack |
| 314 | mutableScreencastEnabled.postValue(screencastTrack?.enabled) | 315 | mutableScreencastEnabled.postValue(screencastTrack?.enabled) |
-
请 注册 或 登录 后发表评论