-
Notifications
You must be signed in to change notification settings - Fork 750
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Screen sharing over WebRTC #5911
Changes from all commits
7939eca
fb7533b
dd5d263
166be43
b358863
ba4413e
754208e
b486559
cf3d145
9a1dbb2
3a02e84
bb862cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Screen sharing over WebRTC |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,11 +27,20 @@ class ScreenCaptureServiceConnection @Inject constructor( | |
private val context: Context | ||
) : ServiceConnection { | ||
|
||
interface Callback { | ||
fun onServiceConnected() | ||
} | ||
|
||
private var isBound = false | ||
private var screenCaptureService: ScreenCaptureService? = null | ||
private var callback: Callback? = null | ||
|
||
fun bind(callback: Callback) { | ||
this.callback = callback | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to reset the callback to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, done. |
||
|
||
fun bind() { | ||
if (!isBound) { | ||
if (isBound) { | ||
callback.onServiceConnected() | ||
} else { | ||
Intent(context, ScreenCaptureService::class.java).also { intent -> | ||
context.bindService(intent, this, 0) | ||
} | ||
|
@@ -45,10 +54,12 @@ class ScreenCaptureServiceConnection @Inject constructor( | |
override fun onServiceConnected(className: ComponentName, binder: IBinder) { | ||
screenCaptureService = (binder as ScreenCaptureService.LocalBinder).getService() | ||
isBound = true | ||
callback?.onServiceConnected() | ||
} | ||
|
||
override fun onServiceDisconnected(className: ComponentName) { | ||
isBound = false | ||
screenCaptureService = null | ||
callback = null | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,10 +80,12 @@ import org.webrtc.MediaConstraints | |
import org.webrtc.MediaStream | ||
import org.webrtc.PeerConnection | ||
import org.webrtc.PeerConnectionFactory | ||
import org.webrtc.RtpSender | ||
import org.webrtc.RtpTransceiver | ||
import org.webrtc.SessionDescription | ||
import org.webrtc.SurfaceTextureHelper | ||
import org.webrtc.SurfaceViewRenderer | ||
import org.webrtc.VideoCapturer | ||
import org.webrtc.VideoSource | ||
import org.webrtc.VideoTrack | ||
import timber.log.Timber | ||
|
@@ -95,6 +97,7 @@ import kotlin.coroutines.CoroutineContext | |
private const val STREAM_ID = "userMedia" | ||
private const val AUDIO_TRACK_ID = "${STREAM_ID}a0" | ||
private const val VIDEO_TRACK_ID = "${STREAM_ID}v0" | ||
private const val SCREEN_TRACK_ID = "${STREAM_ID}s0" | ||
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints() | ||
private const val INVITE_TIMEOUT_IN_MS = 60_000L | ||
|
||
|
@@ -153,13 +156,16 @@ class WebRtcCall( | |
private var makingOffer: Boolean = false | ||
private var ignoreOffer: Boolean = false | ||
|
||
private var videoCapturer: CameraVideoCapturer? = null | ||
private var videoCapturer: VideoCapturer? = null | ||
|
||
private val availableCamera = ArrayList<CameraProxy>() | ||
private var cameraInUse: CameraProxy? = null | ||
private var currentCaptureFormat: CaptureFormat = CaptureFormat.HD | ||
private var cameraAvailabilityCallback: CameraManager.AvailabilityCallback? = null | ||
|
||
private var videoSender: RtpSender? = null | ||
private var screenSender: RtpSender? = null | ||
|
||
private val timer = CountUpTimer(1000L).apply { | ||
tickListener = object : CountUpTimer.TickListener { | ||
override fun onTick(milliseconds: Long) { | ||
|
@@ -617,7 +623,7 @@ class WebRtcCall( | |
val videoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource) | ||
Timber.tag(loggerTag.value).v("Add video track $VIDEO_TRACK_ID to call ${mxCall.callId}") | ||
videoTrack.setEnabled(true) | ||
peerConnection?.addTrack(videoTrack, listOf(STREAM_ID)) | ||
videoSender = peerConnection?.addTrack(videoTrack, listOf(STREAM_ID)) | ||
localVideoSource = videoSource | ||
localVideoTrack = videoTrack | ||
} | ||
|
@@ -722,7 +728,7 @@ class WebRtcCall( | |
Timber.tag(loggerTag.value).v("switchCamera") | ||
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) { | ||
val oppositeCamera = getOppositeCameraIfAny() ?: return@launch | ||
videoCapturer?.switchCamera( | ||
(videoCapturer as? CameraVideoCapturer)?.switchCamera( | ||
object : CameraVideoCapturer.CameraSwitchHandler { | ||
// Invoked on success. |isFrontCamera| is true if the new camera is front facing. | ||
override fun onCameraSwitchDone(isFrontCamera: Boolean) { | ||
|
@@ -770,12 +776,60 @@ class WebRtcCall( | |
return currentCaptureFormat | ||
} | ||
|
||
fun startSharingScreen() { | ||
// TODO. Will be handled within the next PR. | ||
fun startSharingScreen(videoCapturer: VideoCapturer) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you tried to extract the screen sharing capabilities into a dedicated component to avoid adding new responsabilities to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it is easily possible. This class has many local fields. We will start implementing Element Call soon, so we probably won't invest in this class more. |
||
val factory = peerConnectionFactoryProvider.get() ?: return | ||
|
||
this.videoCapturer = videoCapturer | ||
|
||
val localMediaStream = factory.createLocalMediaStream(STREAM_ID) | ||
val videoSource = factory.createVideoSource(videoCapturer.isScreencast) | ||
|
||
startCapturingScreen(videoCapturer, videoSource) | ||
|
||
removeLocalSurfaceRenderers() | ||
|
||
showScreenLocally(factory, videoSource, localMediaStream) | ||
|
||
videoSender?.let { removeStream(it) } | ||
|
||
screenSender = peerConnection?.addTrack(localVideoTrack, listOf(STREAM_ID)) | ||
} | ||
|
||
fun stopSharingScreen() { | ||
// TODO. Will be handled within the next PR. | ||
localVideoTrack?.setEnabled(false) | ||
screenSender?.let { removeStream(it) } | ||
if (mxCall.isVideoCall) { | ||
peerConnectionFactoryProvider.get()?.let { configureVideoTrack(it) } | ||
} | ||
updateMuteStatus() | ||
sessionScope?.launch(dispatcher) { attachViewRenderersInternal() } | ||
} | ||
|
||
private fun removeStream(sender: RtpSender) { | ||
peerConnection?.removeTrack(sender) | ||
} | ||
|
||
private fun showScreenLocally(factory: PeerConnectionFactory, videoSource: VideoSource?, localMediaStream: MediaStream?) { | ||
localVideoTrack = factory.createVideoTrack(SCREEN_TRACK_ID, videoSource).apply { setEnabled(true) } | ||
localMediaStream?.addTrack(localVideoTrack) | ||
localSurfaceRenderers.forEach { it.get()?.let { localVideoTrack?.addSink(it) } } | ||
} | ||
|
||
private fun removeLocalSurfaceRenderers() { | ||
localSurfaceRenderers.forEach { it.get()?.let { localVideoTrack?.removeSink(it) } } | ||
} | ||
|
||
private fun startCapturingScreen(videoCapturer: VideoCapturer, videoSource: VideoSource) { | ||
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext) | ||
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.capturerObserver) | ||
videoCapturer.startCapture(currentCaptureFormat.width, currentCaptureFormat.height, currentCaptureFormat.fps) | ||
} | ||
|
||
/** | ||
* Returns true if the user is sharing the screen, false otherwise. | ||
*/ | ||
fun isSharingScreen(): Boolean { | ||
return localVideoTrack?.enabled().orFalse() && localVideoTrack?.id() == SCREEN_TRACK_ID | ||
} | ||
|
||
private suspend fun release() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I think the set of the
isSharingScreen
to false in the state is missing. Maybe we should reuse an mutualize what is done inhandleToggleScreenSharing()
when screen is shared:What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, although we finish the call activity as soon as the call ended.