davidliu
Committed by GitHub

android higher level track apis (#22)

* connect options and publish defaults

* default capture options

* more higher level track apis

* selecting camera by deviceId

* fix tests
正在显示 20 个修改的文件 包含 516 行增加94 行删除
1 package io.livekit.android 1 package io.livekit.android
2 2
  3 +import io.livekit.android.room.participant.AudioTrackPublishDefaults
  4 +import io.livekit.android.room.participant.VideoTrackPublishDefaults
  5 +import io.livekit.android.room.track.LocalAudioTrackOptions
  6 +import io.livekit.android.room.track.LocalVideoTrackOptions
  7 +import org.webrtc.PeerConnection
3 8
4 -class ConnectOptions(  
5 - var autoSubscribe: Boolean = true 9 +
  10 +data class ConnectOptions(
  11 + val autoSubscribe: Boolean = true,
  12 + val iceServers: List<PeerConnection.IceServer>? = null,
  13 + val rtcConfig: PeerConnection.RTCConfiguration? = null,
  14 + /**
  15 + * capture and publish audio track on connect, defaults to false
  16 + */
  17 + val audio: Boolean = false,
  18 + /**
  19 + * capture and publish video track on connect, defaults to false
  20 + */
  21 + val video: Boolean = false,
  22 +
  23 + val audioTrackCaptureDefaults: LocalAudioTrackOptions? = null,
  24 + val videoTrackCaptureDefaults: LocalVideoTrackOptions? = null,
  25 + val audioTrackPublishDefaults: AudioTrackPublishDefaults? = null,
  26 + val videoTrackPublishDefaults: VideoTrackPublishDefaults? = null,
6 ) { 27 ) {
7 internal var reconnect: Boolean = false 28 internal var reconnect: Boolean = false
8 } 29 }
@@ -48,6 +48,28 @@ class LiveKit { @@ -48,6 +48,28 @@ class LiveKit {
48 room.listener = listener 48 room.listener = listener
49 room.connect(url, token, options) 49 room.connect(url, token, options)
50 50
  51 + options?.audioTrackCaptureDefaults?.let {
  52 + room.localParticipant.audioTrackCaptureDefaults = it
  53 + }
  54 + options?.videoTrackCaptureDefaults?.let {
  55 + room.localParticipant.videoTrackCaptureDefaults = it
  56 + }
  57 +
  58 + options?.audioTrackPublishDefaults?.let {
  59 + room.localParticipant.audioTrackPublishDefaults = it
  60 + }
  61 + options?.videoTrackPublishDefaults?.let {
  62 + room.localParticipant.videoTrackPublishDefaults = it
  63 + }
  64 +
  65 + if (options?.audio == true) {
  66 + val audioTrack = room.localParticipant.createAudioTrack()
  67 + room.localParticipant.publishAudioTrack(audioTrack)
  68 + }
  69 + if (options?.video == true) {
  70 + val videoTrack = room.localParticipant.createVideoTrack()
  71 + room.localParticipant.publishVideoTrack(videoTrack)
  72 + }
51 return room 73 return room
52 } 74 }
53 75
  1 +package io.livekit.android.room
  2 +
  3 +import io.livekit.android.room.participant.AudioTrackPublishDefaults
  4 +import io.livekit.android.room.participant.VideoTrackPublishDefaults
  5 +import io.livekit.android.room.track.LocalAudioTrackOptions
  6 +import io.livekit.android.room.track.LocalVideoTrackOptions
  7 +import javax.inject.Inject
  8 +import javax.inject.Singleton
  9 +
  10 +@Singleton
  11 +class DefaultsManager
  12 +@Inject
  13 +constructor() {
  14 + var audioTrackCaptureDefaults: LocalAudioTrackOptions = LocalAudioTrackOptions()
  15 + var audioTrackPublishDefaults: AudioTrackPublishDefaults = AudioTrackPublishDefaults()
  16 + var videoTrackCaptureDefaults: LocalVideoTrackOptions = LocalVideoTrackOptions()
  17 + var videoTrackPublishDefaults: VideoTrackPublishDefaults = VideoTrackPublishDefaults()
  18 +}
  1 +package io.livekit.android.room
  2 +
  3 +import android.content.Context
  4 +import android.hardware.camera2.CameraManager
  5 +import android.os.Handler
  6 +import android.os.Looper
  7 +import org.webrtc.Camera1Enumerator
  8 +import org.webrtc.Camera2Enumerator
  9 +
  10 +object DeviceManager {
  11 +
  12 + enum class Kind {
  13 + // Only camera input currently, audio input/output only has one option atm.
  14 + CAMERA;
  15 + }
  16 +
  17 + private val defaultDevices = mutableMapOf<Kind, String>()
  18 + private val listeners =
  19 + mutableMapOf<Kind, MutableList<OnDeviceAvailabilityChangeListener>>()
  20 +
  21 + private var hasSetupListeners = false
  22 +
  23 + @Synchronized
  24 + internal fun setupListenersIfNeeded(context: Context) {
  25 + if (hasSetupListeners) {
  26 + return
  27 + }
  28 +
  29 + hasSetupListeners = true
  30 + val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
  31 + cameraManager.registerAvailabilityCallback(object : CameraManager.AvailabilityCallback() {
  32 + override fun onCameraAvailable(cameraId: String) {
  33 + notifyListeners(Kind.CAMERA)
  34 + }
  35 +
  36 + override fun onCameraUnavailable(cameraId: String) {
  37 + notifyListeners(Kind.CAMERA)
  38 + }
  39 +
  40 + override fun onCameraAccessPrioritiesChanged() {
  41 + notifyListeners(Kind.CAMERA)
  42 + }
  43 + }, Handler(Looper.getMainLooper()))
  44 + }
  45 +
  46 + fun getDefaultDevice(kind: Kind): String? {
  47 + return defaultDevices[kind]
  48 + }
  49 +
  50 + fun setDefaultDevice(kind: Kind, deviceId: String?) {
  51 + if (deviceId != null) {
  52 + defaultDevices[kind] = deviceId
  53 + } else {
  54 + defaultDevices.remove(kind)
  55 + }
  56 + }
  57 +
  58 + /**
  59 + * @return the list of device ids for [kind]
  60 + */
  61 + fun getDevices(context: Context, kind: Kind): List<String> {
  62 + return when (kind) {
  63 + Kind.CAMERA -> {
  64 + val cameraEnumerator = if (Camera2Enumerator.isSupported(context)) {
  65 + Camera2Enumerator(context)
  66 + } else {
  67 + Camera1Enumerator()
  68 + }
  69 + cameraEnumerator.deviceNames.toList()
  70 + }
  71 + }
  72 + }
  73 +
  74 + fun registerOnDeviceAvailabilityChange(
  75 + kind: Kind,
  76 + listener: OnDeviceAvailabilityChangeListener
  77 + ) {
  78 + if (listeners[kind] == null) {
  79 + listeners[kind] = mutableListOf()
  80 + }
  81 + listeners[kind]!!.add(listener)
  82 + }
  83 +
  84 + fun unregisterOnDeviceAvailabilityChange(
  85 + kind: Kind,
  86 + listener: OnDeviceAvailabilityChangeListener
  87 + ) {
  88 + listeners[kind]?.remove(listener)
  89 + }
  90 +
  91 + private fun notifyListeners(kind: Kind) {
  92 + listeners[kind]?.forEach {
  93 + it.onDeviceAvailabilityChanged(kind)
  94 + }
  95 + }
  96 +
  97 + interface OnDeviceAvailabilityChangeListener {
  98 + fun onDeviceAvailabilityChanged(kind: Kind)
  99 + }
  100 +}
@@ -3,10 +3,7 @@ package io.livekit.android.room @@ -3,10 +3,7 @@ package io.livekit.android.room
3 import android.os.SystemClock 3 import android.os.SystemClock
4 import io.livekit.android.ConnectOptions 4 import io.livekit.android.ConnectOptions
5 import io.livekit.android.dagger.InjectionNames 5 import io.livekit.android.dagger.InjectionNames
6 -import io.livekit.android.room.track.DataPublishReliability  
7 -import io.livekit.android.room.track.Track  
8 import io.livekit.android.room.track.TrackException 6 import io.livekit.android.room.track.TrackException
9 -import io.livekit.android.room.track.TrackPublication  
10 import io.livekit.android.room.util.* 7 import io.livekit.android.room.util.*
11 import io.livekit.android.util.CloseableCoroutineScope 8 import io.livekit.android.util.CloseableCoroutineScope
12 import io.livekit.android.util.Either 9 import io.livekit.android.util.Either
@@ -21,7 +18,6 @@ import livekit.LivekitRtc @@ -21,7 +18,6 @@ import livekit.LivekitRtc
21 import org.webrtc.* 18 import org.webrtc.*
22 import java.net.ConnectException 19 import java.net.ConnectException
23 import java.nio.ByteBuffer 20 import java.nio.ByteBuffer
24 -import java.util.concurrent.TimeUnit  
25 import javax.inject.Inject 21 import javax.inject.Inject
26 import javax.inject.Named 22 import javax.inject.Named
27 import javax.inject.Singleton 23 import javax.inject.Singleton
@@ -101,7 +97,7 @@ internal constructor( @@ -101,7 +97,7 @@ internal constructor(
101 isSubscriberPrimary = joinResponse.subscriberPrimary 97 isSubscriberPrimary = joinResponse.subscriberPrimary
102 98
103 if (!this::publisher.isInitialized) { 99 if (!this::publisher.isInitialized) {
104 - configure(joinResponse) 100 + configure(joinResponse, options)
105 } 101 }
106 // create offer 102 // create offer
107 if (!this.isSubscriberPrimary) { 103 if (!this.isSubscriberPrimary) {
@@ -111,18 +107,21 @@ internal constructor( @@ -111,18 +107,21 @@ internal constructor(
111 return joinResponse 107 return joinResponse
112 } 108 }
113 109
114 - private fun configure(joinResponse: LivekitRtc.JoinResponse) { 110 + private fun configure(joinResponse: LivekitRtc.JoinResponse, connectOptions: ConnectOptions?) {
115 if (this::publisher.isInitialized || this::subscriber.isInitialized) { 111 if (this::publisher.isInitialized || this::subscriber.isInitialized) {
116 // already configured 112 // already configured
117 return 113 return
118 } 114 }
119 115
120 // update ICE servers before creating PeerConnection 116 // update ICE servers before creating PeerConnection
121 - val iceServers = mutableListOf<PeerConnection.IceServer>() 117 + val iceServers = if (connectOptions?.iceServers != null) {
  118 + connectOptions.iceServers
  119 + } else {
  120 + val servers = mutableListOf<PeerConnection.IceServer>()
122 for (serverInfo in joinResponse.iceServersList) { 121 for (serverInfo in joinResponse.iceServersList) {
123 val username = serverInfo.username ?: "" 122 val username = serverInfo.username ?: ""
124 val credential = serverInfo.credential ?: "" 123 val credential = serverInfo.credential ?: ""
125 - iceServers.add( 124 + servers.add(
126 PeerConnection.IceServer 125 PeerConnection.IceServer
127 .builder(serverInfo.urlsList) 126 .builder(serverInfo.urlsList)
128 .setUsername(username) 127 .setUsername(username)
@@ -131,25 +130,29 @@ internal constructor( @@ -131,25 +130,29 @@ internal constructor(
131 ) 130 )
132 } 131 }
133 132
134 - if (iceServers.isEmpty()) {  
135 - iceServers.addAll(SignalClient.DEFAULT_ICE_SERVERS)  
136 - }  
137 - joinResponse.iceServersList.forEach {  
138 - LKLog.v { "username = \"${it.username}\"" }  
139 - LKLog.v { "credential = \"${it.credential}\"" }  
140 - LKLog.v { "urls: " }  
141 - it.urlsList.forEach {  
142 - LKLog.v { " $it" } 133 + if (servers.isEmpty()) {
  134 + servers.addAll(SignalClient.DEFAULT_ICE_SERVERS)
143 } 135 }
  136 + servers
144 } 137 }
145 138
146 // Setup peer connections 139 // Setup peer connections
147 - val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply { 140 + val rtcConfig = connectOptions?.rtcConfig?.apply {
  141 + val mergedServers = this.iceServers.toMutableList()
  142 + iceServers.forEach { server ->
  143 + if (!mergedServers.contains(server)) {
  144 + mergedServers.add(server)
  145 + }
  146 + }
  147 + }
  148 + ?: PeerConnection.RTCConfiguration(iceServers).apply {
148 sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN 149 sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN
149 - continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY 150 + continualGatheringPolicy =
  151 + PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
150 enableDtlsSrtp = true 152 enableDtlsSrtp = true
151 } 153 }
152 154
  155 +
153 publisher = pctFactory.create( 156 publisher = pctFactory.create(
154 rtcConfig, 157 rtcConfig,
155 publisherObserver, 158 publisherObserver,
@@ -24,7 +24,8 @@ constructor( @@ -24,7 +24,8 @@ constructor(
24 @Assisted private val context: Context, 24 @Assisted private val context: Context,
25 private val engine: RTCEngine, 25 private val engine: RTCEngine,
26 private val eglBase: EglBase, 26 private val eglBase: EglBase,
27 - private val localParticipantFactory: LocalParticipant.Factory 27 + private val localParticipantFactory: LocalParticipant.Factory,
  28 + private val defaultsManager: DefaultsManager,
28 ) : RTCEngine.Listener, ParticipantListener, ConnectivityManager.NetworkCallback() { 29 ) : RTCEngine.Listener, ParticipantListener, ConnectivityManager.NetworkCallback() {
29 init { 30 init {
30 engine.listener = this 31 engine.listener = this
@@ -51,6 +52,11 @@ constructor( @@ -51,6 +52,11 @@ constructor(
51 var metadata: String? = null 52 var metadata: String? = null
52 private set 53 private set
53 54
  55 + var audioTrackCaptureDefaults: LocalAudioTrackOptions by defaultsManager::audioTrackCaptureDefaults
  56 + var audioTrackPublishDefaults: AudioTrackPublishDefaults by defaultsManager::audioTrackPublishDefaults
  57 + var videoTrackCaptureDefaults: LocalVideoTrackOptions by defaultsManager::videoTrackCaptureDefaults
  58 + var videoTrackPublishDefaults: VideoTrackPublishDefaults by defaultsManager::videoTrackPublishDefaults
  59 +
54 lateinit var localParticipant: LocalParticipant 60 lateinit var localParticipant: LocalParticipant
55 private set 61 private set
56 private val mutableRemoteParticipants = mutableMapOf<String, RemoteParticipant>() 62 private val mutableRemoteParticipants = mutableMapOf<String, RemoteParticipant>()
@@ -583,6 +589,16 @@ interface RoomListener { @@ -583,6 +589,16 @@ interface RoomListener {
583 * @param quality the new connection quality 589 * @param quality the new connection quality
584 */ 590 */
585 fun onConnectionQualityChanged(participant: Participant, quality: ConnectionQuality) {} 591 fun onConnectionQualityChanged(participant: Participant, quality: ConnectionQuality) {}
  592 +
  593 + companion object {
  594 + fun getDefaultDevice(kind: DeviceManager.Kind): String? {
  595 + return DeviceManager.getDefaultDevice(kind)
  596 + }
  597 +
  598 + fun setDefaultDevice(kind: DeviceManager.Kind, deviceId: String?) {
  599 + DeviceManager.setDefaultDevice(kind, deviceId)
  600 + }
  601 + }
586 } 602 }
587 603
588 sealed class RoomException(message: String? = null, cause: Throwable? = null) : 604 sealed class RoomException(message: String? = null, cause: Throwable? = null) :
@@ -7,12 +7,16 @@ import com.google.protobuf.ByteString @@ -7,12 +7,16 @@ import com.google.protobuf.ByteString
7 import dagger.assisted.Assisted 7 import dagger.assisted.Assisted
8 import dagger.assisted.AssistedFactory 8 import dagger.assisted.AssistedFactory
9 import dagger.assisted.AssistedInject 9 import dagger.assisted.AssistedInject
  10 +import io.livekit.android.room.DefaultsManager
10 import io.livekit.android.room.RTCEngine 11 import io.livekit.android.room.RTCEngine
11 import io.livekit.android.room.track.* 12 import io.livekit.android.room.track.*
12 import io.livekit.android.util.LKLog 13 import io.livekit.android.util.LKLog
13 import livekit.LivekitModels 14 import livekit.LivekitModels
14 import livekit.LivekitRtc 15 import livekit.LivekitRtc
15 -import org.webrtc.* 16 +import org.webrtc.EglBase
  17 +import org.webrtc.PeerConnectionFactory
  18 +import org.webrtc.RtpParameters
  19 +import org.webrtc.RtpTransceiver
16 import kotlin.math.abs 20 import kotlin.math.abs
17 import kotlin.math.roundToInt 21 import kotlin.math.roundToInt
18 22
@@ -26,8 +30,14 @@ internal constructor( @@ -26,8 +30,14 @@ internal constructor(
26 private val context: Context, 30 private val context: Context,
27 private val eglBase: EglBase, 31 private val eglBase: EglBase,
28 private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory, 32 private val screencastVideoTrackFactory: LocalScreencastVideoTrack.Factory,
29 -) :  
30 - Participant(info.sid, info.identity) { 33 + private val videoTrackFactory: LocalVideoTrack.Factory,
  34 + private val defaultsManager: DefaultsManager
  35 +) : Participant(info.sid, info.identity) {
  36 +
  37 + var audioTrackCaptureDefaults: LocalAudioTrackOptions by defaultsManager::audioTrackCaptureDefaults
  38 + var audioTrackPublishDefaults: AudioTrackPublishDefaults by defaultsManager::audioTrackPublishDefaults
  39 + var videoTrackCaptureDefaults: LocalVideoTrackOptions by defaultsManager::videoTrackCaptureDefaults
  40 + var videoTrackPublishDefaults: VideoTrackPublishDefaults by defaultsManager::videoTrackPublishDefaults
31 41
32 init { 42 init {
33 updateFromInfo(info) 43 updateFromInfo(info)
@@ -45,18 +55,9 @@ internal constructor( @@ -45,18 +55,9 @@ internal constructor(
45 */ 55 */
46 fun createAudioTrack( 56 fun createAudioTrack(
47 name: String = "", 57 name: String = "",
48 - options: LocalAudioTrackOptions = LocalAudioTrackOptions(), 58 + options: LocalAudioTrackOptions = audioTrackCaptureDefaults,
49 ): LocalAudioTrack { 59 ): LocalAudioTrack {
50 - val audioConstraints = MediaConstraints()  
51 - val items = listOf(  
52 - MediaConstraints.KeyValuePair("googEchoCancellation", options.echoCancellation.toString()),  
53 - MediaConstraints.KeyValuePair("googAutoGainControl", options.autoGainControl.toString()),  
54 - MediaConstraints.KeyValuePair("googHighpassFilter", options.highPassFilter.toString()),  
55 - MediaConstraints.KeyValuePair("googNoiseSuppression", options.noiseSuppression.toString()),  
56 - MediaConstraints.KeyValuePair("googTypingNoiseDetection", options.typingNoiseDetection.toString()),  
57 - )  
58 - audioConstraints.optional.addAll(items)  
59 - return LocalAudioTrack.createTrack(context, peerConnectionFactory, audioConstraints, name) 60 + return LocalAudioTrack.createTrack(context, peerConnectionFactory, options, name)
60 } 61 }
61 62
62 /** 63 /**
@@ -66,14 +67,15 @@ internal constructor( @@ -66,14 +67,15 @@ internal constructor(
66 */ 67 */
67 fun createVideoTrack( 68 fun createVideoTrack(
68 name: String = "", 69 name: String = "",
69 - options: LocalVideoTrackOptions = LocalVideoTrackOptions(), 70 + options: LocalVideoTrackOptions = videoTrackCaptureDefaults.copy(),
70 ): LocalVideoTrack { 71 ): LocalVideoTrack {
71 return LocalVideoTrack.createTrack( 72 return LocalVideoTrack.createTrack(
72 peerConnectionFactory, 73 peerConnectionFactory,
73 context, 74 context,
74 name, 75 name,
75 options, 76 options,
76 - eglBase 77 + eglBase,
  78 + videoTrackFactory,
77 ) 79 )
78 } 80 }
79 81
@@ -98,9 +100,62 @@ internal constructor( @@ -98,9 +100,62 @@ internal constructor(
98 ) 100 )
99 } 101 }
100 102
  103 + override fun getTrackPublication(source: Track.Source): LocalTrackPublication? {
  104 + return super.getTrackPublication(source) as? LocalTrackPublication
  105 + }
  106 +
  107 + override fun getTrackPublicationByName(name: String): LocalTrackPublication? {
  108 + return super.getTrackPublicationByName(name) as? LocalTrackPublication
  109 + }
  110 +
  111 + private suspend fun setTrackEnabled(
  112 + source: Track.Source,
  113 + enabled: Boolean,
  114 + mediaProjectionPermissionResultData: Intent? = null
  115 +
  116 + ) {
  117 + val pub = getTrackPublication(source)
  118 + if (enabled) {
  119 + if (pub != null) {
  120 + pub.muted = false
  121 + } else {
  122 + when (source) {
  123 + Track.Source.CAMERA -> {
  124 + val track = createVideoTrack()
  125 + publishVideoTrack(track)
  126 + }
  127 + Track.Source.MICROPHONE -> {
  128 + val track = createAudioTrack()
  129 + publishAudioTrack(track)
  130 + }
  131 + Track.Source.SCREEN_SHARE -> {
  132 + if (mediaProjectionPermissionResultData == null) {
  133 + throw IllegalArgumentException("Media Projection permission result data is required to create a screen share track.")
  134 + }
  135 + val track =
  136 + createScreencastTrack(mediaProjectionPermissionResultData = mediaProjectionPermissionResultData)
  137 + publishVideoTrack(track)
  138 + }
  139 + }
  140 + }
  141 + } else {
  142 + pub?.track?.let { track ->
  143 + // screenshare cannot be muted, unpublish instead
  144 + if (pub.source == Track.Source.SCREEN_SHARE) {
  145 + unpublishTrack(track)
  146 + } else {
  147 + pub.muted = true
  148 + }
  149 + }
  150 + }
  151 + }
  152 +
101 suspend fun publishAudioTrack( 153 suspend fun publishAudioTrack(
102 track: LocalAudioTrack, 154 track: LocalAudioTrack,
103 - options: AudioTrackPublishOptions = AudioTrackPublishOptions(), 155 + options: AudioTrackPublishOptions = AudioTrackPublishOptions(
  156 + null,
  157 + audioTrackPublishDefaults
  158 + ),
104 publishListener: PublishListener? = null 159 publishListener: PublishListener? = null
105 ) { 160 ) {
106 if (localTrackPublications.any { it.track == track }) { 161 if (localTrackPublications.any { it.track == track }) {
@@ -140,7 +195,7 @@ internal constructor( @@ -140,7 +195,7 @@ internal constructor(
140 195
141 suspend fun publishVideoTrack( 196 suspend fun publishVideoTrack(
142 track: LocalVideoTrack, 197 track: LocalVideoTrack,
143 - options: VideoTrackPublishOptions = VideoTrackPublishOptions(), 198 + options: VideoTrackPublishOptions = VideoTrackPublishOptions(null, videoTrackPublishDefaults),
144 publishListener: PublishListener? = null 199 publishListener: PublishListener? = null
145 ) { 200 ) {
146 if (localTrackPublications.any { it.track == track }) { 201 if (localTrackPublications.any { it.track == track }) {
@@ -339,7 +394,7 @@ internal constructor( @@ -339,7 +394,7 @@ internal constructor(
339 for (ti in info.tracksList) { 394 for (ti in info.tracksList) {
340 val publication = this.tracks[ti.sid] as? LocalTrackPublication ?: continue 395 val publication = this.tracks[ti.sid] as? LocalTrackPublication ?: continue
341 if (ti.muted != publication.muted) { 396 if (ti.muted != publication.muted) {
342 - publication.setMuted(ti.muted) 397 + publication.muted = ti.muted
343 } 398 }
344 } 399 }
345 } 400 }
@@ -382,15 +437,53 @@ interface TrackPublishOptions { @@ -382,15 +437,53 @@ interface TrackPublishOptions {
382 val name: String? 437 val name: String?
383 } 438 }
384 439
  440 +abstract class BaseVideoTrackPublishOptions {
  441 + abstract val videoEncoding: VideoEncoding?
  442 + abstract val simulcast: Boolean
  443 + //val videoCodec: VideoCodec? = null,
  444 +}
  445 +
  446 +data class VideoTrackPublishDefaults(
  447 + override val videoEncoding: VideoEncoding? = null,
  448 + override val simulcast: Boolean = false
  449 +) : BaseVideoTrackPublishOptions()
  450 +
385 data class VideoTrackPublishOptions( 451 data class VideoTrackPublishOptions(
386 override val name: String? = null, 452 override val name: String? = null,
387 - val videoEncoding: VideoEncoding? = null,  
388 - //val videoCodec: VideoCodec? = null,  
389 - val simulcast: Boolean = false  
390 -) : TrackPublishOptions 453 + override val videoEncoding: VideoEncoding? = null,
  454 + override val simulcast: Boolean = false
  455 +) : BaseVideoTrackPublishOptions(), TrackPublishOptions {
  456 + constructor(
  457 + name: String? = null,
  458 + base: BaseVideoTrackPublishOptions
  459 + ) : this(
  460 + name,
  461 + base.videoEncoding,
  462 + base.simulcast
  463 + )
  464 +}
  465 +
  466 +abstract class BaseAudioTrackPublishOptions {
  467 + abstract val audioBitrate: Int?
  468 + abstract val dtx: Boolean
  469 +}
  470 +
  471 +data class AudioTrackPublishDefaults(
  472 + override val audioBitrate: Int? = null,
  473 + override val dtx: Boolean = true
  474 +) : BaseAudioTrackPublishOptions()
391 475
392 data class AudioTrackPublishOptions( 476 data class AudioTrackPublishOptions(
393 override val name: String? = null, 477 override val name: String? = null,
394 - val audioBitrate: Int? = null,  
395 - val dtx: Boolean = true  
396 -) : TrackPublishOptions  
  478 + override val audioBitrate: Int? = null,
  479 + override val dtx: Boolean = true
  480 +) : BaseAudioTrackPublishOptions(), TrackPublishOptions {
  481 + constructor(
  482 + name: String? = null,
  483 + base: BaseAudioTrackPublishOptions
  484 + ) : this(
  485 + name,
  486 + base.audioBitrate,
  487 + base.dtx
  488 + )
  489 +}
@@ -63,9 +63,51 @@ open class Participant(var sid: String, identity: String? = null) { @@ -63,9 +63,51 @@ open class Participant(var sid: String, identity: String? = null) {
63 when (publication.kind) { 63 when (publication.kind) {
64 Track.Kind.AUDIO -> audioTracks[publication.sid] = publication 64 Track.Kind.AUDIO -> audioTracks[publication.sid] = publication
65 Track.Kind.VIDEO -> videoTracks[publication.sid] = publication 65 Track.Kind.VIDEO -> videoTracks[publication.sid] = publication
66 - else -> {} 66 + else -> {
67 } 67 }
68 } 68 }
  69 + }
  70 +
  71 + /**
  72 + * Retrieves the first track that matches the source, or null
  73 + */
  74 + open fun getTrackPublication(source: Track.Source): TrackPublication? {
  75 + if (source == Track.Source.UNKNOWN) {
  76 + return null
  77 + }
  78 +
  79 + for ((_, pub) in tracks) {
  80 + if (pub.source == source) {
  81 + return pub
  82 + }
  83 +
  84 + // Alternative heuristics for finding track if source is unknown
  85 + if (pub.source == Track.Source.UNKNOWN) {
  86 + if (source == Track.Source.MICROPHONE && pub.kind == Track.Kind.AUDIO) {
  87 + return pub
  88 + }
  89 + if (source == Track.Source.CAMERA && pub.kind == Track.Kind.VIDEO && pub.name != "screen") {
  90 + return pub
  91 + }
  92 + if (source == Track.Source.SCREEN_SHARE && pub.kind == Track.Kind.VIDEO && pub.name == "screen") {
  93 + return pub
  94 + }
  95 + }
  96 + }
  97 + return null
  98 + }
  99 +
  100 + /**
  101 + * Retrieves the first track that matches [name], or null
  102 + */
  103 + open fun getTrackPublicationByName(name: String): TrackPublication? {
  104 + for ((_, pub) in tracks) {
  105 + if (pub.name == name) {
  106 + return pub
  107 + }
  108 + }
  109 + return null
  110 + }
69 111
70 /** 112 /**
71 * @suppress 113 * @suppress
@@ -33,7 +33,7 @@ class LocalAudioTrack( @@ -33,7 +33,7 @@ class LocalAudioTrack(
33 internal fun createTrack( 33 internal fun createTrack(
34 context: Context, 34 context: Context,
35 factory: PeerConnectionFactory, 35 factory: PeerConnectionFactory,
36 - audioConstraints: MediaConstraints = MediaConstraints(), 36 + options: LocalAudioTrackOptions = LocalAudioTrackOptions(),
37 name: String = "" 37 name: String = ""
38 ): LocalAudioTrack { 38 ): LocalAudioTrack {
39 if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != 39 if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) !=
@@ -42,6 +42,16 @@ class LocalAudioTrack( @@ -42,6 +42,16 @@ class LocalAudioTrack(
42 throw SecurityException("Record audio permissions are required to create an audio track.") 42 throw SecurityException("Record audio permissions are required to create an audio track.")
43 } 43 }
44 44
  45 + val audioConstraints = MediaConstraints()
  46 + val items = listOf(
  47 + MediaConstraints.KeyValuePair("googEchoCancellation", options.echoCancellation.toString()),
  48 + MediaConstraints.KeyValuePair("googAutoGainControl", options.autoGainControl.toString()),
  49 + MediaConstraints.KeyValuePair("googHighpassFilter", options.highPassFilter.toString()),
  50 + MediaConstraints.KeyValuePair("googNoiseSuppression", options.noiseSuppression.toString()),
  51 + MediaConstraints.KeyValuePair("googTypingNoiseDetection", options.typingNoiseDetection.toString()),
  52 + )
  53 + audioConstraints.optional.addAll(items)
  54 +
45 val audioSource = factory.createAudioSource(audioConstraints) 55 val audioSource = factory.createAudioSource(audioConstraints)
46 val rtcAudioTrack = 56 val rtcAudioTrack =
47 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource) 57 factory.createAudioTrack(UUID.randomUUID().toString(), audioSource)
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 -class LocalAudioTrackOptions(  
4 - var noiseSuppression: Boolean = true,  
5 - var echoCancellation: Boolean = true,  
6 - var autoGainControl: Boolean = true,  
7 - var highPassFilter: Boolean = true,  
8 - var typingNoiseDetection: Boolean = true, 3 +data class LocalAudioTrackOptions(
  4 + val noiseSuppression: Boolean = true,
  5 + val echoCancellation: Boolean = true,
  6 + val autoGainControl: Boolean = true,
  7 + val highPassFilter: Boolean = true,
  8 + val typingNoiseDetection: Boolean = true,
9 ) 9 )
@@ -7,6 +7,7 @@ import android.media.projection.MediaProjection @@ -7,6 +7,7 @@ import android.media.projection.MediaProjection
7 import dagger.assisted.Assisted 7 import dagger.assisted.Assisted
8 import dagger.assisted.AssistedFactory 8 import dagger.assisted.AssistedFactory
9 import dagger.assisted.AssistedInject 9 import dagger.assisted.AssistedInject
  10 +import io.livekit.android.room.DefaultsManager
10 import io.livekit.android.room.track.screencapture.ScreenCaptureConnection 11 import io.livekit.android.room.track.screencapture.ScreenCaptureConnection
11 import org.webrtc.* 12 import org.webrtc.*
12 import java.util.* 13 import java.util.*
@@ -23,6 +24,8 @@ constructor( @@ -23,6 +24,8 @@ constructor(
23 peerConnectionFactory: PeerConnectionFactory, 24 peerConnectionFactory: PeerConnectionFactory,
24 context: Context, 25 context: Context,
25 eglBase: EglBase, 26 eglBase: EglBase,
  27 + defaultsManager: DefaultsManager,
  28 + videoTrackFactory: LocalVideoTrack.Factory,
26 ) : LocalVideoTrack( 29 ) : LocalVideoTrack(
27 capturer, 30 capturer,
28 source, 31 source,
@@ -31,7 +34,9 @@ constructor( @@ -31,7 +34,9 @@ constructor(
31 rtcTrack, 34 rtcTrack,
32 peerConnectionFactory, 35 peerConnectionFactory,
33 context, 36 context,
34 - eglBase 37 + eglBase,
  38 + defaultsManager,
  39 + videoTrackFactory
35 ) { 40 ) {
36 41
37 private val serviceConnection = ScreenCaptureConnection(context) 42 private val serviceConnection = ScreenCaptureConnection(context)
@@ -13,7 +13,9 @@ class LocalTrackPublication( @@ -13,7 +13,9 @@ class LocalTrackPublication(
13 * Mute or unmute the current track. Muting the track would stop audio or video from being 13 * Mute or unmute the current track. Muting the track would stop audio or video from being
14 * transmitted to the server, and notify other participants in the room. 14 * transmitted to the server, and notify other participants in the room.
15 */ 15 */
16 - fun setMuted(muted: Boolean) { 16 + override var muted: Boolean
  17 + get() = super.muted
  18 + set(muted) {
17 if (muted == this.muted) { 19 if (muted == this.muted) {
18 return 20 return
19 } 21 }
@@ -21,7 +23,7 @@ class LocalTrackPublication( @@ -21,7 +23,7 @@ class LocalTrackPublication(
21 val mediaTrack = track ?: return 23 val mediaTrack = track ?: return
22 24
23 mediaTrack.rtcTrack.setEnabled(!muted) 25 mediaTrack.rtcTrack.setEnabled(!muted)
24 - this.muted = muted 26 + super.muted = muted
25 27
26 // send updates to server 28 // send updates to server
27 val participant = this.participant.get() as? LocalParticipant ?: return 29 val participant = this.participant.get() as? LocalParticipant ?: return
1 package io.livekit.android.room.track 1 package io.livekit.android.room.track
2 2
3 -data class LocalTrackPublicationOptions(val placeholder: Unit)  
4 -  
5 enum class DataPublishReliability { 3 enum class DataPublishReliability {
6 RELIABLE, 4 RELIABLE,
7 LOSSY, 5 LOSSY,
@@ -5,6 +5,10 @@ import android.content.Context @@ -5,6 +5,10 @@ import android.content.Context
5 import android.content.pm.PackageManager 5 import android.content.pm.PackageManager
6 import android.hardware.camera2.CameraManager 6 import android.hardware.camera2.CameraManager
7 import androidx.core.content.ContextCompat 7 import androidx.core.content.ContextCompat
  8 +import dagger.assisted.Assisted
  9 +import dagger.assisted.AssistedFactory
  10 +import dagger.assisted.AssistedInject
  11 +import io.livekit.android.room.DefaultsManager
8 import io.livekit.android.room.track.video.Camera1CapturerWithSize 12 import io.livekit.android.room.track.video.Camera1CapturerWithSize
9 import io.livekit.android.room.track.video.Camera2CapturerWithSize 13 import io.livekit.android.room.track.video.Camera2CapturerWithSize
10 import io.livekit.android.room.track.video.VideoCapturerWithSize 14 import io.livekit.android.room.track.video.VideoCapturerWithSize
@@ -18,15 +22,19 @@ import java.util.* @@ -18,15 +22,19 @@ import java.util.*
18 * 22 *
19 * [startCapture] should be called before use. 23 * [startCapture] should be called before use.
20 */ 24 */
21 -open class LocalVideoTrack(  
22 - private var capturer: VideoCapturer,  
23 - private var source: VideoSource,  
24 - name: String,  
25 - var options: LocalVideoTrackOptions,  
26 - rtcTrack: org.webrtc.VideoTrack, 25 +open class LocalVideoTrack
  26 +@AssistedInject
  27 +constructor(
  28 + @Assisted private var capturer: VideoCapturer,
  29 + @Assisted private var source: VideoSource,
  30 + @Assisted name: String,
  31 + @Assisted var options: LocalVideoTrackOptions,
  32 + @Assisted rtcTrack: org.webrtc.VideoTrack,
27 private val peerConnectionFactory: PeerConnectionFactory, 33 private val peerConnectionFactory: PeerConnectionFactory,
28 private val context: Context, 34 private val context: Context,
29 private val eglBase: EglBase, 35 private val eglBase: EglBase,
  36 + private val defaultsManager: DefaultsManager,
  37 + private val trackFactory: Factory,
30 ) : VideoTrack(name, rtcTrack) { 38 ) : VideoTrack(name, rtcTrack) {
31 39
32 override var rtcTrack: org.webrtc.VideoTrack = rtcTrack 40 override var rtcTrack: org.webrtc.VideoTrack = rtcTrack
@@ -61,13 +69,18 @@ open class LocalVideoTrack( @@ -61,13 +69,18 @@ open class LocalVideoTrack(
61 super.stop() 69 super.stop()
62 } 70 }
63 71
64 - fun restartTrack(options: LocalVideoTrackOptions = LocalVideoTrackOptions()) { 72 + fun setDeviceId(deviceId: String) {
  73 + restartTrack(options.copy(deviceId = deviceId))
  74 + }
  75 +
  76 + fun restartTrack(options: LocalVideoTrackOptions = defaultsManager.videoTrackCaptureDefaults.copy()) {
65 val newTrack = createTrack( 77 val newTrack = createTrack(
66 peerConnectionFactory, 78 peerConnectionFactory,
67 context, 79 context,
68 name, 80 name,
69 options, 81 options,
70 - eglBase 82 + eglBase,
  83 + trackFactory
71 ) 84 )
72 85
73 val oldCapturer = capturer 86 val oldCapturer = capturer
@@ -95,6 +108,17 @@ open class LocalVideoTrack( @@ -95,6 +108,17 @@ open class LocalVideoTrack(
95 sender?.setTrack(newTrack.rtcTrack, true) 108 sender?.setTrack(newTrack.rtcTrack, true)
96 } 109 }
97 110
  111 + @AssistedFactory
  112 + interface Factory {
  113 + fun create(
  114 + capturer: VideoCapturer,
  115 + source: VideoSource,
  116 + name: String,
  117 + options: LocalVideoTrackOptions,
  118 + rtcTrack: org.webrtc.VideoTrack,
  119 + ): LocalVideoTrack
  120 + }
  121 +
98 companion object { 122 companion object {
99 123
100 internal fun createTrack( 124 internal fun createTrack(
@@ -103,6 +127,7 @@ open class LocalVideoTrack( @@ -103,6 +127,7 @@ open class LocalVideoTrack(
103 name: String, 127 name: String,
104 options: LocalVideoTrackOptions, 128 options: LocalVideoTrackOptions,
105 rootEglBase: EglBase, 129 rootEglBase: EglBase,
  130 + trackFactory: Factory
106 ): LocalVideoTrack { 131 ): LocalVideoTrack {
107 132
108 if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != 133 if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
@@ -112,7 +137,7 @@ open class LocalVideoTrack( @@ -112,7 +137,7 @@ open class LocalVideoTrack(
112 } 137 }
113 138
114 val source = peerConnectionFactory.createVideoSource(options.isScreencast) 139 val source = peerConnectionFactory.createVideoSource(options.isScreencast)
115 - val capturer = createVideoCapturer(context, options.position) ?: TODO() 140 + val (capturer, newOptions) = createVideoCapturer(context, options) ?: TODO()
116 capturer.initialize( 141 capturer.initialize(
117 SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext), 142 SurfaceTextureHelper.create("VideoCaptureThread", rootEglBase.eglBaseContext),
118 context, 143 context,
@@ -120,41 +145,44 @@ open class LocalVideoTrack( @@ -120,41 +145,44 @@ open class LocalVideoTrack(
120 ) 145 )
121 val track = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source) 146 val track = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source)
122 147
123 - return LocalVideoTrack( 148 + return trackFactory.create(
124 capturer = capturer, 149 capturer = capturer,
125 source = source, 150 source = source,
126 - options = options, 151 + options = newOptions,
127 name = name, 152 name = name,
128 - rtcTrack = track,  
129 - peerConnectionFactory = peerConnectionFactory,  
130 - context = context,  
131 - eglBase = rootEglBase, 153 + rtcTrack = track
132 ) 154 )
133 } 155 }
134 156
135 - private fun createVideoCapturer(context: Context, position: CameraPosition): VideoCapturer? {  
136 - val videoCapturer: VideoCapturer? = if (Camera2Enumerator.isSupported(context)) {  
137 - createCameraCapturer(context, Camera2Enumerator(context), position) 157 + private fun createVideoCapturer(
  158 + context: Context,
  159 + options: LocalVideoTrackOptions
  160 + ): Pair<VideoCapturer, LocalVideoTrackOptions>? {
  161 + val pair = if (Camera2Enumerator.isSupported(context)) {
  162 + createCameraCapturer(context, Camera2Enumerator(context), options)
138 } else { 163 } else {
139 - createCameraCapturer(context, Camera1Enumerator(true), position) 164 + createCameraCapturer(context, Camera1Enumerator(true), options)
140 } 165 }
141 - if (videoCapturer == null) { 166 +
  167 + if (pair == null) {
142 LKLog.d { "Failed to open camera" } 168 LKLog.d { "Failed to open camera" }
143 return null 169 return null
144 } 170 }
145 - return videoCapturer 171 + return pair
146 } 172 }
147 173
148 private fun createCameraCapturer( 174 private fun createCameraCapturer(
149 context: Context, 175 context: Context,
150 enumerator: CameraEnumerator, 176 enumerator: CameraEnumerator,
151 - position: CameraPosition  
152 - ): VideoCapturer? { 177 + options: LocalVideoTrackOptions
  178 + ): Pair<VideoCapturerWithSize, LocalVideoTrackOptions>? {
153 val deviceNames = enumerator.deviceNames 179 val deviceNames = enumerator.deviceNames
154 var targetDeviceName: String? = null 180 var targetDeviceName: String? = null
155 var targetVideoCapturer: VideoCapturer? = null 181 var targetVideoCapturer: VideoCapturer? = null
156 for (deviceName in deviceNames) { 182 for (deviceName in deviceNames) {
157 - if (enumerator.isFrontFacing(deviceName) && position == CameraPosition.FRONT) { 183 + if ((options.deviceId != null && deviceName == options.deviceId)
  184 + || (enumerator.isFrontFacing(deviceName) && options.position == CameraPosition.FRONT)
  185 + ) {
158 LKLog.v { "Creating front facing camera capturer." } 186 LKLog.v { "Creating front facing camera capturer." }
159 val videoCapturer = enumerator.createCapturer(deviceName, null) 187 val videoCapturer = enumerator.createCapturer(deviceName, null)
160 if (videoCapturer != null) { 188 if (videoCapturer != null) {
@@ -162,7 +190,9 @@ open class LocalVideoTrack( @@ -162,7 +190,9 @@ open class LocalVideoTrack(
162 targetVideoCapturer = videoCapturer 190 targetVideoCapturer = videoCapturer
163 break 191 break
164 } 192 }
165 - } else if (enumerator.isBackFacing(deviceName) && position == CameraPosition.BACK) { 193 + } else if ((options.deviceId != null && deviceName == options.deviceId)
  194 + || (enumerator.isBackFacing(deviceName) && options.position == CameraPosition.BACK)
  195 + ) {
166 LKLog.v { "Creating back facing camera capturer." } 196 LKLog.v { "Creating back facing camera capturer." }
167 val videoCapturer = enumerator.createCapturer(deviceName, null) 197 val videoCapturer = enumerator.createCapturer(deviceName, null)
168 if (videoCapturer != null) { 198 if (videoCapturer != null) {
@@ -173,19 +203,40 @@ open class LocalVideoTrack( @@ -173,19 +203,40 @@ open class LocalVideoTrack(
173 } 203 }
174 } 204 }
175 205
  206 + // back fill any missing information
  207 + val newOptions = options.copy(
  208 + deviceId = targetDeviceName,
  209 + position = enumerator.getCameraPosition(targetDeviceName!!)
  210 + )
176 if (targetVideoCapturer is Camera1Capturer) { 211 if (targetVideoCapturer is Camera1Capturer) {
177 - return Camera1CapturerWithSize(targetVideoCapturer, targetDeviceName) 212 + return Pair(
  213 + Camera1CapturerWithSize(targetVideoCapturer, targetDeviceName),
  214 + newOptions
  215 + )
178 } 216 }
179 217
180 if (targetVideoCapturer is Camera2Capturer) { 218 if (targetVideoCapturer is Camera2Capturer) {
181 - return Camera2CapturerWithSize( 219 + return Pair(
  220 + Camera2CapturerWithSize(
182 targetVideoCapturer, 221 targetVideoCapturer,
183 context.getSystemService(Context.CAMERA_SERVICE) as CameraManager, 222 context.getSystemService(Context.CAMERA_SERVICE) as CameraManager,
184 targetDeviceName 223 targetDeviceName
  224 + ),
  225 + newOptions
185 ) 226 )
186 } 227 }
187 228
188 return null 229 return null
189 } 230 }
  231 +
  232 + fun CameraEnumerator.getCameraPosition(deviceName: String): CameraPosition? {
  233 + if (isBackFacing(deviceName)) {
  234 + return CameraPosition.BACK
  235 + } else if (isFrontFacing(deviceName)) {
  236 + return CameraPosition.FRONT
  237 + }
  238 + return null
190 } 239 }
  240 + }
  241 +
191 } 242 }
@@ -2,10 +2,15 @@ package io.livekit.android.room.track @@ -2,10 +2,15 @@ package io.livekit.android.room.track
2 2
3 import org.webrtc.RtpParameters 3 import org.webrtc.RtpParameters
4 4
5 -class LocalVideoTrackOptions(  
6 - var isScreencast: Boolean = false,  
7 - var position: CameraPosition = CameraPosition.FRONT,  
8 - var captureParams: VideoCaptureParameter = VideoPreset169.QHD.capture 5 +data class LocalVideoTrackOptions(
  6 + val isScreencast: Boolean = false,
  7 + /**
  8 + * Preferred deviceId to capture from. If not set or found,
  9 + * will prefer a camera according to [position]
  10 + */
  11 + val deviceId: String? = null,
  12 + val position: CameraPosition? = CameraPosition.FRONT,
  13 + val captureParams: VideoCaptureParameter = VideoPreset169.QHD.capture
9 ) 14 )
10 15
11 data class VideoCaptureParameter( 16 data class VideoCaptureParameter(
@@ -44,6 +44,34 @@ open class Track( @@ -44,6 +44,34 @@ open class Track(
44 } 44 }
45 } 45 }
46 46
  47 + enum class Source {
  48 + CAMERA,
  49 + MICROPHONE,
  50 + SCREEN_SHARE,
  51 + UNKNOWN;
  52 +
  53 +
  54 + fun toProto(): LivekitModels.TrackSource {
  55 + return when (this) {
  56 + CAMERA -> LivekitModels.TrackSource.CAMERA
  57 + MICROPHONE -> LivekitModels.TrackSource.MICROPHONE
  58 + SCREEN_SHARE -> LivekitModels.TrackSource.SCREEN_SHARE
  59 + UNKNOWN -> LivekitModels.TrackSource.UNKNOWN
  60 + }
  61 + }
  62 +
  63 + companion object {
  64 + fun fromProto(source: LivekitModels.TrackSource): Source {
  65 + return when (source) {
  66 + LivekitModels.TrackSource.CAMERA -> CAMERA
  67 + LivekitModels.TrackSource.MICROPHONE -> MICROPHONE
  68 + LivekitModels.TrackSource.SCREEN_SHARE -> SCREEN_SHARE
  69 + else -> UNKNOWN
  70 + }
  71 + }
  72 + }
  73 + }
  74 +
47 data class Dimensions(var width: Int, var height: Int) 75 data class Dimensions(var width: Int, var height: Int)
48 76
49 open fun start() { 77 open fun start() {
@@ -27,7 +27,8 @@ open class TrackPublication( @@ -27,7 +27,8 @@ open class TrackPublication(
27 internal set 27 internal set
28 var dimensions: Track.Dimensions? = null 28 var dimensions: Track.Dimensions? = null
29 internal set 29 internal set
30 - 30 + var source: Track.Source = Track.Source.UNKNOWN
  31 + internal set
31 32
32 var participant: WeakReference<Participant> 33 var participant: WeakReference<Participant>
33 34
@@ -44,6 +45,7 @@ open class TrackPublication( @@ -44,6 +45,7 @@ open class TrackPublication(
44 name = info.name 45 name = info.name
45 kind = Track.Kind.fromProto(info.type) 46 kind = Track.Kind.fromProto(info.type)
46 muted = info.muted 47 muted = info.muted
  48 + source = Track.Source.fromProto(info.source)
47 if (kind == Track.Kind.VIDEO) { 49 if (kind == Track.Kind.VIDEO) {
48 simulcasted = info.simulcast 50 simulcasted = info.simulcast
49 dimensions = Track.Dimensions(info.width, info.height) 51 dimensions = Track.Dimensions(info.width, info.height)
@@ -2,6 +2,8 @@ package org.webrtc @@ -2,6 +2,8 @@ package org.webrtc
2 2
3 /** 3 /**
4 * A helper to access package-protected methods used in [Camera2Session] 4 * A helper to access package-protected methods used in [Camera2Session]
  5 + *
  6 + * Note: cameraId as used in the Camera1XXX classes refers to the index within the list of cameras.
5 * @suppress 7 * @suppress
6 */ 8 */
7 internal class Camera1Helper { 9 internal class Camera1Helper {
@@ -4,6 +4,9 @@ import android.hardware.camera2.CameraManager @@ -4,6 +4,9 @@ import android.hardware.camera2.CameraManager
4 4
5 /** 5 /**
6 * A helper to access package-protected methods used in [Camera2Session] 6 * A helper to access package-protected methods used in [Camera2Session]
  7 + *
  8 + * Note: cameraId as used in the Camera2XXX classes refers to the id returned
  9 + * by [CameraManager.getCameraIdList].
7 * @suppress 10 * @suppress
8 */ 11 */
9 internal class Camera2Helper { 12 internal class Camera2Helper {
@@ -54,7 +54,8 @@ class RoomTest { @@ -54,7 +54,8 @@ class RoomTest {
54 context, 54 context,
55 rtcEngine, 55 rtcEngine,
56 eglBase, 56 eglBase,
57 - localParticantFactory 57 + localParticantFactory,
  58 + DefaultsManager()
58 ) 59 )
59 } 60 }
60 61