davidliu
Committed by GitHub

Updates for 2.0.0 (#353)

* Prefix webrtc to avoid collisions with other libraries

* Prepare for v2.0.0

* Move test webrtc files as well

* Revert java target to Java 1.8

* Remove LiveKit.connect method

* Rename participant's track variables to trackPublication to accurately reflect type

* update protocol submodule

* Change remoteParticipants to identity keys

Also add in the Participant Sid/Identity value classes to prevent mistakes

* change publishData to take in Identity type

* Move to android-prefixed webrtc library

* Make inline value classes serializable

* Fix publishdata send to identity destination

* Support sync stream id

* Remap LivekitModels.VideoQuality to VideoQuality enum

The new VideoQuality enum exposes only the ones that should be used by consumers

* Suspend function for getSid

* spotless

* fix compile

* Add documentation and internal visibility for internal classes
正在显示 100 个修改的文件 包含 933 行增加683 行删除

要显示太多修改。

为保证性能只显示 100 of 100+ 个文件。

... ... @@ -57,7 +57,11 @@ subprojects {
}
kotlin {
target("src/*/java/**/*.kt")
targetExclude("src/*/java/**/ReentrantMutex.kt") // Different license
targetExclude(
"src/*/java/**/ReentrantMutex.kt", // Different license
"src/*/java/**/TextureViewRenderer.kt", // Different license
"src/*/java/**/FlowDelegate.kt", // Different license
)
ktlint("0.50.0")
.setEditorConfigPath("$rootDir/.editorconfig")
licenseHeaderFile(rootProject.file("LicenseHeaderFile.txt"))
... ...
... ... @@ -6,66 +6,66 @@ ext {
java_version = JavaVersion.VERSION_1_8
dokka_version = '1.5.0'
androidSdk = [
compileVersion: 33,
targetVersion : 33,
minVersion : 21,
compileVersion: 33,
targetVersion : 33,
minVersion : 21,
]
versions = [
androidx_core : "1.10.1",
androidx_lifecycle: "2.5.1",
autoService : '1.0.1',
coroutines : "1.6.0",
dagger : "2.46",
groupie : "2.9.0",
junit : "4.13.2",
junitJupiter : "5.5.0",
lint : "30.0.1",
serialization : "1.5.0",
protobuf : "3.22.0",
androidx_core : "1.10.1",
androidx_lifecycle: "2.5.1",
autoService : '1.0.1',
coroutines : "1.6.0",
dagger : "2.46",
groupie : "2.9.0",
junit : "4.13.2",
junitJupiter : "5.5.0",
lint : "30.0.1",
serialization : "1.5.0",
protobuf : "3.22.0",
]
generated = [
protoSrc: "$projectDir/protocol",
protoSrc: "$projectDir/protocol",
]
deps = [
androidx : [
'annotation' : 'androidx.annotation:annotation:1.6.0',
'activity_compose' : 'androidx.activity:activity-compose:1.7.1',
'constraintlayout_compose': "androidx.constraintlayout:constraintlayout-compose:1.0.1",
],
auto : [
'service' : "com.google.auto.service:auto-service:${versions.autoService}",
'serviceAnnotations': "com.google.auto.service:auto-service-annotations:${versions.autoService}",
],
coroutines : [
"lib" : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}",
"test": "org.jetbrains.kotlinx:kotlinx-coroutines-test: ${versions.coroutines}",
],
compose : [
"bom": "androidx.compose:compose-bom:2023.04.01",
],
timber : "com.github.ajalt:timberkt:1.5.1",
androidx : [
'annotation' : 'androidx.annotation:annotation:1.6.0',
'activity_compose' : 'androidx.activity:activity-compose:1.7.1',
'constraintlayout_compose': "androidx.constraintlayout:constraintlayout-compose:1.0.1",
],
auto : [
'service' : "com.google.auto.service:auto-service:${versions.autoService}",
'serviceAnnotations': "com.google.auto.service:auto-service-annotations:${versions.autoService}",
],
coroutines : [
"lib" : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}",
"test": "org.jetbrains.kotlinx:kotlinx-coroutines-test: ${versions.coroutines}",
],
compose : [
"bom": "androidx.compose:compose-bom:2023.04.01",
],
timber : "com.github.ajalt:timberkt:1.5.1",
// lint
lint : "com.android.tools.lint:lint:${versions.lint}",
lintApi : "com.android.tools.lint:lint-api:${versions.lint}",
lintChecks : "com.android.tools.lint:lint-checks:${versions.lint}",
lintTests : "com.android.tools.lint:lint-tests:${versions.lint}",
// tests
androidx_test : [
"core" : 'androidx.test:core:1.5.0',
"junit": "androidx.test.ext:junit:1.1.5",
],
espresso : 'androidx.test.espresso:espresso-core:3.5.1',
junit : "junit:junit:${versions.junit}",
junitJupiterApi : "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}",
junitJupiterEngine: "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}",
mockito : [
"core" : 'org.mockito:mockito-core:4.0.0',
"kotlin": "org.mockito.kotlin:mockito-kotlin:4.0.0",
],
robolectric : 'org.robolectric:robolectric:4.10.2',
// lint
lint : "com.android.tools.lint:lint:${versions.lint}",
lintApi : "com.android.tools.lint:lint-api:${versions.lint}",
lintChecks : "com.android.tools.lint:lint-checks:${versions.lint}",
lintTests : "com.android.tools.lint:lint-tests:${versions.lint}",
// tests
androidx_test : [
"core" : 'androidx.test:core:1.5.0',
"junit": "androidx.test.ext:junit:1.1.5",
],
espresso : 'androidx.test.espresso:espresso-core:3.5.1',
junit : "junit:junit:${versions.junit}",
junitJupiterApi : "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}",
junitJupiterEngine: "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}",
mockito : [
"core" : 'org.mockito:mockito-core:4.0.0',
"kotlin": "org.mockito.kotlin:mockito-kotlin:4.0.0",
],
robolectric : 'org.robolectric:robolectric:4.10.2',
turbine : 'app.cash.turbine:turbine:1.0.0',
]
annotations = [
]
... ...
... ... @@ -23,7 +23,8 @@ kotlin.code.style=official
###############################################################
GROUP=io.livekit
VERSION_NAME=1.6.0
VERSION_NAME=2.0.0-SNAPSHOT
POM_DESCRIPTION=LiveKit Android SDK, WebRTC Rooms
... ...
... ... @@ -147,7 +147,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation deps.coroutines.lib
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:${versions.serialization}"
api 'io.github.webrtc-sdk:android:114.5735.05'
api 'io.github.webrtc-sdk:android-prefixed:114.5735.07'
api "com.squareup.okhttp3:okhttp:4.12.0"
api 'com.github.davidliu:audioswitch:89582c47c9a04c62f90aa5e57251af4800a62c9a'
implementation deps.androidx.annotation
... ...
... ... @@ -22,7 +22,7 @@
# WebRTC
#########################################
-keep class org.webrtc.** { *; }
-keep class livekit.org.webrtc.** { *; }
# NIST sdp parser
#########################################
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,8 +17,12 @@
package io.livekit.android
import io.livekit.android.room.ProtocolVersion
import org.webrtc.PeerConnection
import io.livekit.android.room.Room
import livekit.org.webrtc.PeerConnection
/**
* Options for using with [Room.connect].
*/
data class ConnectOptions(
/** Auto subscribe to room tracks upon connect, defaults to true */
val autoSubscribe: Boolean = true,
... ... @@ -48,7 +52,7 @@ data class ConnectOptions(
/**
* the protocol version to use with the server.
*/
val protocolVersion: ProtocolVersion = ProtocolVersion.v9
val protocolVersion: ProtocolVersion = ProtocolVersion.v12,
) {
internal var reconnect: Boolean = false
internal var participantSid: String? = null
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,119 +21,89 @@ import android.content.Context
import io.livekit.android.dagger.DaggerLiveKitComponent
import io.livekit.android.dagger.RTCModule
import io.livekit.android.dagger.create
import io.livekit.android.room.ProtocolVersion
import io.livekit.android.room.Room
import io.livekit.android.room.RoomListener
import io.livekit.android.util.LKLog
import io.livekit.android.util.LoggingLevel
import timber.log.Timber
class LiveKit {
companion object {
/**
* [LoggingLevel] to use for Livekit logs. Set to [LoggingLevel.OFF] to turn off logs.
*
* Defaults to [LoggingLevel.OFF]
*/
@JvmStatic
var loggingLevel: LoggingLevel
get() = LKLog.loggingLevel
set(value) {
LKLog.loggingLevel = value
// Plant debug tree if needed.
if (value != LoggingLevel.OFF) {
val forest = Timber.forest()
val needsPlanting = forest.none { it is Timber.DebugTree }
if (needsPlanting) {
Timber.plant(Timber.DebugTree())
}
object LiveKit {
/**
* [LoggingLevel] to use for Livekit logs. Set to [LoggingLevel.OFF] to turn off logs.
*
* Defaults to [LoggingLevel.OFF]
*/
@JvmStatic
var loggingLevel: LoggingLevel
get() = LKLog.loggingLevel
set(value) {
LKLog.loggingLevel = value
// Plant debug tree if needed.
if (value != LoggingLevel.OFF) {
val forest = Timber.forest()
val needsPlanting = forest.none { it is Timber.DebugTree }
if (needsPlanting) {
Timber.plant(Timber.DebugTree())
}
}
/**
* Enables logs for the underlying WebRTC sdk logging. Used in conjunction with [loggingLevel].
*
* Note: WebRTC logging is very noisy and should only be used to diagnose native WebRTC issues.
*/
@JvmStatic
var enableWebRTCLogging: Boolean = false
/**
* Certain WebRTC classes need to be initialized prior to use.
*
* This does not need to be called under normal circumstances, as [LiveKit.create]
* will handle this for you.
*/
fun init(appContext: Context) {
RTCModule.libWebrtcInitialization(appContext)
}
/**
* Create a Room object.
*/
fun create(
appContext: Context,
options: RoomOptions = RoomOptions(),
overrides: LiveKitOverrides = LiveKitOverrides(),
): Room {
val ctx = appContext.applicationContext
if (ctx !is Application) {
LKLog.w { "Application context was not found, this may cause memory leaks." }
}
val component = DaggerLiveKitComponent
.factory()
.create(ctx, overrides)
val room = component.roomFactory().create(ctx)
options.audioTrackCaptureDefaults?.let {
room.audioTrackCaptureDefaults = it
}
options.videoTrackCaptureDefaults?.let {
room.videoTrackCaptureDefaults = it
}
options.audioTrackPublishDefaults?.let {
room.audioTrackPublishDefaults = it
}
options.videoTrackPublishDefaults?.let {
room.videoTrackPublishDefaults = it
}
room.adaptiveStream = options.adaptiveStream
room.dynacast = options.dynacast
room.e2eeOptions = options.e2eeOptions
/**
* Enables logs for the underlying WebRTC sdk logging. Used in conjunction with [loggingLevel].
*
* Note: WebRTC logging is very noisy and should only be used to diagnose native WebRTC issues.
*/
@JvmStatic
var enableWebRTCLogging: Boolean = false
/**
* Certain WebRTC classes need to be initialized prior to use.
*
* This does not need to be called under normal circumstances, as [LiveKit.create]
* will handle this for you.
*/
fun init(appContext: Context) {
RTCModule.libWebrtcInitialization(appContext)
}
return room
/**
* Create a Room object.
*/
fun create(
appContext: Context,
options: RoomOptions = RoomOptions(),
overrides: LiveKitOverrides = LiveKitOverrides(),
): Room {
val ctx = appContext.applicationContext
if (ctx !is Application) {
LKLog.w { "Application context was not found, this may cause memory leaks." }
}
/**
* Connect to a LiveKit room
* @param url URL to LiveKit server (i.e. ws://mylivekitdeploy.io)
* @param listener Listener to Room events. LiveKit interactions take place with these callbacks
*/
@Deprecated("Use LiveKit.create and Room.connect instead. This is limited to max protocol 7.")
suspend fun connect(
appContext: Context,
url: String,
token: String,
options: ConnectOptions = ConnectOptions(),
roomOptions: RoomOptions = RoomOptions(),
listener: RoomListener? = null,
overrides: LiveKitOverrides = LiveKitOverrides(),
): Room {
val room = create(appContext, roomOptions, overrides)
val component = DaggerLiveKitComponent
.factory()
.create(ctx, overrides)
room.listener = listener
val room = component.roomFactory().create(ctx)
val protocolVersion = maxOf(options.protocolVersion, ProtocolVersion.v7)
val connectOptions = options.copy(protocolVersion = protocolVersion)
options.audioTrackCaptureDefaults?.let {
room.audioTrackCaptureDefaults = it
}
options.videoTrackCaptureDefaults?.let {
room.videoTrackCaptureDefaults = it
}
room.connect(url, token, connectOptions)
return room
options.audioTrackPublishDefaults?.let {
room.audioTrackPublishDefaults = it
}
options.videoTrackPublishDefaults?.let {
room.videoTrackPublishDefaults = it
}
room.adaptiveStream = options.adaptiveStream
room.dynacast = options.dynacast
room.e2eeOptions = options.e2eeOptions
return room
}
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -23,12 +23,12 @@ import io.livekit.android.audio.AudioHandler
import io.livekit.android.audio.AudioSwitchHandler
import io.livekit.android.audio.NoAudioHandler
import io.livekit.android.room.Room
import livekit.org.webrtc.EglBase
import livekit.org.webrtc.VideoDecoderFactory
import livekit.org.webrtc.VideoEncoderFactory
import livekit.org.webrtc.audio.AudioDeviceModule
import livekit.org.webrtc.audio.JavaAudioDeviceModule
import okhttp3.OkHttpClient
import org.webrtc.EglBase
import org.webrtc.VideoDecoderFactory
import org.webrtc.VideoEncoderFactory
import org.webrtc.audio.AudioDeviceModule
import org.webrtc.audio.JavaAudioDeviceModule
/**
* Overrides to replace LiveKit internally used components with custom implementations.
... ... @@ -65,6 +65,9 @@ data class LiveKitOverrides(
val eglBase: EglBase? = null,
)
/**
* Options for customizing the audio settings of LiveKit.
*/
class AudioOptions(
/**
* Override the default output [AudioType].
... ... @@ -103,6 +106,9 @@ class AudioOptions(
val javaAudioDeviceModuleCustomizer: ((builder: JavaAudioDeviceModule.Builder) -> Unit)? = null,
)
/**
* Audio types for customizing the audio of LiveKit.
*/
sealed class AudioType(
val audioMode: Int,
val audioAttributes: AudioAttributes,
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,6 +16,12 @@
package io.livekit.android
/**
* Version information about LiveKit
*/
object Version {
/**
* The current LiveKit SDK version.
*/
const val CLIENT_VERSION = BuildConfig.VERSION_NAME
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -76,6 +76,9 @@ constructor(context: Context) : AudioHandler {
onAudioFocusChangeListener?.onAudioFocusChange(it)
}
/**
* Set this to listen to audio focus changes.
*/
var onAudioFocusChangeListener: AudioManager.OnAudioFocusChangeListener? = null
override fun start() {
... ... @@ -98,6 +101,16 @@ constructor(context: Context) : AudioHandler {
}
}
/**
* Creates the request used when requesting audio focus.
*
* The default implementation creates an audio focus request based on the
* settings of this object.
*
* Only used on Android O and upwards. On lower platforms,
* the request will be made using the [audioStreamType] and [focusMode]
* settings.
*/
@RequiresApi(Build.VERSION_CODES.O)
open fun createAudioRequest(): AudioFocusRequest {
return AudioFocusRequest.Builder(focusMode)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -183,12 +183,21 @@ constructor(private val context: Context) : AudioHandler {
}
}
/**
* The currently selected audio device, or null if none (or this handler is not started).
*/
val selectedAudioDevice: AudioDevice?
get() = audioSwitch?.selectedAudioDevice
/**
* The available audio devices. This requires calling [start] before it is populated.
*/
val availableAudioDevices: List<AudioDevice>
get() = audioSwitch?.availableAudioDevices ?: listOf()
/**
* Select a specific audio device.
*/
fun selectDevice(audioDevice: AudioDevice?) {
if (Looper.myLooper() == Looper.getMainLooper()) {
audioSwitch?.selectDevice(audioDevice)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -30,7 +30,7 @@ import javax.inject.Singleton
* @suppress
*/
@Module
object AudioHandlerModule {
internal object AudioHandlerModule {
@Provides
fun audioOutputType(
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -25,7 +25,7 @@ import javax.inject.Named
* @suppress
*/
@Module
object CoroutinesModule {
internal object CoroutinesModule {
@Provides
@Named(InjectionNames.DISPATCHER_DEFAULT)
fun defaultDispatcher() = Dispatchers.Default
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,10 +16,7 @@
package io.livekit.android.dagger
/**
* @suppress
*/
object InjectionNames {
internal object InjectionNames {
/**
* @see [kotlinx.coroutines.Dispatchers.Default]
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,11 +21,8 @@ import dagger.Provides
import dagger.Reusable
import kotlinx.serialization.json.Json
/**
* @suppress
*/
@Module
object JsonFormatModule {
internal object JsonFormatModule {
@Provides
@Reusable
fun kotlinSerializationJson(): Json =
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,8 +21,8 @@ import dagger.BindsInstance
import dagger.Component
import io.livekit.android.LiveKitOverrides
import io.livekit.android.room.Room
import org.webrtc.EglBase
import org.webrtc.PeerConnectionFactory
import livekit.org.webrtc.EglBase
import livekit.org.webrtc.PeerConnectionFactory
import javax.inject.Singleton
@Singleton
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,11 +21,8 @@ import dagger.Provides
import io.livekit.android.memory.CloseableManager
import javax.inject.Singleton
/**
* @suppress
*/
@Module
object MemoryModule {
internal object MemoryModule {
@Singleton
@Provides
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -24,11 +24,8 @@ import io.livekit.android.LiveKitOverrides
import javax.inject.Named
@SuppressLint("KotlinNullnessAnnotation")
/**
* @suppress
*/
@Module
class OverridesModule(private val overrides: LiveKitOverrides) {
internal class OverridesModule(private val overrides: LiveKitOverrides) {
@Provides
@Named(InjectionNames.OVERRIDE_OKHTTP)
... ...
... ... @@ -30,20 +30,17 @@ import io.livekit.android.util.LKLog
import io.livekit.android.util.LoggingLevel
import io.livekit.android.webrtc.CustomVideoDecoderFactory
import io.livekit.android.webrtc.CustomVideoEncoderFactory
import org.webrtc.*
import org.webrtc.audio.AudioDeviceModule
import org.webrtc.audio.JavaAudioDeviceModule
import livekit.org.webrtc.*
import livekit.org.webrtc.audio.AudioDeviceModule
import livekit.org.webrtc.audio.JavaAudioDeviceModule
import timber.log.Timber
import javax.inject.Named
import javax.inject.Singleton
typealias CapabilitiesGetter = @JvmSuppressWildcards (MediaStreamTrack.MediaType) -> RtpCapabilities
internal typealias CapabilitiesGetter = @JvmSuppressWildcards (MediaStreamTrack.MediaType) -> RtpCapabilities
/**
* @suppress
*/
@Module
object RTCModule {
internal object RTCModule {
/**
* Certain classes require libwebrtc to be initialized prior to use.
... ... @@ -55,6 +52,7 @@ object RTCModule {
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions
.builder(appContext)
.setNativeLibraryName("lkjingle_peerconnection_so")
.setInjectableLogger(
{ s, severity, s2 ->
if (!LiveKit.enableWebRTCLogging) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -28,11 +28,8 @@ import okhttp3.WebSocket
import javax.inject.Named
import javax.inject.Singleton
/**
* @suppress
*/
@Module
object WebModule {
internal object WebModule {
@Provides
@Singleton
fun okHttpClient(
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,6 +22,7 @@ import dagger.assisted.AssistedInject
import io.livekit.android.events.RoomEvent
import io.livekit.android.room.Room
import io.livekit.android.room.participant.LocalParticipant
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.participant.RemoteParticipant
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.room.track.LocalVideoTrack
... ... @@ -30,13 +31,13 @@ import io.livekit.android.room.track.RemoteVideoTrack
import io.livekit.android.room.track.Track
import io.livekit.android.room.track.TrackPublication
import io.livekit.android.util.LKLog
import org.webrtc.FrameCryptor
import org.webrtc.FrameCryptor.FrameCryptionState
import org.webrtc.FrameCryptorAlgorithm
import org.webrtc.FrameCryptorFactory
import org.webrtc.PeerConnectionFactory
import org.webrtc.RtpReceiver
import org.webrtc.RtpSender
import livekit.org.webrtc.FrameCryptor
import livekit.org.webrtc.FrameCryptor.FrameCryptionState
import livekit.org.webrtc.FrameCryptorAlgorithm
import livekit.org.webrtc.FrameCryptorFactory
import livekit.org.webrtc.PeerConnectionFactory
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.RtpSender
class E2EEManager
@AssistedInject
... ... @@ -47,7 +48,7 @@ constructor(
private var room: Room? = null
private var keyProvider: KeyProvider
private var peerConnectionFactory: PeerConnectionFactory
private var frameCryptors = mutableMapOf<Pair<String, String>, FrameCryptor>()
private var frameCryptors = mutableMapOf<Pair<String, Participant.Identity>, FrameCryptor>()
private var algorithm: FrameCryptorAlgorithm = FrameCryptorAlgorithm.AES_GCM
private lateinit var emitEvent: (roomEvent: RoomEvent) -> Unit?
var enabled: Boolean = false
... ... @@ -69,7 +70,7 @@ constructor(
this.enabled = true
this.room = room
this.emitEvent = emitEvent
this.room?.localParticipant?.tracks?.forEach() { item ->
this.room?.localParticipant?.trackPublications?.forEach() { item ->
var participant = this.room!!.localParticipant
var publication = item.value
if (publication.track != null) {
... ... @@ -78,7 +79,7 @@ constructor(
}
this.room?.remoteParticipants?.forEach() { item ->
var participant = item.value
participant.tracks.forEach() { item ->
participant.trackPublications.forEach() { item ->
var publication = item.value
if (publication.track != null) {
addSubscribedTrack(publication.track!!, publication, participant, room)
... ... @@ -169,11 +170,11 @@ constructor(
}
}
private fun addRtpSender(sender: RtpSender, participantId: String, trackId: String, kind: String): FrameCryptor {
private fun addRtpSender(sender: RtpSender, participantId: Participant.Identity, trackId: String, kind: String): FrameCryptor {
var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender(
peerConnectionFactory,
sender,
participantId,
participantId.value,
algorithm,
keyProvider.rtcKeyProvider,
)
... ... @@ -183,11 +184,11 @@ constructor(
return frameCryptor
}
private fun addRtpReceiver(receiver: RtpReceiver, participantId: String, trackId: String, kind: String): FrameCryptor {
private fun addRtpReceiver(receiver: RtpReceiver, participantId: Participant.Identity, trackId: String, kind: String): FrameCryptor {
var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver(
peerConnectionFactory,
receiver,
participantId,
participantId.value,
algorithm,
keyProvider.rtcKeyProvider,
)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,8 +17,8 @@
package io.livekit.android.e2ee
import io.livekit.android.util.LKLog
import org.webrtc.FrameCryptorFactory
import org.webrtc.FrameCryptorKeyProvider
import livekit.org.webrtc.FrameCryptorFactory
import livekit.org.webrtc.FrameCryptorKeyProvider
class KeyInfo
constructor(var participantId: String, var keyIndex: Int, var key: String) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,12 +16,16 @@
package io.livekit.android.events
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
interface EventListenable<out T> {
val events: SharedFlow<T>
}
/**
* @see [Flow.collect]
*/
suspend inline fun <T> EventListenable<T>.collect(crossinline action: suspend (value: T) -> Unit) {
events.collect { value -> action(value) }
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -232,7 +232,7 @@ enum class DisconnectReason {
JOIN_FAILURE,
}
fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
internal fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
return when (this) {
LivekitModels.DisconnectReason.CLIENT_INITIATED -> DisconnectReason.CLIENT_INITIATED
LivekitModels.DisconnectReason.DUPLICATE_IDENTITY -> DisconnectReason.DUPLICATE_IDENTITY
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -19,7 +19,7 @@ package io.livekit.android.memory
import java.io.Closeable
/**
* @hide
* @suppress
*/
class CloseableManager : Closeable {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.memory
import org.webrtc.SurfaceTextureHelper
import livekit.org.webrtc.SurfaceTextureHelper
import java.io.Closeable
internal class SurfaceTextureHelperCloser(private val surfaceTextureHelper: SurfaceTextureHelper) : Closeable {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -20,7 +20,7 @@ import android.content.Context
import android.util.AttributeSet
import android.view.View
import io.livekit.android.room.track.video.ViewVisibility
import org.webrtc.SurfaceViewRenderer
import livekit.org.webrtc.SurfaceViewRenderer
open class SurfaceViewRenderer : SurfaceViewRenderer, ViewVisibility.Notifier {
constructor(context: Context) : super(context)
... ...
... ... @@ -19,8 +19,8 @@ import android.view.SurfaceHolder
import android.view.TextureView
import android.view.View
import io.livekit.android.room.track.video.ViewVisibility
import org.webrtc.*
import org.webrtc.RendererCommon.*
import livekit.org.webrtc.*
import livekit.org.webrtc.RendererCommon.*
import java.util.concurrent.CountDownLatch
/**
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -20,8 +20,8 @@ import android.content.Context
import android.hardware.camera2.CameraManager
import android.os.Handler
import android.os.Looper
import org.webrtc.Camera1Enumerator
import org.webrtc.Camera2Enumerator
import livekit.org.webrtc.Camera1Enumerator
import livekit.org.webrtc.Camera2Enumerator
object DeviceManager {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,8 +18,5 @@ package io.livekit.android.room
import kotlinx.serialization.Serializable
/**
* @suppress
*/
@Serializable
data class IceCandidateJSON(val candidate: String, val sdpMLineIndex: Int, val sdpMid: String?)
internal data class IceCandidateJSON(val candidate: String, val sdpMLineIndex: Int, val sdpMid: String?)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -40,9 +40,9 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.runBlocking
import org.webrtc.*
import org.webrtc.PeerConnection.RTCConfiguration
import org.webrtc.PeerConnection.SignalingState
import livekit.org.webrtc.*
import livekit.org.webrtc.PeerConnection.RTCConfiguration
import livekit.org.webrtc.PeerConnection.SignalingState
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Named
import kotlin.contracts.ExperimentalContracts
... ... @@ -459,7 +459,7 @@ internal data class TrackBitrateInfo(
val maxBitrate: Long,
)
sealed class TrackBitrateInfoKey {
internal sealed class TrackBitrateInfoKey {
data class Cid(val value: String) : TrackBitrateInfoKey()
data class Transceiver(val value: RtpTransceiver) : TrackBitrateInfoKey()
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -19,19 +19,16 @@ package io.livekit.android.room
import io.livekit.android.util.LKLog
import io.livekit.android.webrtc.peerconnection.executeOnRTCThread
import livekit.LivekitRtc
import org.webrtc.CandidatePairChangeEvent
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
import org.webrtc.PeerConnection
import org.webrtc.RtpReceiver
import org.webrtc.RtpTransceiver
import org.webrtc.SessionDescription
/**
* @suppress
*/
class PublisherTransportObserver(
import livekit.org.webrtc.CandidatePairChangeEvent
import livekit.org.webrtc.DataChannel
import livekit.org.webrtc.IceCandidate
import livekit.org.webrtc.MediaStream
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.RtpTransceiver
import livekit.org.webrtc.SessionDescription
internal class PublisherTransportObserver(
private val engine: RTCEngine,
private val client: SignalClient,
) : PeerConnection.Observer, PeerConnectionTransport.Listener {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -44,9 +44,9 @@ import livekit.LivekitModels
import livekit.LivekitRtc
import livekit.LivekitRtc.JoinResponse
import livekit.LivekitRtc.ReconnectResponse
import org.webrtc.*
import org.webrtc.PeerConnection.RTCConfiguration
import org.webrtc.RtpTransceiver.RtpTransceiverInit
import livekit.org.webrtc.*
import livekit.org.webrtc.PeerConnection.RTCConfiguration
import livekit.org.webrtc.RtpTransceiver.RtpTransceiverInit
import java.net.ConnectException
import java.nio.ByteBuffer
import javax.inject.Inject
... ... @@ -266,6 +266,7 @@ internal constructor(
cid: String,
name: String,
kind: LivekitModels.TrackType,
stream: String?,
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder(),
): LivekitModels.TrackInfo {
if (pendingTrackResolvers[cid] != null) {
... ... @@ -274,7 +275,13 @@ internal constructor(
// Suspend until signal client receives message confirming track publication.
return suspendCoroutine { cont ->
pendingTrackResolvers[cid] = cont
client.sendAddTrack(cid, name, kind, builder)
client.sendAddTrack(
cid = cid,
name = name,
type = kind,
stream = stream,
builder = builder,
)
}
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -41,14 +41,19 @@ import io.livekit.android.room.participant.*
import io.livekit.android.room.track.*
import io.livekit.android.util.FlowObservable
import io.livekit.android.util.LKLog
import io.livekit.android.util.flow
import io.livekit.android.util.flowDelegate
import io.livekit.android.util.invoke
import io.livekit.android.webrtc.getFilteredStats
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.serialization.Serializable
import livekit.LivekitModels
import livekit.LivekitRtc
import org.webrtc.*
import livekit.org.webrtc.*
import javax.inject.Named
import kotlin.jvm.Throws
class Room
@AssistedInject
... ... @@ -92,15 +97,35 @@ constructor(
SERVER_LEAVE,
}
@Serializable
@JvmInline
value class Sid(val sid: String)
@Deprecated("Use events instead.")
var listener: RoomListener? = null
/**
* The session id of the room.
*
* Note: the sid may not be populated immediately upon [connect],
* so using the suspend function [getSid] or listening to the flow
* `room::sid.flow` is highly advised.
*/
@FlowObservable
@get:FlowObservable
var sid: Sid? by flowDelegate(null)
private set
/**
* Gets the sid of the room.
*
* If the sid is not yet available, will suspend until received.
*/
suspend fun getSid(): Sid {
return this@Room::sid.flow
.filterNotNull()
.first()
}
@FlowObservable
@get:FlowObservable
... ... @@ -193,13 +218,15 @@ constructor(
internalListener = this@Room
}
private var mutableRemoteParticipants by flowDelegate(emptyMap<String, RemoteParticipant>())
private var mutableRemoteParticipants by flowDelegate(emptyMap<Participant.Identity, RemoteParticipant>())
@FlowObservable
@get:FlowObservable
val remoteParticipants: Map<String, RemoteParticipant>
val remoteParticipants: Map<Participant.Identity, RemoteParticipant>
get() = mutableRemoteParticipants
private var sidToIdentity = mutableMapOf<Participant.Sid, Participant.Identity>()
private var mutableActiveSpeakers by flowDelegate(emptyList<Participant>())
@FlowObservable
... ... @@ -221,6 +248,14 @@ constructor(
e2eeOptions = e2eeOptions,
)
/**
* Connect to a LiveKit Room.
*
* @param url
* @param token
* @param options
*/
@Throws(Exception::class)
suspend fun connect(url: String, token: String, options: ConnectOptions = ConnectOptions()) {
if (this::coroutineScope.isInitialized) {
coroutineScope.cancel()
... ... @@ -333,7 +368,11 @@ constructor(
override fun onJoinResponse(response: LivekitRtc.JoinResponse) {
LKLog.i { "Connected to server, server version: ${response.serverVersion}, client version: ${Version.CLIENT_VERSION}" }
sid = Sid(response.room.sid)
if (response.room.sid != null) {
sid = Sid(response.room.sid)
} else {
sid = null
}
name = response.room.name
metadata = response.room.metadata
... ... @@ -354,16 +393,16 @@ constructor(
localParticipant.updateFromInfo(response.participant)
if (response.otherParticipantsList.isNotEmpty()) {
response.otherParticipantsList.forEach {
getOrCreateRemoteParticipant(it.sid, it)
response.otherParticipantsList.forEach { info ->
getOrCreateRemoteParticipant(Participant.Identity(info.identity), info)
}
}
}
private fun handleParticipantDisconnect(sid: String) {
private fun handleParticipantDisconnect(identity: Participant.Identity) {
val newParticipants = mutableRemoteParticipants.toMutableMap()
val removedParticipant = newParticipants.remove(sid) ?: return
removedParticipant.tracks.values.toList().forEach { publication ->
val removedParticipant = newParticipants.remove(identity) ?: return
removedParticipant.trackPublications.values.toList().forEach { publication ->
removedParticipant.unpublishTrack(publication.sid, true)
}
... ... @@ -372,29 +411,41 @@ constructor(
eventBus.postEvent(RoomEvent.ParticipantDisconnected(this, removedParticipant), coroutineScope)
}
fun getParticipant(sid: String): Participant? {
fun getParticipantBySid(sid: String): Participant? {
return getParticipantBySid(Participant.Sid(sid))
}
fun getParticipantBySid(sid: Participant.Sid): Participant? {
if (sid == localParticipant.sid) {
return localParticipant
} else {
return remoteParticipants[sid]
return remoteParticipants[sidToIdentity[sid]]
}
}
fun getParticipantByIdentity(identity: String): Participant? {
return getParticipantByIdentity(Participant.Identity(identity))
}
fun getParticipantByIdentity(identity: Participant.Identity): Participant? {
if (identity == localParticipant.identity) {
return localParticipant
} else {
return remoteParticipants[identity]
}
}
@Synchronized
private fun getOrCreateRemoteParticipant(
sid: String,
info: LivekitModels.ParticipantInfo? = null,
identity: Participant.Identity,
info: LivekitModels.ParticipantInfo,
): RemoteParticipant {
var participant = remoteParticipants[sid]
var participant = remoteParticipants[identity]
if (participant != null) {
return participant
}
participant = if (info != null) {
RemoteParticipant(info, engine.client, ioDispatcher, defaultDispatcher)
} else {
RemoteParticipant(sid, null, engine.client, ioDispatcher, defaultDispatcher)
}
participant = RemoteParticipant(info, engine.client, ioDispatcher, defaultDispatcher)
participant.internalListener = this
coroutineScope.launch {
... ... @@ -466,26 +517,25 @@ constructor(
}
}
if (info != null) {
participant.updateFromInfo(info)
}
participant.updateFromInfo(info)
val newRemoteParticipants = mutableRemoteParticipants.toMutableMap()
newRemoteParticipants[sid] = participant
newRemoteParticipants[identity] = participant
mutableRemoteParticipants = newRemoteParticipants
sidToIdentity[participant.sid] = identity
return participant
}
private fun handleActiveSpeakersUpdate(speakerInfos: List<LivekitModels.SpeakerInfo>) {
val speakers = mutableListOf<Participant>()
val seenSids = mutableSetOf<String>()
val seenSids = mutableSetOf<Participant.Sid>()
val localParticipant = localParticipant
speakerInfos.forEach { speakerInfo ->
val speakerSid = speakerInfo.sid!!
val speakerSid = Participant.Sid(speakerInfo.sid)
seenSids.add(speakerSid)
val participant = getParticipant(speakerSid) ?: return@forEach
val participant = getParticipantBySid(speakerSid) ?: return@forEach
participant.audioLevel = speakerInfo.level
participant.isSpeaking = true
speakers.add(participant)
... ... @@ -508,21 +558,22 @@ constructor(
}
private fun handleSpeakersChanged(speakerInfos: List<LivekitModels.SpeakerInfo>) {
val updatedSpeakers = mutableMapOf<String, Participant>()
activeSpeakers.forEach {
updatedSpeakers[it.sid] = it
val updatedSpeakers = mutableMapOf<Participant.Sid, Participant>()
activeSpeakers.forEach { participant ->
updatedSpeakers[participant.sid] = participant
}
speakerInfos.forEach { speaker ->
val participant = getParticipant(speaker.sid) ?: return@forEach
val speakerSid = Participant.Sid(speaker.sid)
val participant = getParticipantBySid(speakerSid) ?: return@forEach
participant.audioLevel = speaker.level
participant.isSpeaking = speaker.active
if (speaker.active) {
updatedSpeakers[speaker.sid] = participant
updatedSpeakers[speakerSid] = participant
} else {
updatedSpeakers.remove(speaker.sid)
updatedSpeakers.remove(speakerSid)
}
}
... ... @@ -555,6 +606,7 @@ constructor(
metadata = null
name = null
isRecording = false
sidToIdentity.clear()
}
private fun handleDisconnect(reason: DisconnectReason) {
... ... @@ -590,8 +642,8 @@ constructor(
val participantTracksList = mutableListOf<LivekitModels.ParticipantTracks>()
for (participant in remoteParticipants.values) {
val builder = LivekitModels.ParticipantTracks.newBuilder()
builder.participantSid = participant.sid
for (trackPub in participant.tracks.values) {
builder.participantSid = participant.sid.value
for (trackPub in participant.trackPublications.values) {
val remoteTrackPub = (trackPub as? RemoteTrackPublication) ?: continue
if (remoteTrackPub.subscribed != sendUnsub) {
builder.addTrackSids(remoteTrackPub.sid)
... ... @@ -712,11 +764,19 @@ constructor(
return
}
var (participantSid, trackSid) = unpackStreamId(streams.first().id)
if (trackSid == null) {
trackSid = track.id()
var (participantSid, streamId) = unpackStreamId(streams.first().id)
var trackSid = track.id()
if (streamId != null && streamId.startsWith("TR")) {
trackSid = streamId
}
val participant = getOrCreateRemoteParticipant(participantSid)
val participant = getParticipantBySid(participantSid) as? RemoteParticipant
if (participant == null) {
LKLog.e { "Tried to add a track for a participant that is not present. sid: $participantSid" }
return
}
val statsGetter = engine.createStatsGetter(receiver)
participant.addSubscribedMediaTrack(
track,
... ... @@ -732,24 +792,37 @@ constructor(
*/
override fun onUpdateParticipants(updates: List<LivekitModels.ParticipantInfo>) {
for (info in updates) {
val participantSid = info.sid
val participantSid = Participant.Sid(info.sid)
// LiveKit server doesn't send identity info prior to version 1.5.2 in disconnect updates
// so we try to map an empty identity to an already known sID manually
@Suppress("NAME_SHADOWING") var info = info
if (info.identity.isNullOrBlank()) {
info = with(info.toBuilder()) {
identity = sidToIdentity[participantSid]?.value ?: ""
build()
}
}
val participantIdentity = Participant.Identity(info.identity)
if (localParticipant.sid == participantSid) {
if (localParticipant.identity == participantIdentity) {
localParticipant.updateFromInfo(info)
continue
}
val isNewParticipant = !remoteParticipants.contains(participantSid)
val isNewParticipant = !remoteParticipants.contains(participantIdentity)
if (info.state == LivekitModels.ParticipantInfo.State.DISCONNECTED) {
handleParticipantDisconnect(participantSid)
handleParticipantDisconnect(participantIdentity)
} else {
val participant = getOrCreateRemoteParticipant(participantSid, info)
val participant = getOrCreateRemoteParticipant(participantIdentity, info)
if (isNewParticipant) {
listener?.onParticipantConnected(this, participant)
eventBus.postEvent(RoomEvent.ParticipantConnected(this, participant), coroutineScope)
} else {
participant.updateFromInfo(info)
sidToIdentity[participantSid] = participantIdentity
}
}
}
... ... @@ -773,6 +846,9 @@ constructor(
* @suppress
*/
override fun onRoomUpdate(update: LivekitModels.Room) {
if (update.sid != null) {
sid = Sid(update.sid)
}
val oldMetadata = metadata
metadata = update.metadata
... ... @@ -794,7 +870,7 @@ constructor(
override fun onConnectionQuality(updates: List<LivekitRtc.ConnectionQualityInfo>) {
updates.forEach { info ->
val quality = ConnectionQuality.fromProto(info.quality)
val participant = getParticipant(info.participantSid) ?: return
val participant = getParticipantBySid(info.participantSid) ?: return
participant.connectionQuality = quality
listener?.onConnectionQualityChanged(participant, quality)
eventBus.postEvent(RoomEvent.ConnectionQualityChanged(this, participant, quality), coroutineScope)
... ... @@ -812,7 +888,7 @@ constructor(
* @suppress
*/
override fun onUserPacket(packet: LivekitModels.UserPacket, kind: LivekitModels.DataPacket.Kind) {
val participant = remoteParticipants[packet.participantSid]
val participant = getParticipantBySid(packet.participantSid) as? RemoteParticipant
val data = packet.payload.toByteArray()
val topic = if (packet.hasTopic()) {
packet.topic
... ... @@ -830,8 +906,8 @@ constructor(
*/
override fun onStreamStateUpdate(streamStates: List<LivekitRtc.StreamStateInfo>) {
for (streamState in streamStates) {
val participant = getParticipant(streamState.participantSid) ?: continue
val track = participant.tracks[streamState.trackSid] ?: continue
val participant = getParticipantBySid(streamState.participantSid) ?: continue
val track = participant.trackPublications[streamState.trackSid] ?: continue
track.track?.streamState = Track.StreamState.fromProto(streamState.state)
}
... ... @@ -848,7 +924,7 @@ constructor(
* @suppress
*/
override fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate) {
val participant = getParticipant(subscriptionPermissionUpdate.participantSid) as? RemoteParticipant ?: return
val participant = getParticipantBySid(subscriptionPermissionUpdate.participantSid) as? RemoteParticipant ?: return
participant.onSubscriptionPermissionUpdate(subscriptionPermissionUpdate)
}
... ... @@ -885,7 +961,7 @@ constructor(
override fun onFullReconnecting() {
localParticipant.prepareForFullReconnect()
remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
.forEach { sid -> handleParticipantDisconnect(sid) }
.forEach { identity -> handleParticipantDisconnect(identity) }
}
/**
... ... @@ -897,7 +973,7 @@ constructor(
} else {
val remoteParticipants = remoteParticipants.values.toList()
for (participant in remoteParticipants) {
val pubs = participant.tracks.values.toList()
val pubs = participant.trackPublications.values.toList()
for (pub in pubs) {
val remotePub = pub as? RemoteTrackPublication ?: continue
if (remotePub.subscribed) {
... ... @@ -1091,8 +1167,6 @@ interface RoomListener {
* Could not connect to the room
*/
fun onFailedToConnect(room: Room, error: Throwable) {}
// fun onReconnecting(room: Room, error: Exception) {}
// fun onReconnect(room: Room) {}
/**
* Active speakers changed. List of speakers are ordered by their audio level. loudest
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -37,12 +37,12 @@ import livekit.LivekitModels
import livekit.LivekitRtc
import livekit.LivekitRtc.JoinResponse
import livekit.LivekitRtc.ReconnectResponse
import livekit.org.webrtc.IceCandidate
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.SessionDescription
import okhttp3.*
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.webrtc.IceCandidate
import org.webrtc.PeerConnection
import org.webrtc.SessionDescription
import java.util.*
import javax.inject.Inject
import javax.inject.Named
... ... @@ -116,7 +116,7 @@ constructor(
/**
* @throws Exception if fails to connect.
*/
suspend fun reconnect(url: String, token: String, participantSid: String?): Either<ReconnectResponse, Unit> {
internal suspend fun reconnect(url: String, token: String, participantSid: String?): Either<ReconnectResponse, Unit> {
val reconnectResponse = connect(
url,
token,
... ... @@ -385,14 +385,21 @@ constructor(
cid: String,
name: String,
type: LivekitModels.TrackType,
stream: String?,
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder(),
) {
val encryptionType = lastRoomOptions?.e2eeOptions?.encryptionType ?: LivekitModels.Encryption.Type.NONE
val addTrackRequest = builder
.setCid(cid)
.setName(name)
.setType(type)
.setEncryption(encryptionType)
val addTrackRequest = builder.apply {
setCid(cid)
setName(name)
setType(type)
if (stream != null) {
setStream(stream)
} else {
clearStream()
}
encryption = encryptionType
}
val request = LivekitRtc.SignalRequest.newBuilder()
.setAddTrack(addTrackRequest)
.build()
... ... @@ -834,4 +841,7 @@ enum class ProtocolVersion(val value: Int) {
v7(7),
v8(8),
v9(9),
v10(10),
v11(11),
v12(12),
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -19,14 +19,14 @@ package io.livekit.android.room
import io.livekit.android.util.LKLog
import io.livekit.android.webrtc.peerconnection.executeOnRTCThread
import livekit.LivekitRtc
import org.webrtc.CandidatePairChangeEvent
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaStream
import org.webrtc.MediaStreamTrack
import org.webrtc.PeerConnection
import org.webrtc.RtpReceiver
import org.webrtc.RtpTransceiver
import livekit.org.webrtc.CandidatePairChangeEvent
import livekit.org.webrtc.DataChannel
import livekit.org.webrtc.IceCandidate
import livekit.org.webrtc.MediaStream
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.RtpTransceiver
/**
* @suppress
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -41,8 +41,8 @@ import livekit.LivekitModels
import livekit.LivekitRtc
import livekit.LivekitRtc.AddTrackRequest
import livekit.LivekitRtc.SimulcastCodec
import org.webrtc.*
import org.webrtc.RtpTransceiver.RtpTransceiverInit
import livekit.org.webrtc.*
import livekit.org.webrtc.RtpTransceiver.RtpTransceiverInit
import javax.inject.Named
import kotlin.math.max
... ... @@ -62,16 +62,16 @@ internal constructor(
coroutineDispatcher: CoroutineDispatcher,
@Named(InjectionNames.SENDER)
private val capabilitiesGetter: CapabilitiesGetter,
) : Participant("", null, coroutineDispatcher) {
) : Participant(Sid(""), null, coroutineDispatcher) {
var audioTrackCaptureDefaults: LocalAudioTrackOptions by defaultsManager::audioTrackCaptureDefaults
var audioTrackPublishDefaults: AudioTrackPublishDefaults by defaultsManager::audioTrackPublishDefaults
var videoTrackCaptureDefaults: LocalVideoTrackOptions by defaultsManager::videoTrackCaptureDefaults
var videoTrackPublishDefaults: VideoTrackPublishDefaults by defaultsManager::videoTrackPublishDefaults
var republishes: List<LocalTrackPublication>? = null
private var republishes: List<LocalTrackPublication>? = null
private val localTrackPublications
get() = tracks.values
get() = trackPublications.values
.mapNotNull { it as? LocalTrackPublication }
.toList()
... ... @@ -259,7 +259,7 @@ internal constructor(
requestConfig = {
disableDtx = !options.dtx
disableRed = !options.red
source = LivekitModels.TrackSource.MICROPHONE
source = options.source?.toProto() ?: LivekitModels.TrackSource.MICROPHONE
},
encodings = encodings,
publishListener = publishListener,
... ... @@ -295,7 +295,7 @@ internal constructor(
requestConfig = {
width = track.dimensions.width
height = track.dimensions.height
source = if (track.options.isScreencast) {
source = options.source?.toProto() ?: if (track.options.isScreencast) {
LivekitModels.TrackSource.SCREEN_SHARE
} else {
LivekitModels.TrackSource.CAMERA
... ... @@ -350,8 +350,9 @@ internal constructor(
}
val trackInfo = engine.addTrack(
cid = cid,
name = track.name,
name = options.name ?: track.name,
kind = track.kind.toProto(),
stream = options.stream,
builder = builder,
)
... ... @@ -374,7 +375,7 @@ internal constructor(
val transInit = RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(this.sid),
listOf(this.sid.value),
encodings,
)
val transceiver = engine.createSenderTransceiver(track.rtcTrack, transInit)
... ... @@ -552,7 +553,7 @@ internal constructor(
}
val sid = publication.sid
tracks = tracks.toMutableMap().apply { remove(sid) }
trackPublications = trackPublications.toMutableMap().apply { remove(sid) }
if (engine.connectionState == ConnectionState.CONNECTED) {
engine.removeTrack(track.rtcTrack)
... ... @@ -570,15 +571,15 @@ internal constructor(
*
* @param data payload to send
* @param reliability for delivery guarantee, use RELIABLE. for fastest delivery without guarantee, use LOSSY
* @param destination list of participant SIDs to deliver the payload, null to deliver to everyone
* @param topic the topic under which the message was published
* @param identities list of participant identities to deliver the payload, null to deliver to everyone
*/
@Suppress("unused")
suspend fun publishData(
data: ByteArray,
reliability: DataPublishReliability = DataPublishReliability.RELIABLE,
destination: List<String>? = null,
topic: String? = null,
identities: List<Identity>? = null,
) {
if (data.size > RTCEngine.MAX_DATA_PACKET_SIZE) {
throw IllegalArgumentException("cannot publish data larger than " + RTCEngine.MAX_DATA_PACKET_SIZE)
... ... @@ -590,12 +591,12 @@ internal constructor(
}
val packetBuilder = LivekitModels.UserPacket.newBuilder().apply {
payload = ByteString.copyFrom(data)
participantSid = sid
participantSid = sid.value
if (topic != null) {
setTopic(topic)
}
if (destination != null) {
addAllDestinationSids(destination)
if (identities != null) {
addAllDestinationIdentities(identities.map { it.value })
}
}
val dataPacket = LivekitModels.DataPacket.newBuilder()
... ... @@ -611,10 +612,10 @@ internal constructor(
// detect tracks that have mute status mismatched on server
for (ti in info.tracksList) {
val publication = this.tracks[ti.sid] as? LocalTrackPublication ?: continue
val publication = this.trackPublications[ti.sid] as? LocalTrackPublication ?: continue
val localMuted = publication.muted
if (ti.muted != localMuted) {
engine.updateMuteStatus(sid, localMuted)
engine.updateMuteStatus(sid.value, localMuted)
}
}
}
... ... @@ -640,7 +641,7 @@ internal constructor(
}
internal fun onRemoteMuteChanged(trackSid: String, muted: Boolean) {
val pub = tracks[trackSid]
val pub = trackPublications[trackSid]
pub?.muted = muted
}
... ... @@ -652,7 +653,7 @@ internal constructor(
val trackSid = subscribedQualityUpdate.trackSid
val subscribedCodecs = subscribedQualityUpdate.subscribedCodecsList
val qualities = subscribedQualityUpdate.subscribedQualitiesList
val pub = tracks[trackSid] as? LocalTrackPublication ?: return
val pub = trackPublications[trackSid] as? LocalTrackPublication ?: return
val track = pub.track as? LocalVideoTrack ?: return
val options = pub.options as? VideoTrackPublishOptions ?: return
... ... @@ -671,7 +672,7 @@ internal constructor(
}
private fun publishAdditionalCodecForTrack(track: LocalVideoTrack, codec: VideoCodec, options: VideoTrackPublishOptions) {
val existingPublication = tracks[track.sid] ?: run {
val existingPublication = trackPublications[track.sid] ?: run {
LKLog.w { "attempting to publish additional codec for non-published track?!" }
return
}
... ... @@ -685,7 +686,7 @@ internal constructor(
val transceiverInit = RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(this.sid),
listOf(this.sid.value),
newEncodings,
)
... ... @@ -725,6 +726,7 @@ internal constructor(
cid = simulcastTrack.rtcTrack.id(),
name = existingPublication.name,
kind = existingPublication.kind.toProto(),
stream = options.stream,
builder = trackRequest,
)
... ... @@ -735,7 +737,7 @@ internal constructor(
}
internal fun handleLocalTrackUnpublished(unpublishedResponse: LivekitRtc.TrackUnpublishedResponse) {
val pub = tracks[unpublishedResponse.trackSid]
val pub = trackPublications[unpublishedResponse.trackSid]
val track = pub?.track
if (track == null) {
LKLog.w { "Received unpublished track response for unknown or non-published track: ${unpublishedResponse.trackSid}" }
... ... @@ -753,7 +755,7 @@ internal constructor(
republishes = pubs
}
tracks = tracks.toMutableMap().apply { clear() }
trackPublications = trackPublications.toMutableMap().apply { clear() }
for (publication in pubs) {
internalListener?.onTrackUnpublished(publication, this)
... ... @@ -780,7 +782,7 @@ internal constructor(
}
fun cleanup() {
for (pub in tracks.values) {
for (pub in trackPublications.values) {
val track = pub.track
if (track != null) {
... ... @@ -810,7 +812,7 @@ internal constructor(
}
internal fun LocalParticipant.publishTracksInfo(): List<LivekitRtc.TrackPublishedResponse> {
return tracks.values.mapNotNull { trackPub ->
return trackPublications.values.mapNotNull { trackPub ->
val track = trackPub.track ?: return@mapNotNull null
LivekitRtc.TrackPublishedResponse.newBuilder()
... ... @@ -821,7 +823,23 @@ internal fun LocalParticipant.publishTracksInfo(): List<LivekitRtc.TrackPublishe
}
interface TrackPublishOptions {
/**
* The name of the track.
*/
val name: String?
/**
* The source of a track, camera, microphone or screen.
*/
val source: Track.Source?
/**
* The stream name for the track. Audio and video tracks with the same stream
* name will be placed in the same `MediaStream` and offer better synchronization.
*
* By default, camera and microphone will be placed in the same stream.
*/
val stream: String?
}
abstract class BaseVideoTrackPublishOptions {
... ... @@ -868,17 +886,23 @@ data class VideoTrackPublishOptions(
override val videoCodec: String = VideoCodec.VP8.codecName,
override val scalabilityMode: String? = null,
override val backupCodec: BackupVideoCodec? = null,
override val source: Track.Source? = null,
override val stream: String? = null,
) : BaseVideoTrackPublishOptions(), TrackPublishOptions {
constructor(
name: String? = null,
base: BaseVideoTrackPublishOptions,
source: Track.Source? = null,
stream: String? = null,
) : this(
name,
base.videoEncoding,
base.simulcast,
base.videoCodec,
base.scalabilityMode,
base.backupCodec,
name = name,
videoEncoding = base.videoEncoding,
simulcast = base.simulcast,
videoCodec = base.videoCodec,
scalabilityMode = base.scalabilityMode,
backupCodec = base.backupCodec,
source = source,
stream = stream,
)
fun createBackupOptions(): VideoTrackPublishOptions? {
... ... @@ -913,25 +937,38 @@ abstract class BaseAudioTrackPublishOptions {
abstract val red: Boolean
}
/**
* Default options for publishing an audio track.
*/
data class AudioTrackPublishDefaults(
override val audioBitrate: Int? = 20_000,
override val dtx: Boolean = true,
override val red: Boolean = true,
) : BaseAudioTrackPublishOptions()
/**
* Options for publishing an audio track.
*/
data class AudioTrackPublishOptions(
override val name: String? = null,
override val audioBitrate: Int? = null,
override val dtx: Boolean = true,
override val red: Boolean = true,
override val source: Track.Source? = null,
override val stream: String? = null,
) : BaseAudioTrackPublishOptions(), TrackPublishOptions {
constructor(
name: String? = null,
base: BaseAudioTrackPublishOptions,
source: Track.Source? = null,
stream: String? = null,
) : this(
name,
base.audioBitrate,
base.dtx,
name = name,
audioBitrate = base.audioBitrate,
dtx = base.dtx,
red = base.red,
source = source,
stream = stream,
)
}
... ... @@ -962,7 +999,7 @@ data class ParticipantTrackPermission(
}
}
fun toProto(): LivekitRtc.TrackPermission {
internal fun toProto(): LivekitRtc.TrackPermission {
return LivekitRtc.TrackPermission.newBuilder()
.setParticipantIdentity(participantIdentity)
.setParticipantSid(participantSid)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -29,17 +29,26 @@ import io.livekit.android.util.flow
import io.livekit.android.util.flowDelegate
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable
import livekit.LivekitModels
import java.util.Date
import javax.inject.Named
open class Participant(
var sid: String,
identity: String? = null,
var sid: Sid,
identity: Identity? = null,
@Named(InjectionNames.DISPATCHER_DEFAULT)
private val coroutineDispatcher: CoroutineDispatcher,
) {
@Serializable
@JvmInline
value class Identity(val value: String)
@Serializable
@JvmInline
value class Sid(val value: String)
/**
* To only be used for flow delegate scoping, and should not be cancelled.
**/
... ... @@ -65,7 +74,7 @@ open class Participant(
*/
@FlowObservable
@get:FlowObservable
var identity: String? by flowDelegate(identity)
var identity: Identity? by flowDelegate(identity)
internal set
/**
... ... @@ -179,7 +188,7 @@ open class Participant(
*/
@FlowObservable
@get:FlowObservable
var tracks by flowDelegate(emptyMap<String, TrackPublication>())
var trackPublications by flowDelegate(emptyMap<String, TrackPublication>())
protected set
private fun Flow<Map<String, TrackPublication>>.trackUpdateFlow(): Flow<List<Pair<TrackPublication, Track?>>> {
... ... @@ -206,8 +215,8 @@ open class Participant(
*/
@FlowObservable
@get:FlowObservable
val audioTracks by flowDelegate(
stateFlow = ::tracks.flow
val audioTrackPublications by flowDelegate(
stateFlow = ::trackPublications.flow
.map { it.filterValues { publication -> publication.kind == Track.Kind.AUDIO } }
.trackUpdateFlow()
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList()),
... ... @@ -218,8 +227,8 @@ open class Participant(
*/
@FlowObservable
@get:FlowObservable
val videoTracks by flowDelegate(
stateFlow = ::tracks.flow
val videoTrackPublications by flowDelegate(
stateFlow = ::trackPublications.flow
.map { it.filterValues { publication -> publication.kind == Track.Kind.VIDEO } }
.trackUpdateFlow()
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList()),
... ... @@ -231,7 +240,7 @@ open class Participant(
fun addTrackPublication(publication: TrackPublication) {
val track = publication.track
track?.sid = publication.sid
tracks = tracks.toMutableMap().apply {
trackPublications = trackPublications.toMutableMap().apply {
this[publication.sid] = publication
}
}
... ... @@ -244,7 +253,7 @@ open class Participant(
return null
}
for ((_, pub) in tracks) {
for ((_, pub) in trackPublications) {
if (pub.source == source) {
return pub
}
... ... @@ -269,7 +278,7 @@ open class Participant(
* Retrieves the first track that matches [name], or null
*/
open fun getTrackPublicationByName(name: String): TrackPublication? {
for ((_, pub) in tracks) {
for ((_, pub) in trackPublications) {
if (pub.name == name) {
return pub
}
... ... @@ -300,8 +309,8 @@ open class Participant(
* @suppress
*/
internal open fun updateFromInfo(info: LivekitModels.ParticipantInfo) {
sid = info.sid
identity = info.identity
sid = Sid(info.sid)
identity = Identity(info.identity)
participantInfo = info
metadata = info.metadata
name = info.name
... ... @@ -339,7 +348,7 @@ open class Participant(
}
internal fun onTrackStreamStateChanged(trackEvent: TrackEvent.StreamStateChanged) {
val trackPublication = tracks[trackEvent.track.sid] ?: return
val trackPublication = trackPublications[trackEvent.track.sid] ?: return
eventBus.postEvent(
ParticipantEvent.TrackStreamStateChanged(this, trackPublication, trackEvent.streamState),
scope,
... ... @@ -355,7 +364,7 @@ open class Participant(
internal open fun dispose() {
scope.cancel()
sid = ""
sid = Sid("")
name = null
identity = null
metadata = null
... ... @@ -455,6 +464,7 @@ enum class ConnectionQuality {
GOOD,
POOR,
UNKNOWN,
LOST,
;
companion object {
... ... @@ -464,6 +474,7 @@ enum class ConnectionQuality {
LivekitModels.ConnectionQuality.GOOD -> GOOD
LivekitModels.ConnectionQuality.POOR -> POOR
LivekitModels.ConnectionQuality.UNRECOGNIZED -> UNKNOWN
LivekitModels.ConnectionQuality.LOST -> LOST
}
}
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.room.participant
fun String.mimeTypeToVideoCodec(): String? {
internal fun String.mimeTypeToVideoCodec(): String? {
return split("/")
.takeIf { length > 1 }
?.get(1)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -32,14 +32,14 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import livekit.LivekitModels
import livekit.LivekitRtc
import org.webrtc.AudioTrack
import org.webrtc.MediaStreamTrack
import org.webrtc.RtpReceiver
import org.webrtc.VideoTrack
import livekit.org.webrtc.AudioTrack
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.VideoTrack
class RemoteParticipant(
sid: String,
identity: String? = null,
sid: Sid,
identity: Identity? = null,
val signalClient: SignalClient,
private val ioDispatcher: CoroutineDispatcher,
defaultDispatcher: CoroutineDispatcher,
... ... @@ -57,8 +57,8 @@ class RemoteParticipant(
ioDispatcher: CoroutineDispatcher,
defaultDispatcher: CoroutineDispatcher,
) : this(
info.sid,
info.identity,
Sid(info.sid),
Identity(info.identity),
signalClient,
ioDispatcher,
defaultDispatcher,
... ... @@ -68,7 +68,7 @@ class RemoteParticipant(
private val coroutineScope = CloseableCoroutineScope(defaultDispatcher + SupervisorJob())
fun getTrackPublication(sid: String): RemoteTrackPublication? = tracks[sid] as? RemoteTrackPublication
fun getTrackPublication(sid: String): RemoteTrackPublication? = trackPublications[sid] as? RemoteTrackPublication
/**
* @suppress
... ... @@ -105,9 +105,9 @@ class RemoteParticipant(
eventBus.postEvent(ParticipantEvent.TrackPublished(this, publication), scope)
}
val invalidKeys = tracks.keys - validTrackPublication.keys
val invalidKeys = trackPublications.keys - validTrackPublication.keys
for (invalidKey in invalidKeys) {
val publication = tracks[invalidKey] ?: continue
val publication = trackPublications[invalidKey] ?: continue
unpublishTrack(publication.sid, true)
}
}
... ... @@ -175,8 +175,8 @@ class RemoteParticipant(
}
fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) {
val publication = tracks[trackSid] as? RemoteTrackPublication ?: return
tracks = tracks.toMutableMap().apply { remove(trackSid) }
val publication = trackPublications[trackSid] as? RemoteTrackPublication ?: return
trackPublications = trackPublications.toMutableMap().apply { remove(trackSid) }
val track = publication.track
if (track != null) {
... ... @@ -198,7 +198,7 @@ class RemoteParticipant(
}
internal fun onSubscriptionPermissionUpdate(subscriptionPermissionUpdate: LivekitRtc.SubscriptionPermissionUpdate) {
val pub = tracks[subscriptionPermissionUpdate.trackSid] as? RemoteTrackPublication ?: return
val pub = trackPublications[subscriptionPermissionUpdate.trackSid] as? RemoteTrackPublication ?: return
if (pub.subscriptionAllowed != subscriptionPermissionUpdate.allowed) {
pub.subscriptionAllowed = subscriptionPermissionUpdate.allowed
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,16 @@
package io.livekit.android.room.track
import org.webrtc.AudioTrack
import livekit.org.webrtc.AudioTrack
abstract class AudioTrack(name: String, override val rtcTrack: AudioTrack) :
/**
* A class representing an audio track.
*/
abstract class AudioTrack(
name: String,
/**
* The underlying WebRTC audio track.
*/
override val rtcTrack: AudioTrack
) :
Track(name, Kind.AUDIO, rtcTrack)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,10 +21,10 @@ import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread
import org.webrtc.MediaConstraints
import org.webrtc.PeerConnectionFactory
import org.webrtc.RtpSender
import org.webrtc.RtpTransceiver
import livekit.org.webrtc.MediaConstraints
import livekit.org.webrtc.PeerConnectionFactory
import livekit.org.webrtc.RtpSender
import livekit.org.webrtc.RtpTransceiver
import java.util.*
/**
... ... @@ -34,7 +34,7 @@ import java.util.*
*/
class LocalAudioTrack(
name: String,
mediaTrack: org.webrtc.AudioTrack
mediaTrack: livekit.org.webrtc.AudioTrack
) : AudioTrack(name, mediaTrack) {
var enabled: Boolean
get() = executeBlockingOnRTCThread { rtcTrack.enabled() }
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -28,7 +28,7 @@ import dagger.assisted.AssistedInject
import io.livekit.android.room.DefaultsManager
import io.livekit.android.room.track.screencapture.ScreenCaptureConnection
import io.livekit.android.room.track.screencapture.ScreenCaptureService
import org.webrtc.*
import livekit.org.webrtc.*
import java.util.*
class LocalScreencastVideoTrack
... ... @@ -38,7 +38,7 @@ constructor(
@Assisted source: VideoSource,
@Assisted name: String,
@Assisted options: LocalVideoTrackOptions,
@Assisted rtcTrack: org.webrtc.VideoTrack,
@Assisted rtcTrack: livekit.org.webrtc.VideoTrack,
@Assisted mediaProjectionCallback: MediaProjectionCallback,
peerConnectionFactory: PeerConnectionFactory,
context: Context,
... ... @@ -104,7 +104,7 @@ constructor(
source: VideoSource,
name: String,
options: LocalVideoTrackOptions,
rtcTrack: org.webrtc.VideoTrack,
rtcTrack: livekit.org.webrtc.VideoTrack,
mediaProjectionCallback: MediaProjectionCallback,
): LocalScreencastVideoTrack
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -37,22 +37,22 @@ import io.livekit.android.util.FlowObservable
import io.livekit.android.util.LKLog
import io.livekit.android.util.flowDelegate
import livekit.LivekitModels
import livekit.LivekitModels.VideoQuality
import livekit.LivekitRtc
import livekit.LivekitRtc.SubscribedCodec
import org.webrtc.CameraVideoCapturer
import org.webrtc.CameraVideoCapturer.CameraEventsHandler
import org.webrtc.EglBase
import org.webrtc.MediaStreamTrack
import org.webrtc.PeerConnectionFactory
import org.webrtc.RtpParameters
import org.webrtc.RtpSender
import org.webrtc.RtpTransceiver
import org.webrtc.SurfaceTextureHelper
import org.webrtc.VideoCapturer
import org.webrtc.VideoProcessor
import org.webrtc.VideoSource
import livekit.org.webrtc.CameraVideoCapturer
import livekit.org.webrtc.CameraVideoCapturer.CameraEventsHandler
import livekit.org.webrtc.EglBase
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.PeerConnectionFactory
import livekit.org.webrtc.RtpParameters
import livekit.org.webrtc.RtpSender
import livekit.org.webrtc.RtpTransceiver
import livekit.org.webrtc.SurfaceTextureHelper
import livekit.org.webrtc.VideoCapturer
import livekit.org.webrtc.VideoProcessor
import livekit.org.webrtc.VideoSource
import java.util.UUID
import livekit.LivekitModels.VideoQuality as ProtoVideoQuality
/**
* A representation of a local video track (generally input coming from camera or screen).
... ... @@ -66,7 +66,7 @@ constructor(
@Assisted private var source: VideoSource,
@Assisted name: String,
@Assisted options: LocalVideoTrackOptions,
@Assisted rtcTrack: org.webrtc.VideoTrack,
@Assisted rtcTrack: livekit.org.webrtc.VideoTrack,
private val peerConnectionFactory: PeerConnectionFactory,
private val context: Context,
private val eglBase: EglBase,
... ... @@ -74,7 +74,7 @@ constructor(
private val trackFactory: Factory,
) : VideoTrack(name, rtcTrack) {
override var rtcTrack: org.webrtc.VideoTrack = rtcTrack
override var rtcTrack: livekit.org.webrtc.VideoTrack = rtcTrack
internal set
internal var codec: String? = null
... ... @@ -274,14 +274,14 @@ constructor(
if (encodings.firstOrNull()?.scalabilityMode != null) {
val encoding = encodings.first()
var maxQuality = VideoQuality.OFF
var maxQuality = ProtoVideoQuality.OFF
for (quality in qualities) {
if (quality.enabled && (maxQuality == VideoQuality.OFF || quality.quality.number > maxQuality.number)) {
if (quality.enabled && (maxQuality == ProtoVideoQuality.OFF || quality.quality.number > maxQuality.number)) {
maxQuality = quality.quality
}
}
if (maxQuality == VideoQuality.OFF) {
if (maxQuality == ProtoVideoQuality.OFF) {
if (encoding.active) {
LKLog.v { "setting svc track to disabled" }
encoding.active = false
... ... @@ -379,7 +379,7 @@ constructor(
source: VideoSource,
name: String,
options: LocalVideoTrackOptions,
rtcTrack: org.webrtc.VideoTrack,
rtcTrack: livekit.org.webrtc.VideoTrack,
): LocalVideoTrack
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.room.track
import org.webrtc.RtpParameters
import livekit.org.webrtc.RtpParameters
data class LocalVideoTrackOptions(
val isScreencast: Boolean = false,
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,9 +17,9 @@
package io.livekit.android.room.track
import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread
import org.webrtc.AudioTrack
import org.webrtc.AudioTrackSink
import org.webrtc.RtpReceiver
import livekit.org.webrtc.AudioTrack
import livekit.org.webrtc.AudioTrackSink
import livekit.org.webrtc.RtpReceiver
class RemoteAudioTrack(
name: String,
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -34,7 +34,6 @@ class RemoteTrackPublication(
private val ioDispatcher: CoroutineDispatcher,
) : TrackPublication(info, track, participant) {
@OptIn(FlowPreview::class)
override var track: Track?
get() = super.track
set(value) {
... ... @@ -67,7 +66,7 @@ class RemoteTrackPublication(
private var unsubscribed: Boolean = false
private var disabled: Boolean = false
private var videoQuality: LivekitModels.VideoQuality? = LivekitModels.VideoQuality.HIGH
private var videoQuality: VideoQuality? = VideoQuality.HIGH
private var videoDimensions: Track.Dimensions? = null
private var fps: Int? = null
... ... @@ -123,7 +122,7 @@ class RemoteTrackPublication(
unsubscribed = !subscribed
val participant = this.participant.get() as? RemoteParticipant ?: return
val participantTracks = with(LivekitModels.ParticipantTracks.newBuilder()) {
participantSid = participant.sid
participantSid = participant.sid.value
addTrackSids(sid)
build()
}
... ... @@ -152,7 +151,7 @@ class RemoteTrackPublication(
*
* Will override previous calls to [setVideoDimensions].
*/
fun setVideoQuality(quality: LivekitModels.VideoQuality) {
fun setVideoQuality(quality: VideoQuality) {
if (isAutoManaged ||
!subscribed ||
quality == videoQuality ||
... ... @@ -223,7 +222,7 @@ class RemoteTrackPublication(
val participant = this.participant.get() as? RemoteParticipant ?: return
val rtcTrack = track?.rtcTrack
if (rtcTrack is org.webrtc.VideoTrack) {
if (rtcTrack is livekit.org.webrtc.VideoTrack) {
rtcTrack.setShouldReceive(!disabled)
}
... ... @@ -231,7 +230,7 @@ class RemoteTrackPublication(
sid,
disabled,
videoDimensions,
videoQuality,
videoQuality?.toProto(),
fps,
)
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -23,14 +23,14 @@ import io.livekit.android.room.track.video.VideoSinkVisibility
import io.livekit.android.room.track.video.ViewVisibility
import io.livekit.android.util.LKLog
import kotlinx.coroutines.*
import org.webrtc.RtpReceiver
import org.webrtc.VideoSink
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.VideoSink
import javax.inject.Named
import kotlin.math.max
class RemoteVideoTrack(
name: String,
rtcTrack: org.webrtc.VideoTrack,
rtcTrack: livekit.org.webrtc.VideoTrack,
val autoManageVideo: Boolean = false,
@Named(InjectionNames.DISPATCHER_DEFAULT)
private val dispatcher: CoroutineDispatcher,
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -24,9 +24,9 @@ import io.livekit.android.webrtc.getStats
import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread
import livekit.LivekitModels
import livekit.LivekitRtc
import org.webrtc.MediaStreamTrack
import org.webrtc.RTCStatsCollectorCallback
import org.webrtc.RTCStatsReport
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.RTCStatsCollectorCallback
import livekit.org.webrtc.RTCStatsReport
abstract class Track(
name: String,
... ...
/*
* Copyright 2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.livekit.android.room.track
import livekit.LivekitModels
enum class VideoQuality {
LOW,
MEDIUM,
HIGH,
;
fun toProto(): LivekitModels.VideoQuality {
return when (this) {
LOW -> LivekitModels.VideoQuality.LOW
MEDIUM -> LivekitModels.VideoQuality.MEDIUM
HIGH -> LivekitModels.VideoQuality.HIGH
}
}
companion object {
fun fromProto(quality: LivekitModels.VideoQuality): VideoQuality? {
return when (quality) {
LivekitModels.VideoQuality.LOW -> LOW
LivekitModels.VideoQuality.MEDIUM -> MEDIUM
LivekitModels.VideoQuality.HIGH -> HIGH
LivekitModels.VideoQuality.OFF -> null
LivekitModels.VideoQuality.UNRECOGNIZED -> null
}
}
}
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,8 +17,8 @@
package io.livekit.android.room.track
import io.livekit.android.webrtc.peerconnection.executeBlockingOnRTCThread
import org.webrtc.VideoSink
import org.webrtc.VideoTrack
import livekit.org.webrtc.VideoSink
import livekit.org.webrtc.VideoTrack
abstract class VideoTrack(name: String, override val rtcTrack: VideoTrack) :
Track(name, Kind.VIDEO, rtcTrack) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -33,10 +33,10 @@ import androidx.core.app.NotificationCompat
* This a simple default foreground service to display a notification while screen
* capturing.
*/
open class ScreenCaptureService : Service() {
private var binder: IBinder = ScreenCaptureBinder()
private var bindCount = 0
override fun onBind(intent: Intent?): IBinder {
bindCount++
return binder
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,9 +22,9 @@ import android.graphics.Matrix
import android.graphics.Paint
import android.os.Build
import android.view.Surface
import org.webrtc.CapturerObserver
import org.webrtc.SurfaceTextureHelper
import org.webrtc.VideoCapturer
import livekit.org.webrtc.CapturerObserver
import livekit.org.webrtc.SurfaceTextureHelper
import livekit.org.webrtc.VideoCapturer
/**
* A [VideoCapturer] that can be manually driven by passing in [Bitmap].
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -23,14 +23,17 @@ import android.hardware.camera2.CameraManager
import io.livekit.android.room.track.CameraPosition
import io.livekit.android.room.track.LocalVideoTrackOptions
import io.livekit.android.util.LKLog
import org.webrtc.Camera1Capturer
import org.webrtc.Camera1Enumerator
import org.webrtc.Camera1Helper
import org.webrtc.Camera2Capturer
import org.webrtc.Camera2Enumerator
import org.webrtc.CameraEnumerator
import org.webrtc.VideoCapturer
import livekit.org.webrtc.Camera1Capturer
import livekit.org.webrtc.Camera1Enumerator
import livekit.org.webrtc.Camera1Helper
import livekit.org.webrtc.Camera2Capturer
import livekit.org.webrtc.Camera2Enumerator
import livekit.org.webrtc.CameraEnumerator
import livekit.org.webrtc.VideoCapturer
/**
* Various utils for handling camera capturers.
*/
object CameraCapturerUtils {
/**
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.room.track.video
import org.webrtc.CameraVideoCapturer.CameraEventsHandler
import livekit.org.webrtc.CameraVideoCapturer.CameraEventsHandler
/**
* Dispatches CameraEventsHandler callbacks to registered handlers.
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -44,8 +44,16 @@ class ComposeVisibility : VideoSinkVisibility() {
return Track.Dimensions(width, height)
}
// Note, LayoutCoordinates are mutable and may be reused.
/**
* To be called from a compose view, using `Modifier.onGloballyPositioned`.
*
* Example:
* ```
* modifier = Modifier.onGloballyPositioned { videoSinkVisibility.onGloballyPositioned(it) }
* ```
*/
fun onGloballyPositioned(layoutCoordinates: LayoutCoordinates) {
// Note, LayoutCoordinates are mutable and may be reused.
coordinates = layoutCoordinates
val visible = isVisible()
val size = size()
... ... @@ -58,6 +66,18 @@ class ComposeVisibility : VideoSinkVisibility() {
lastSize = size
}
/**
* To be called when the associated compose view no longer exists.
*
* Example:
* ```
* DisposableEffect(room, videoTrack) {
* onDispose {
* videoSinkVisibility.onDispose()
* }
* }
* ```
*/
fun onDispose() {
if (coordinates == null) {
return
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,7 +17,7 @@
package io.livekit.android.room.track.video
import android.hardware.camera2.CameraManager
import org.webrtc.*
import livekit.org.webrtc.*
/**
* @suppress
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -32,7 +32,14 @@ import java.util.Observable
* is used.
*/
abstract class VideoSinkVisibility : Observable() {
/**
* @return whether this VideoSink is visible or not.
*/
abstract fun isVisible(): Boolean
/**
* @return the dimensions of this VideoSink.
*/
abstract fun size(): Track.Dimensions
/**
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,15 +21,15 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.webrtc.MediaConstraints
import org.webrtc.PeerConnection
import org.webrtc.SdpObserver
import org.webrtc.SessionDescription
import livekit.org.webrtc.MediaConstraints
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.SdpObserver
import livekit.org.webrtc.SessionDescription
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
open class CoroutineSdpObserver : SdpObserver {
internal open class CoroutineSdpObserver : SdpObserver {
private val stateLock = Mutex()
private var createOutcome: Either<SessionDescription, String?>? = null
... ... @@ -136,25 +136,25 @@ open class CoroutineSdpObserver : SdpObserver {
}
}
suspend fun PeerConnection.createOffer(constraints: MediaConstraints): Either<SessionDescription, String?> {
internal suspend fun PeerConnection.createOffer(constraints: MediaConstraints): Either<SessionDescription, String?> {
val observer = CoroutineSdpObserver()
this.createOffer(observer, constraints)
return observer.awaitCreate()
}
suspend fun PeerConnection.createAnswer(constraints: MediaConstraints): Either<SessionDescription, String?> {
internal suspend fun PeerConnection.createAnswer(constraints: MediaConstraints): Either<SessionDescription, String?> {
val observer = CoroutineSdpObserver()
this.createAnswer(observer, constraints)
return observer.awaitCreate()
}
suspend fun PeerConnection.setRemoteDescription(description: SessionDescription): Either<Unit, String?> {
internal suspend fun PeerConnection.setRemoteDescription(description: SessionDescription): Either<Unit, String?> {
val observer = CoroutineSdpObserver()
this.setRemoteDescription(observer, description)
return observer.awaitSet()
}
suspend fun PeerConnection.setLocalDescription(description: SessionDescription): Either<Unit, String?> {
internal suspend fun PeerConnection.setLocalDescription(description: SessionDescription): Either<Unit, String?> {
val observer = CoroutineSdpObserver()
this.setLocalDescription(observer, description)
return observer.awaitSet()
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,7 +22,7 @@ import io.livekit.android.room.track.VideoPreset169
import io.livekit.android.room.track.VideoPreset43
import io.livekit.android.room.track.video.ScalabilityMode
import livekit.LivekitModels
import org.webrtc.RtpParameters
import livekit.org.webrtc.RtpParameters
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.room.util
import org.webrtc.MediaConstraints
import livekit.org.webrtc.MediaConstraints
object MediaConstraintKeys {
const val OFFER_TO_RECV_AUDIO = "OfferToReceiveAudio"
... ...
/*
* Copyright 2023 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.livekit.android.room.util
import org.webrtc.DataChannel
fun DataChannel.unpackedTrackLabel(): Triple<String, String, String> {
val parts = label().split("|")
val participantSid: String
val trackSid: String
val name: String
if (parts.count() == 3) {
participantSid = parts[0]
trackSid = parts[1]
name = parts[2]
} else {
participantSid = ""
trackSid = ""
name = ""
}
return Triple(participantSid, trackSid, name)
}
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -25,7 +25,7 @@ interface NetworkInfo {
fun getNetworkType(): NetworkType
}
class AndroidNetworkInfo(private val context: Context) : NetworkInfo {
internal class AndroidNetworkInfo(private val context: Context) : NetworkInfo {
override fun getNetworkType(): NetworkType {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return NetworkType.UNKNOWN
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,7 +18,7 @@ package io.livekit.android.util
import kotlinx.coroutines.*
fun <T, R> debounce(
internal fun <T, R> debounce(
waitMs: Long = 300L,
coroutineScope: CoroutineScope,
destinationFunction: suspend (T) -> R,
... ... @@ -33,6 +33,6 @@ fun <T, R> debounce(
}
}
fun <R> ((Unit) -> R).invoke() {
internal fun <R> ((Unit) -> R).invoke() {
this.invoke(Unit)
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,11 +16,11 @@
package io.livekit.android.util
sealed class Either<out A, out B> {
internal sealed class Either<out A, out B> {
class Left<out A>(val value: A) : Either<A, Nothing>()
class Right<out B>(val value: B) : Either<Nothing, B>()
}
fun <A> Either<A, String?>?.nullSafe(): Either<A, String?> {
internal fun <A> Either<A, String?>?.nullSafe(): Either<A, String?> {
return this ?: Either.Right("null")
}
... ...
... ... @@ -57,6 +57,10 @@ internal val <T> KProperty0<T>.delegate: Any?
}
}
/**
* @return the flow associated with a [FlowObservable] property,
* which can be collected upon to observe changes in the value.
*/
@Suppress("UNCHECKED_CAST")
val <T> KProperty0<T>.flow: StateFlow<T>
get() = delegate as StateFlow<T>
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -20,7 +20,7 @@ import com.google.protobuf.MessageLite
import okio.ByteString
import okio.ByteString.Companion.toByteString
fun MessageLite.toOkioByteString(): ByteString {
internal fun MessageLite.toOkioByteString(): ByteString {
val byteArray = toByteArray()
return byteArray.toByteString(0, byteArray.size)
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,12 +16,12 @@
package io.livekit.android.webrtc
import org.webrtc.EglBase
import org.webrtc.SoftwareVideoDecoderFactory
import org.webrtc.VideoCodecInfo
import org.webrtc.VideoDecoder
import org.webrtc.VideoDecoderFactory
import org.webrtc.WrappedVideoDecoderFactory
import livekit.org.webrtc.EglBase
import livekit.org.webrtc.SoftwareVideoDecoderFactory
import livekit.org.webrtc.VideoCodecInfo
import livekit.org.webrtc.VideoDecoder
import livekit.org.webrtc.VideoDecoderFactory
import livekit.org.webrtc.WrappedVideoDecoderFactory
open class CustomVideoDecoderFactory(
sharedContext: EglBase.Context?,
... ... @@ -31,10 +31,16 @@ open class CustomVideoDecoderFactory(
private val softwareVideoDecoderFactory = SoftwareVideoDecoderFactory()
private val wrappedVideoDecoderFactory = WrappedVideoDecoderFactory(sharedContext)
/**
* Set to true to force software codecs.
*/
fun setForceSWCodec(forceSWCodec: Boolean) {
this.forceSWCodec = forceSWCodec
}
/**
* Set a list of codecs for which to use software codecs.
*/
fun setForceSWCodecList(forceSWCodecs: List<String>) {
this.forceSWCodecs = forceSWCodecs
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,11 +16,11 @@
package io.livekit.android.webrtc
import org.webrtc.EglBase
import org.webrtc.SoftwareVideoEncoderFactory
import org.webrtc.VideoCodecInfo
import org.webrtc.VideoEncoder
import org.webrtc.VideoEncoderFactory
import livekit.org.webrtc.EglBase
import livekit.org.webrtc.SoftwareVideoEncoderFactory
import livekit.org.webrtc.VideoCodecInfo
import livekit.org.webrtc.VideoEncoder
import livekit.org.webrtc.VideoEncoderFactory
open class CustomVideoEncoderFactory(
sharedContext: EglBase.Context?,
... ... @@ -37,10 +37,16 @@ open class CustomVideoEncoderFactory(
SimulcastVideoEncoderFactoryWrapper(sharedContext, enableIntelVp8Encoder, enableH264HighProfile)
}
/**
* Set to true to force software codecs.
*/
fun setForceSWCodec(forceSWCodec: Boolean) {
this.forceSWCodec = forceSWCodec
}
/**
* Set a list of codecs for which to use software codecs.
*/
fun setForceSWCodecList(forceSWCodecs: List<String>) {
this.forceSWCodecs = forceSWCodecs
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -20,9 +20,9 @@ import android.gov.nist.javax.sdp.fields.AttributeField
import android.javax.sdp.MediaDescription
import io.livekit.android.util.LKLog
data class SdpRtp(val payload: Long, val codec: String, val rate: Long?, val encoding: String?)
internal data class SdpRtp(val payload: Long, val codec: String, val rate: Long?, val encoding: String?)
fun MediaDescription.getRtps(): List<Pair<AttributeField, SdpRtp>> {
internal fun MediaDescription.getRtps(): List<Pair<AttributeField, SdpRtp>> {
return getAttributes(true)
.filterIsInstance<AttributeField>()
.filter { it.attribute.name == "rtpmap" }
... ... @@ -37,23 +37,23 @@ fun MediaDescription.getRtps(): List<Pair<AttributeField, SdpRtp>> {
}
private val RTP = """(\d*) ([\w\-.]*)(?:\s*/(\d*)(?:\s*/(\S*))?)?""".toRegex()
fun tryParseRtp(string: String): SdpRtp? {
internal fun tryParseRtp(string: String): SdpRtp? {
val match = RTP.matchEntire(string) ?: return null
val (payload, codec, rate, encoding) = match.destructured
return SdpRtp(payload.toLong(), codec, toOptionalLong(rate), toOptionalString(encoding))
}
data class SdpMsid(
internal data class SdpMsid(
/** holds the msid-id (and msid-appdata if available) */
val value: String,
)
fun MediaDescription.getMsid(): SdpMsid? {
internal fun MediaDescription.getMsid(): SdpMsid? {
val attribute = getAttribute("msid") ?: return null
return SdpMsid(attribute)
}
data class SdpFmtp(val payload: Long, val config: String) {
internal data class SdpFmtp(val payload: Long, val config: String) {
fun toAttributeField(): AttributeField {
return AttributeField().apply {
name = "fmtp"
... ... @@ -62,7 +62,7 @@ data class SdpFmtp(val payload: Long, val config: String) {
}
}
fun MediaDescription.getFmtps(): List<Pair<AttributeField, SdpFmtp>> {
internal fun MediaDescription.getFmtps(): List<Pair<AttributeField, SdpFmtp>> {
return getAttributes(true)
.filterIsInstance<AttributeField>()
.filter { it.attribute.name == "fmtp" }
... ... @@ -77,13 +77,13 @@ fun MediaDescription.getFmtps(): List<Pair<AttributeField, SdpFmtp>> {
}
private val FMTP = """(\d*) ([\S| ]*)""".toRegex()
fun tryParseFmtp(string: String): SdpFmtp? {
internal fun tryParseFmtp(string: String): SdpFmtp? {
val match = FMTP.matchEntire(string) ?: return null
val (payload, config) = match.destructured
return SdpFmtp(payload.toLong(), config)
}
data class SdpExt(val value: Long, val direction: String?, val encryptUri: String?, val uri: String, val config: String?) {
internal data class SdpExt(val value: Long, val direction: String?, val encryptUri: String?, val uri: String, val config: String?) {
fun toAttributeField(): AttributeField {
return AttributeField().apply {
name = "extmap"
... ... @@ -104,7 +104,7 @@ data class SdpExt(val value: Long, val direction: String?, val encryptUri: Strin
}
}
fun MediaDescription.getExts(): List<Pair<AttributeField, SdpExt>> {
internal fun MediaDescription.getExts(): List<Pair<AttributeField, SdpExt>> {
return getAttributes(true)
.filterIsInstance<AttributeField>()
.filter { it.attribute.name == "extmap" }
... ... @@ -119,11 +119,11 @@ fun MediaDescription.getExts(): List<Pair<AttributeField, SdpExt>> {
}
private val EXT = """(\d+)(?:/(\w+))?(?: (urn:ietf:params:rtp-hdrext:encrypt))? (\S*)(?: (\S*))?""".toRegex()
fun tryParseExt(string: String): SdpExt? {
internal fun tryParseExt(string: String): SdpExt? {
val match = EXT.matchEntire(string) ?: return null
val (value, direction, encryptUri, uri, config) = match.destructured
return SdpExt(value.toLong(), toOptionalString(direction), toOptionalString(encryptUri), uri, toOptionalString(config))
}
fun toOptionalLong(str: String): Long? = if (str.isEmpty()) null else str.toLong()
fun toOptionalString(str: String): String? = str.ifEmpty { null }
internal fun toOptionalLong(str: String): Long? = if (str.isEmpty()) null else str.toLong()
internal fun toOptionalString(str: String): String? = str.ifEmpty { null }
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,8 +16,8 @@
package io.livekit.android.webrtc
import org.webrtc.PeerConnection
import org.webrtc.PeerConnection.RTCConfiguration
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.PeerConnection.RTCConfiguration
/**
* Completed state is a valid state for a connected connection, so this should be used
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,10 +18,10 @@ package io.livekit.android.webrtc
import io.livekit.android.util.LKLog
import kotlinx.coroutines.suspendCancellableCoroutine
import org.webrtc.MediaStreamTrack
import org.webrtc.RTCStats
import org.webrtc.RTCStatsCollectorCallback
import org.webrtc.RTCStatsReport
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.RTCStats
import livekit.org.webrtc.RTCStatsCollectorCallback
import livekit.org.webrtc.RTCStatsReport
import kotlin.coroutines.resume
/**
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,9 +18,9 @@ package io.livekit.android.webrtc
import io.livekit.android.dagger.CapabilitiesGetter
import io.livekit.android.util.LKLog
import org.webrtc.MediaStreamTrack
import org.webrtc.RtpCapabilities
import org.webrtc.RtpTransceiver
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.RtpCapabilities
import livekit.org.webrtc.RtpTransceiver
internal fun RtpTransceiver.sortVideoCodecPreferences(targetCodec: String, capabilitiesGetter: CapabilitiesGetter) {
val capabilities = capabilitiesGetter(MediaStreamTrack.MediaType.MEDIA_TYPE_VIDEO)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,9 +17,9 @@
package io.livekit.android.webrtc
import livekit.LivekitRtc
import org.webrtc.SessionDescription
import livekit.org.webrtc.SessionDescription
fun SessionDescription.toProtoSessionDescription(): LivekitRtc.SessionDescription {
internal fun SessionDescription.toProtoSessionDescription(): LivekitRtc.SessionDescription {
val sdBuilder = LivekitRtc.SessionDescription.newBuilder()
sdBuilder.sdp = description
sdBuilder.type = type.canonicalForm()
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,7 +17,7 @@
package io.livekit.android.webrtc
import io.livekit.android.util.LKLog
import org.webrtc.*
import livekit.org.webrtc.*
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,10 +16,10 @@
package io.livekit.android.webrtc.peerconnection
import org.webrtc.PeerConnection
import org.webrtc.RtpReceiver
import org.webrtc.RtpSender
import org.webrtc.RtpTransceiver
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.RtpSender
import livekit.org.webrtc.RtpTransceiver
/**
* Objects obtained through [PeerConnection] are transient,
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.webrtc
package livekit.org.webrtc
/**
* A helper to access package-protected methods used in [Camera2Session]
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.webrtc
package livekit.org.webrtc
import android.hardware.camera2.CameraManager
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -30,6 +30,7 @@ import io.livekit.android.util.toOkioByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import livekit.LivekitRtc
import livekit.org.webrtc.PeerConnection
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
... ... @@ -37,7 +38,6 @@ import okio.ByteString
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.webrtc.PeerConnection
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.mock
import org.webrtc.AudioTrack
import livekit.org.webrtc.AudioTrack
class MockAudioStreamTrack(
val id: String = "id",
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.mock
import org.webrtc.DataChannel
import livekit.org.webrtc.DataChannel
class MockDataChannel(private val label: String?) : DataChannel(1L) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -18,7 +18,7 @@ package io.livekit.android.mock
import android.graphics.SurfaceTexture
import android.view.Surface
import org.webrtc.EglBase
import livekit.org.webrtc.EglBase
class MockEglBase(
private val eglContext: EglBase.Context = EglBase.Context { 0 },
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,9 +16,9 @@
package io.livekit.android.mock
import org.webrtc.AudioTrack
import org.webrtc.MediaStream
import org.webrtc.VideoTrack
import livekit.org.webrtc.AudioTrack
import livekit.org.webrtc.MediaStream
import livekit.org.webrtc.VideoTrack
fun createMediaStreamId(participantSid: String, trackSid: String) =
"$participantSid|$trackSid"
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,7 +16,7 @@
package io.livekit.android.mock
import org.webrtc.MediaStreamTrack
import livekit.org.webrtc.MediaStreamTrack
class MockMediaStreamTrack(
val id: String = "id",
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,23 +16,23 @@
package io.livekit.android.mock
import org.webrtc.DataChannel
import org.webrtc.IceCandidate
import org.webrtc.MediaConstraints
import org.webrtc.MediaStream
import org.webrtc.MediaStreamTrack
import org.webrtc.MockRtpTransceiver
import org.webrtc.NativePeerConnectionFactory
import org.webrtc.PeerConnection
import org.webrtc.RTCStatsCollectorCallback
import org.webrtc.RTCStatsReport
import org.webrtc.RtcCertificatePem
import org.webrtc.RtpReceiver
import org.webrtc.RtpSender
import org.webrtc.RtpTransceiver
import org.webrtc.SdpObserver
import org.webrtc.SessionDescription
import org.webrtc.StatsObserver
import livekit.org.webrtc.DataChannel
import livekit.org.webrtc.IceCandidate
import livekit.org.webrtc.MediaConstraints
import livekit.org.webrtc.MediaStream
import livekit.org.webrtc.MediaStreamTrack
import livekit.org.webrtc.MockRtpTransceiver
import livekit.org.webrtc.NativePeerConnectionFactory
import livekit.org.webrtc.PeerConnection
import livekit.org.webrtc.RTCStatsCollectorCallback
import livekit.org.webrtc.RTCStatsReport
import livekit.org.webrtc.RtcCertificatePem
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.RtpSender
import livekit.org.webrtc.RtpTransceiver
import livekit.org.webrtc.SdpObserver
import livekit.org.webrtc.SessionDescription
import livekit.org.webrtc.StatsObserver
private class MockNativePeerConnectionFactory : NativePeerConnectionFactory {
override fun createNativePeerConnection(): Long = 0L
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,8 +16,8 @@
package io.livekit.android.mock
import livekit.org.webrtc.RtpReceiver
import org.mockito.Mockito
import org.webrtc.RtpReceiver
object MockRtpReceiver {
fun create(): RtpReceiver {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,8 +16,8 @@
package io.livekit.android.mock
import livekit.org.webrtc.RtpSender
import org.mockito.Mockito
import org.webrtc.RtpSender
object MockRtpSender {
fun create(): RtpSender {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -17,9 +17,9 @@
package io.livekit.android.mock
import android.content.Context
import org.webrtc.CapturerObserver
import org.webrtc.SurfaceTextureHelper
import org.webrtc.VideoCapturer
import livekit.org.webrtc.CapturerObserver
import livekit.org.webrtc.SurfaceTextureHelper
import livekit.org.webrtc.VideoCapturer
class MockVideoCapturer : VideoCapturer {
override fun initialize(p0: SurfaceTextureHelper?, p1: Context?, p2: CapturerObserver?) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,6 +16,6 @@
package io.livekit.android.mock
import org.webrtc.VideoSource
import livekit.org.webrtc.VideoSource
class MockVideoSource(nativeSource: Long = 100) : VideoSource(nativeSource)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -16,8 +16,8 @@
package io.livekit.android.mock
import org.webrtc.VideoSink
import org.webrtc.VideoTrack
import livekit.org.webrtc.VideoSink
import livekit.org.webrtc.VideoTrack
import java.util.UUID
class MockVideoStreamTrack(
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -21,24 +21,24 @@ import livekit.LivekitModels
object TestData {
val LOCAL_AUDIO_TRACK = with(LivekitModels.TrackInfo.newBuilder()) {
sid = "local_audio_track_sid"
sid = "TR_local_audio_track_sid"
type = LivekitModels.TrackType.AUDIO
build()
}
val LOCAL_VIDEO_TRACK = with(LivekitModels.TrackInfo.newBuilder()) {
sid = "local_video_track_sid"
sid = "TR_local_video_track_sid"
type = LivekitModels.TrackType.VIDEO
build()
}
val REMOTE_AUDIO_TRACK = with(LivekitModels.TrackInfo.newBuilder()) {
sid = "remote_audio_track_sid"
sid = "TR_remote_audio_track_sid"
type = LivekitModels.TrackType.AUDIO
build()
}
val REMOTE_VIDEO_TRACK = with(LivekitModels.TrackInfo.newBuilder()) {
sid = "remote_video_track_sid"
sid = "TR_remote_video_track_sid"
type = LivekitModels.TrackType.VIDEO
build()
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -23,7 +23,7 @@ import dagger.Provides
import io.livekit.android.dagger.CapabilitiesGetter
import io.livekit.android.dagger.InjectionNames
import io.livekit.android.mock.MockEglBase
import org.webrtc.*
import livekit.org.webrtc.*
import javax.inject.Named
import javax.inject.Singleton
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,13 +22,13 @@ import io.livekit.android.util.toPBByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import livekit.LivekitModels
import livekit.LivekitRtc
import livekit.org.webrtc.PeerConnection
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.webrtc.PeerConnection
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -26,9 +26,9 @@ import io.livekit.android.mock.MockPeerConnection
import kotlinx.coroutines.ExperimentalCoroutinesApi
import livekit.LivekitModels.DataPacket
import livekit.LivekitModels.UserPacket
import livekit.org.webrtc.DataChannel
import org.junit.Assert.assertEquals
import org.junit.Test
import org.webrtc.DataChannel
import java.nio.ByteBuffer
@OptIn(ExperimentalCoroutinesApi::class)
... ... @@ -50,7 +50,7 @@ class RoomDataMockE2ETest : MockE2ETest() {
}
val dataBuffer = DataChannel.Buffer(
ByteBuffer.wrap(dataPacket.toByteArray()),
true
true,
)
subDataChannel.observer?.onMessage(dataBuffer)
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -285,7 +285,7 @@ class RoomMockE2ETest : MockE2ETest() {
Assert.assertEquals(true, events[0] is RoomEvent.TrackSubscriptionPermissionChanged)
val event = events[0] as RoomEvent.TrackSubscriptionPermissionChanged
Assert.assertEquals(TestData.REMOTE_PARTICIPANT.sid, event.participant.sid)
Assert.assertEquals(TestData.REMOTE_PARTICIPANT.sid, event.participant.sid.value)
Assert.assertEquals(TestData.REMOTE_AUDIO_TRACK.sid, event.trackPublication.sid)
Assert.assertEquals(false, event.subscriptionAllowed)
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -22,12 +22,12 @@ import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.util.toPBByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import livekit.LivekitRtc
import livekit.org.webrtc.PeerConnection
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.webrtc.PeerConnection
/**
* For tests that only target one reconnection type.
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -30,9 +30,13 @@ import io.livekit.android.memory.CloseableManager
import io.livekit.android.mock.*
import io.livekit.android.room.participant.LocalParticipant
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import livekit.LivekitRtc.JoinResponse
import livekit.org.webrtc.EglBase
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
... ... @@ -45,7 +49,6 @@ import org.mockito.kotlin.*
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows
import org.robolectric.shadows.ShadowConnectivityManager
import org.webrtc.EglBase
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
... ... @@ -99,12 +102,12 @@ class RoomTest {
)
}
suspend fun connect() {
suspend fun connect(joinResponse: JoinResponse = SignalClientTest.JOIN.join) {
rtcEngine.stub {
onBlocking { rtcEngine.join(any(), any(), anyOrNull(), anyOrNull()) }
.doSuspendableAnswer {
room.onJoinResponse(SignalClientTest.JOIN.join)
SignalClientTest.JOIN.join
room.onJoinResponse(joinResponse)
joinResponse
}
}
rtcEngine.stub {
... ... @@ -138,6 +141,7 @@ class RoomTest {
room.onRoomUpdate(update)
val events = eventCollector.stopCollecting()
assertEquals(update.sid, room.sid?.sid)
assertEquals(update.metadata, room.metadata)
assertEquals(update.activeRecording, room.isRecording)
... ... @@ -209,4 +213,23 @@ class RoomTest {
assertNull(room.name)
assertFalse(room.isRecording)
}
@Test
fun getSidSuspendsUntilPopulated() = runTest {
val job = async {
room.getSid()
}
assertFalse(job.isCompleted)
connect()
assertFalse(job.isCompleted)
val update = SignalClientTest.ROOM_UPDATE.roomUpdate.room
room.onRoomUpdate(update)
advanceUntilIdle()
assertTrue(job.isCompleted)
val sid = job.await()
assertEquals(update.sid, sid.sid)
}
}
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -30,8 +30,15 @@ import livekit.LivekitModels
import livekit.LivekitModels.ClientConfiguration
import livekit.LivekitRtc
import livekit.LivekitRtc.ICEServer
import okhttp3.*
import org.junit.Assert.*
import livekit.org.webrtc.SessionDescription
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocketListener
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
... ... @@ -40,7 +47,6 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argThat
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.times
import org.webrtc.SessionDescription
@ExperimentalCoroutinesApi
class SignalClientTest : BaseTest() {
... ... @@ -330,7 +336,6 @@ class SignalClientTest : BaseTest() {
join = with(LivekitRtc.JoinResponse.newBuilder()) {
room = with(LivekitModels.Room.newBuilder()) {
name = "roomname"
sid = "room_sid"
build()
}
participant = TestData.LOCAL_PARTICIPANT
... ... @@ -380,6 +385,7 @@ class SignalClientTest : BaseTest() {
val ROOM_UPDATE = with(LivekitRtc.SignalResponse.newBuilder()) {
roomUpdate = with(LivekitRtc.RoomUpdate.newBuilder()) {
room = with(LivekitModels.Room.newBuilder()) {
sid = "room_sid"
metadata = "metadata"
activeRecording = true
build()
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -30,6 +30,7 @@ import io.livekit.android.room.SignalClientTest
import io.livekit.android.room.track.LocalAudioTrack
import io.livekit.android.room.track.LocalVideoTrack
import io.livekit.android.room.track.LocalVideoTrackOptions
import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoCaptureParameter
import io.livekit.android.room.track.VideoCodec
import io.livekit.android.util.toOkioByteString
... ... @@ -39,6 +40,7 @@ import livekit.LivekitModels
import livekit.LivekitRtc
import livekit.LivekitRtc.SubscribedCodec
import livekit.LivekitRtc.SubscribedQuality
import livekit.org.webrtc.VideoSource
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
... ... @@ -46,7 +48,6 @@ import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.kotlin.argThat
import org.robolectric.RobolectricTestRunner
import org.webrtc.VideoSource
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
... ... @@ -65,7 +66,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
room.disconnect()
assertEquals("", room.localParticipant.sid)
assertEquals("", room.localParticipant.sid.value)
assertNull(room.localParticipant.name)
assertNull(room.localParticipant.identity)
assertNull(room.localParticipant.metadata)
... ... @@ -74,9 +75,9 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
assertFalse(room.localParticipant.isSpeaking)
assertEquals(ConnectionQuality.UNKNOWN, room.localParticipant.connectionQuality)
assertEquals(0, room.localParticipant.tracks.values.size)
assertEquals(0, room.localParticipant.audioTracks.size)
assertEquals(0, room.localParticipant.videoTracks.size)
assertEquals(0, room.localParticipant.trackPublications.values.size)
assertEquals(0, room.localParticipant.audioTrackPublications.size)
assertEquals(0, room.localParticipant.videoTrackPublications.size)
}
@Test
... ... @@ -97,6 +98,30 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
}
@Test
fun publishVideoTrackRequest() = runTest {
connect()
wsFactory.ws.clearRequests()
val videoTrack = createLocalTrack()
val publishOptions = VideoTrackPublishOptions(
name = "name",
source = Track.Source.SCREEN_SHARE,
stream = "stream_id",
)
room.localParticipant.publishVideoTrack(videoTrack, publishOptions)
// Verify the add track request gets the proper publish options set.
val requestString = wsFactory.ws.sentRequests.first().toPBByteString()
val sentRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(requestString)
.build()
assertTrue(sentRequest.hasAddTrack())
assertEquals(publishOptions.name, sentRequest.addTrack.name)
assertEquals(publishOptions.source?.toProto(), sentRequest.addTrack.source)
assertEquals(publishOptions.stream, sentRequest.addTrack.stream)
}
@Test
fun updateMetadata() = runTest {
connect()
val newMetadata = "new_metadata"
... ... @@ -249,7 +274,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
wsFactory.receiveMessage(
with(LivekitRtc.SignalResponse.newBuilder()) {
subscribedQualityUpdate = with(LivekitRtc.SubscribedQualityUpdate.newBuilder()) {
trackSid = room.localParticipant.videoTracks.first().first.sid
trackSid = room.localParticipant.videoTrackPublications.first().first.sid
addAllSubscribedCodecs(
listOf(
with(SubscribedCodec.newBuilder()) {
... ...
/*
* Copyright 2023 LiveKit, Inc.
* Copyright 2023-2024 LiveKit, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
... ... @@ -54,7 +54,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
assertEquals(1, events.size)
assertEquals(true, events[0] is RoomEvent.TrackUnpublished)
assertEquals(0, room.localParticipant.tracks.size)
assertEquals(0, room.localParticipant.trackPublications.size)
}
@Test
... ...