davidliu
Committed by GitHub

restart video track api (#10)

@@ -78,8 +78,8 @@ internal constructor( @@ -78,8 +78,8 @@ internal constructor(
78 listOf(this.sid) 78 listOf(this.sid)
79 ) 79 )
80 // TODO: sendEncodings to customize 80 // TODO: sendEncodings to customize
81 - val transceiver =  
82 - engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) 81 + val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
  82 + track.transceiver = transceiver
83 83
84 if (transceiver == null) { 84 if (transceiver == null) {
85 publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection")) 85 publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
@@ -108,8 +108,8 @@ internal constructor( @@ -108,8 +108,8 @@ internal constructor(
108 listOf(this.sid) 108 listOf(this.sid)
109 ) 109 )
110 // TODO: video encodings & simulcast 110 // TODO: video encodings & simulcast
111 - val transceiver =  
112 - engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit) 111 + val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
  112 + track.transceiver = transceiver
113 113
114 if (transceiver == null) { 114 if (transceiver == null) {
115 publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection")) 115 publishListener?.onPublishFailure(TrackException.PublishException("null sender returned from peer connection"))
@@ -2,6 +2,8 @@ package io.livekit.android.room.track @@ -2,6 +2,8 @@ package io.livekit.android.room.track
2 2
3 import org.webrtc.MediaConstraints 3 import org.webrtc.MediaConstraints
4 import org.webrtc.PeerConnectionFactory 4 import org.webrtc.PeerConnectionFactory
  5 +import org.webrtc.RtpSender
  6 +import org.webrtc.RtpTransceiver
5 import java.util.* 7 import java.util.*
6 8
7 /** 9 /**
@@ -19,6 +21,10 @@ class LocalAudioTrack( @@ -19,6 +21,10 @@ class LocalAudioTrack(
19 rtcTrack.setEnabled(value) 21 rtcTrack.setEnabled(value)
20 } 22 }
21 23
  24 + internal var transceiver: RtpTransceiver? = null
  25 + private val sender: RtpSender?
  26 + get() = transceiver?.sender
  27 +
22 companion object { 28 companion object {
23 internal fun createTrack( 29 internal fun createTrack(
24 factory: PeerConnectionFactory, 30 factory: PeerConnectionFactory,
@@ -11,20 +11,32 @@ import java.util.* @@ -11,20 +11,32 @@ import java.util.*
11 * [startCapture] should be called before use. 11 * [startCapture] should be called before use.
12 */ 12 */
13 class LocalVideoTrack( 13 class LocalVideoTrack(
14 - private val capturer: VideoCapturer,  
15 - private val source: VideoSource, 14 + private var capturer: VideoCapturer,
  15 + private var source: VideoSource,
16 name: String, 16 name: String,
17 - private val options: LocalVideoTrackOptions,  
18 - rtcTrack: org.webrtc.VideoTrack 17 + var options: LocalVideoTrackOptions,
  18 + rtcTrack: org.webrtc.VideoTrack,
  19 + private val peerConnectionFactory: PeerConnectionFactory,
  20 + private val context: Context,
  21 + private val eglBase: EglBase,
19 ) : VideoTrack(name, rtcTrack) { 22 ) : VideoTrack(name, rtcTrack) {
20 - val dimensions: Dimensions  
21 23
22 - init {  
23 - dimensions = Dimensions(options.captureParams.width, options.captureParams.height)  
24 - } 24 + override var rtcTrack: org.webrtc.VideoTrack = rtcTrack
  25 + internal set
  26 +
  27 + val dimensions: Dimensions =
  28 + Dimensions(options.captureParams.width, options.captureParams.height)
  29 +
  30 + internal var transceiver: RtpTransceiver? = null
  31 + private val sender: RtpSender?
  32 + get() = transceiver?.sender
25 33
26 fun startCapture() { 34 fun startCapture() {
27 - capturer.startCapture(options.captureParams.width, options.captureParams.height, options.captureParams.maxFps) 35 + capturer.startCapture(
  36 + options.captureParams.width,
  37 + options.captureParams.height,
  38 + options.captureParams.maxFps
  39 + )
28 } 40 }
29 41
30 override fun stop() { 42 override fun stop() {
@@ -32,6 +44,40 @@ class LocalVideoTrack( @@ -32,6 +44,40 @@ class LocalVideoTrack(
32 super.stop() 44 super.stop()
33 } 45 }
34 46
  47 + fun restartTrack(options: LocalVideoTrackOptions = LocalVideoTrackOptions()) {
  48 + val newTrack = createTrack(
  49 + peerConnectionFactory,
  50 + context,
  51 + name,
  52 + options,
  53 + eglBase
  54 + )
  55 +
  56 + val oldCapturer = capturer
  57 + val oldSource = source
  58 + val oldRtcTrack = rtcTrack
  59 +
  60 + oldCapturer.stopCapture()
  61 + oldCapturer.dispose()
  62 + oldSource.dispose()
  63 +
  64 + // sender owns rtcTrack, so it'll take care of disposing it.
  65 + oldRtcTrack.setEnabled(false)
  66 +
  67 + // migrate video sinks to the new track
  68 + for (sink in sinks) {
  69 + oldRtcTrack.removeSink(sink)
  70 + newTrack.addRenderer(sink)
  71 + }
  72 +
  73 + capturer = newTrack.capturer
  74 + source = newTrack.source
  75 + rtcTrack = newTrack.rtcTrack
  76 + this.options = options
  77 + startCapture()
  78 + sender?.setTrack(newTrack.rtcTrack, true)
  79 + }
  80 +
35 companion object { 81 companion object {
36 internal fun createTrack( 82 internal fun createTrack(
37 peerConnectionFactory: PeerConnectionFactory, 83 peerConnectionFactory: PeerConnectionFactory,
@@ -55,6 +101,9 @@ class LocalVideoTrack( @@ -55,6 +101,9 @@ class LocalVideoTrack(
55 options = options, 101 options = options,
56 name = name, 102 name = name,
57 rtcTrack = track, 103 rtcTrack = track,
  104 + peerConnectionFactory = peerConnectionFactory,
  105 + context = context,
  106 + eglBase = rootEglBase,
58 ) 107 )
59 } 108 }
60 109
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 -import livekit.LivekitModels  
4 import org.webrtc.VideoSink 3 import org.webrtc.VideoSink
5 import org.webrtc.VideoTrack 4 import org.webrtc.VideoTrack
6 5
7 open class VideoTrack(name: String, override val rtcTrack: VideoTrack) : 6 open class VideoTrack(name: String, override val rtcTrack: VideoTrack) :
8 Track(name, Kind.VIDEO, rtcTrack) { 7 Track(name, Kind.VIDEO, rtcTrack) {
9 - private val sinks: MutableList<VideoSink> = ArrayList(); 8 + internal val sinks: MutableList<VideoSink> = ArrayList();
10 9
11 var enabled: Boolean 10 var enabled: Boolean
12 get() = rtcTrack.enabled() 11 get() = rtcTrack.enabled()
@@ -12,8 +12,10 @@ import io.livekit.android.room.Room @@ -12,8 +12,10 @@ import io.livekit.android.room.Room
12 import io.livekit.android.room.RoomListener 12 import io.livekit.android.room.RoomListener
13 import io.livekit.android.room.participant.Participant 13 import io.livekit.android.room.participant.Participant
14 import io.livekit.android.room.participant.RemoteParticipant 14 import io.livekit.android.room.participant.RemoteParticipant
  15 +import io.livekit.android.room.track.CameraPosition
15 import io.livekit.android.room.track.LocalAudioTrack 16 import io.livekit.android.room.track.LocalAudioTrack
16 import io.livekit.android.room.track.LocalVideoTrack 17 import io.livekit.android.room.track.LocalVideoTrack
  18 +import io.livekit.android.room.track.LocalVideoTrackOptions
17 import kotlinx.coroutines.launch 19 import kotlinx.coroutines.launch
18 20
19 class CallViewModel( 21 class CallViewModel(
@@ -118,7 +120,19 @@ class CallViewModel( @@ -118,7 +120,19 @@ class CallViewModel(
118 } 120 }
119 121
120 fun flipVideo() { 122 fun flipVideo() {
121 - // TODO 123 + room.value?.localParticipant?.let { participant ->
  124 + val videoTrack = participant.videoTracks.values
  125 + .firstOrNull()
  126 + ?.track as? LocalVideoTrack
  127 + ?: return@let
  128 +
  129 + val newOptions = when (videoTrack.options.position) {
  130 + CameraPosition.FRONT -> LocalVideoTrackOptions(position = CameraPosition.BACK)
  131 + CameraPosition.BACK -> LocalVideoTrackOptions(position = CameraPosition.FRONT)
  132 + }
  133 +
  134 + videoTrack.restartTrack(newOptions)
  135 + }
122 } 136 }
123 } 137 }
124 138
@@ -4,7 +4,6 @@ import android.Manifest @@ -4,7 +4,6 @@ import android.Manifest
4 import android.content.Intent 4 import android.content.Intent
5 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
6 import android.os.Bundle 6 import android.os.Bundle
7 -import android.text.SpannableStringBuilder  
8 import android.widget.Toast 7 import android.widget.Toast
9 import androidx.activity.ComponentActivity 8 import androidx.activity.ComponentActivity
10 import androidx.activity.compose.setContent 9 import androidx.activity.compose.setContent