davidliu
Committed by GitHub

Add spotless check to CI builds (#270)

* add spotless

* fix some format violations

* kotlin styling

* add spotless check to ci

* Update actions and fix spotless check

* ktlint fixes

* more spotless fixes

* more spotless fixes

* turn off annotation lint

* run spotless check first
正在显示 215 个修改的文件 包含 973 行增加818 行删除
# https://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
max_line_length = 180
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{java,kt,kts,scala,rs,xml,kt.spec,kts.spec,gradle,md}]
indent_size = 4
[*.{kt,kts}]
ktlint_code_style = android_studio
# default IntelliJ IDEA style, same as alphabetical, but with "java", "javax", "kotlin" and alias imports in the end of the imports list
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
ktlint_standard = enabled
ktlint_standard_annotation = disabled
ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_trailing-comma-on-call-site = disabled
ij_kotlin_allow_trailing_comma_on_call_site = true
ktlint_standard_trailing-comma-on-declaration-site = disabled
ij_kotlin_allow_trailing_comma = true
ktlint_standard_wrapping = disabled
[*.md]
trim_trailing_whitespace = false
max_line_length = unset
[*.yml]
ij_yaml_spaces_within_brackets = false
... ...
... ... @@ -24,18 +24,18 @@ jobs:
working-directory: ./client-sdk-android
steps:
- name: checkout client-sdk-android
uses: actions/checkout@v2.3.4
uses: actions/checkout@v4.0.0
with:
path: ./client-sdk-android
submodules: recursive
- name: set up JDK 12
uses: actions/setup-java@v2
uses: actions/setup-java@v3.12.0
with:
java-version: '12'
distribution: 'adopt'
- uses: actions/cache@v2
- uses: actions/cache@v3.3.2
with:
path: |
~/.gradle/caches
... ... @@ -45,10 +45,19 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Gradle clean
run: ./gradlew clean
- name: Spotless check
if: github.event_name == 'pull_request'
run: |
git fetch origin main --depth 1
./gradlew spotlessCheck
- name: Build with Gradle
run: ./gradlew clean assembleRelease livekit-android-sdk:testRelease
run: ./gradlew assembleRelease livekit-android-sdk:testRelease
# TODO: Figure out appropriate place to run this. Takes ~3 mins, so pretty slow.
# TODO: Figure out appropriate place to run this. Takes ~3 mins, so pretty slow.
# # generates coverage-report.md and publishes as checkrun
# - name: JaCoCo Code Coverage Report
# id: jacoco_reporter
... ...
/*
* Copyright $YEAR 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.
*/
... ...
... ... @@ -15,6 +15,7 @@ buildscript {
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19'
classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0"
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.5'
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.21.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
... ... @@ -27,6 +28,41 @@ subprojects {
mavenCentral()
maven { url 'https://jitpack.io' }
}
apply plugin: "com.diffplug.spotless"
spotless {
// optional: limit format enforcement to just the files changed by this feature branch
ratchetFrom 'origin/main'
format 'misc', {
// define the files to apply `misc` to
target '*.gradle', '*.md', '.gitignore'
// define the steps to apply to those files
trimTrailingWhitespace()
indentWithSpaces()
endWithNewline()
}
java {
// apply a specific flavor of google-java-format
googleJavaFormat('1.17.0').aosp().reflowLongStrings()
// fix formatting of type annotations
formatAnnotations()
// make sure every file has the following copyright header.
// optionally, Spotless can set copyright years by digging
// through git history (see "license" section below)
licenseHeaderFile rootProject.file("LicenseHeaderFile.txt")
removeUnusedImports()
}
kotlin {
target("src/*/java/**/*.kt")
ktlint("0.50.0")
.setEditorConfigPath("$rootDir/.editorconfig")
licenseHeaderFile(rootProject.file("LicenseHeaderFile.txt"))
.named('license')
endWithNewline()
}
}
}
task clean(type: Delete) {
... ... @@ -47,8 +83,8 @@ afterEvaluate {
"createRepository",
"getStagingProfile",
"releaseRepository"]
.forEach { taskName ->
getTasksByName(taskName, false).forEach { task -> task.enabled = false }
}
.forEach { taskName ->
getTasksByName(taskName, false).forEach { task -> task.enabled = false }
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -184,4 +184,4 @@ afterEvaluate {
}
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -16,5 +16,5 @@ Room is the primary class that manages the connection to the LiveKit Room. It ex
# Package io.livekit.android.room.track
`AudioTrack` and `VideoTrack` are the classes that represent the types of media streams that can be
subscribed and published.
\ No newline at end of file
`AudioTrack` and `VideoTrack` are the classes that represent the types of media streams that can be
subscribed and published.
... ...
... ... @@ -16,14 +16,12 @@
package io.livekit.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ...
... ... @@ -19,7 +19,6 @@ package io.livekit.android
import io.livekit.android.room.ProtocolVersion
import org.webrtc.PeerConnection
data class ConnectOptions(
/** Auto subscribe to room tracks upon connect, defaults to true */
val autoSubscribe: Boolean = true,
... ...
... ... @@ -122,6 +122,5 @@ class LiveKit {
room.connect(url, token, connectOptions)
return room
}
}
}
... ...
... ... @@ -50,7 +50,6 @@ data class LiveKitOverrides(
val audioOptions: AudioOptions? = null,
)
class AudioOptions(
/**
* Override the default output [AudioType].
... ... @@ -87,7 +86,7 @@ class AudioOptions(
sealed class AudioType(
val audioMode: Int,
val audioAttributes: AudioAttributes,
val audioStreamType: Int
val audioStreamType: Int,
) {
/**
* An audio type for general media playback usage (i.e. listener-only use cases).
... ... @@ -101,7 +100,7 @@ sealed class AudioType(
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.build(),
AudioManager.STREAM_MUSIC
AudioManager.STREAM_MUSIC,
)
/**
... ... @@ -115,7 +114,7 @@ sealed class AudioType(
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
AudioManager.STREAM_VOICE_CALL
AudioManager.STREAM_VOICE_CALL,
)
/**
... ... @@ -123,4 +122,4 @@ sealed class AudioType(
*/
class CustomAudioType(audioMode: Int, audioAttributes: AudioAttributes, audioStreamType: Int) :
AudioType(audioMode, audioAttributes, audioStreamType)
}
\ No newline at end of file
}
... ...
... ... @@ -43,4 +43,4 @@ data class RoomOptions(
val videoTrackCaptureDefaults: LocalVideoTrackOptions? = null,
val audioTrackPublishDefaults: AudioTrackPublishDefaults? = null,
val videoTrackPublishDefaults: VideoTrackPublishDefaults? = null,
)
\ No newline at end of file
)
... ...
... ... @@ -18,4 +18,4 @@ package io.livekit.android
object Version {
const val CLIENT_VERSION = BuildConfig.VERSION_NAME
}
\ No newline at end of file
}
... ...
... ... @@ -106,8 +106,8 @@ constructor(context: Context) : AudioHandler {
AudioAttributes.Builder()
.setUsage(audioAttributeUsageType)
.setContentType(audioAttributeContentType)
.build()
.build(),
)
.build()
}
}
\ No newline at end of file
}
... ...
... ... @@ -29,4 +29,4 @@ interface AudioHandler {
* Called when a room is disconnected.
*/
fun stop()
}
\ No newline at end of file
}
... ...
... ... @@ -150,14 +150,14 @@ constructor(private val context: Context) : AudioHandler {
context = context,
loggingEnabled = loggingEnabled,
audioFocusChangeListener = onAudioFocusChangeListener ?: defaultOnAudioFocusChangeListener,
preferredDeviceList = preferredDeviceList ?: defaultPreferredDeviceList
preferredDeviceList = preferredDeviceList ?: defaultPreferredDeviceList,
)
} else {
LegacyAudioSwitch(
context = context,
loggingEnabled = loggingEnabled,
audioFocusChangeListener = onAudioFocusChangeListener ?: defaultOnAudioFocusChangeListener,
preferredDeviceList = preferredDeviceList ?: defaultPreferredDeviceList
preferredDeviceList = preferredDeviceList ?: defaultPreferredDeviceList,
)
}
switch.manageAudioFocus = manageAudioFocus
... ... @@ -214,8 +214,8 @@ constructor(private val context: Context) : AudioHandler {
AudioDevice.BluetoothHeadset::class.java,
AudioDevice.WiredHeadset::class.java,
AudioDevice.Earpiece::class.java,
AudioDevice.Speakerphone::class.java
AudioDevice.Speakerphone::class.java,
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -29,4 +29,4 @@ constructor() : AudioHandler {
override fun stop() {
}
}
\ No newline at end of file
}
... ...
... ... @@ -37,7 +37,6 @@ fun VideoRenderer(
modifier: Modifier = Modifier,
mirror: Boolean = false,
) {
val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
var view: TextureViewRenderer? by remember { mutableStateOf(null) }
... ... @@ -93,4 +92,4 @@ fun VideoRenderer(
modifier = modifier
.onGloballyPositioned { videoSinkVisibility.onGloballyPositioned(it) },
)
}
\ No newline at end of file
}
... ...
... ... @@ -42,7 +42,7 @@ object AudioHandlerModule {
@Provides
fun audioOutputAttributes(
audioType: AudioType
audioType: AudioType,
): AudioAttributes {
return audioType.audioAttributes
}
... ... @@ -62,4 +62,4 @@ object AudioHandlerModule {
audioStreamType = audioOutputType.audioStreamType
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -41,4 +41,4 @@ object CoroutinesModule {
@Provides
@Named(InjectionNames.DISPATCHER_UNCONFINED)
fun unconfinedDispatcher() = Dispatchers.Unconfined
}
\ No newline at end of file
}
... ...
... ... @@ -29,7 +29,7 @@ object InjectionNames {
/**
* @see [kotlinx.coroutines.Dispatchers.IO]
*/
internal const val DISPATCHER_IO = "dispatcher_io";
internal const val DISPATCHER_IO = "dispatcher_io"
/**
* @see [kotlinx.coroutines.Dispatchers.Main]
... ... @@ -55,4 +55,4 @@ object InjectionNames {
internal const val OVERRIDE_VIDEO_DECODER_FACTORY = "override_video_decoder_factory"
internal const val OVERRIDE_AUDIO_HANDLER = "override_audio_handler"
internal const val OVERRIDE_AUDIO_OUTPUT_TYPE = "override_audio_output_type"
}
\ No newline at end of file
}
... ...
... ... @@ -32,5 +32,4 @@ object JsonFormatModule {
Json {
ignoreUnknownKeys = true
}
}
\ No newline at end of file
}
... ...
... ... @@ -35,7 +35,7 @@ import javax.inject.Singleton
OverridesModule::class,
AudioHandlerModule::class,
MemoryModule::class,
]
],
)
internal interface LiveKitComponent {
... ... @@ -49,7 +49,7 @@ internal interface LiveKitComponent {
interface Factory {
fun create(
@BindsInstance appContext: Context,
overridesModule: OverridesModule
overridesModule: OverridesModule,
): LiveKitComponent
}
}
... ... @@ -60,7 +60,6 @@ internal fun LiveKitComponent.Factory.create(
): LiveKitComponent {
return create(
appContext = context,
overridesModule = OverridesModule(overrides)
overridesModule = OverridesModule(overrides),
)
}
... ...
... ... @@ -30,4 +30,4 @@ object MemoryModule {
@Singleton
@Provides
fun closeableManager() = CloseableManager()
}
\ No newline at end of file
}
... ...
... ... @@ -63,5 +63,4 @@ class OverridesModule(private val overrides: LiveKitOverrides) {
@Provides
@Named(InjectionNames.OVERRIDE_AUDIO_OUTPUT_TYPE)
fun audioOutputType() = overrides.audioOptions?.audioOutputType
}
... ...
... ... @@ -53,24 +53,27 @@ object RTCModule {
PeerConnectionFactory.initialize(
PeerConnectionFactory.InitializationOptions
.builder(appContext)
.setInjectableLogger({ s, severity, s2 ->
if (!LiveKit.enableWebRTCLogging) {
return@setInjectableLogger
}
.setInjectableLogger(
{ s, severity, s2 ->
if (!LiveKit.enableWebRTCLogging) {
return@setInjectableLogger
}
val loggingLevel = when (severity) {
Logging.Severity.LS_VERBOSE -> LoggingLevel.VERBOSE
Logging.Severity.LS_INFO -> LoggingLevel.INFO
Logging.Severity.LS_WARNING -> LoggingLevel.WARN
Logging.Severity.LS_ERROR -> LoggingLevel.ERROR
else -> LoggingLevel.OFF
}
val loggingLevel = when (severity) {
Logging.Severity.LS_VERBOSE -> LoggingLevel.VERBOSE
Logging.Severity.LS_INFO -> LoggingLevel.INFO
Logging.Severity.LS_WARNING -> LoggingLevel.WARN
Logging.Severity.LS_ERROR -> LoggingLevel.ERROR
else -> LoggingLevel.OFF
}
LKLog.log(loggingLevel) {
Timber.log(loggingLevel.toAndroidLogPriority(), "$s2: $s")
}
}, Logging.Severity.LS_VERBOSE)
.createInitializationOptions()
LKLog.log(loggingLevel) {
Timber.log(loggingLevel.toAndroidLogPriority(), "$s2: $s")
}
},
Logging.Severity.LS_VERBOSE,
)
.createInitializationOptions(),
)
return LibWebrtcInitialization
}
... ... @@ -86,7 +89,7 @@ object RTCModule {
@Nullable
moduleCustomizer: ((builder: JavaAudioDeviceModule.Builder) -> Unit)?,
audioOutputAttributes: AudioAttributes,
appContext: Context
appContext: Context,
): AudioDeviceModule {
if (audioDeviceModuleOverride != null) {
return audioDeviceModuleOverride
... ... @@ -100,7 +103,7 @@ object RTCModule {
override fun onWebRtcAudioRecordStartError(
errorCode: JavaAudioDeviceModule.AudioRecordStartErrorCode?,
errorMessage: String?
errorMessage: String?,
) {
LKLog.e { "onWebRtcAudioRecordStartError: $errorCode. $errorMessage" }
}
... ... @@ -117,7 +120,7 @@ object RTCModule {
override fun onWebRtcAudioTrackStartError(
errorCode: JavaAudioDeviceModule.AudioTrackStartErrorCode?,
errorMessage: String?
errorMessage: String?,
) {
LKLog.e { "onWebRtcAudioTrackStartError: $errorCode. $errorMessage" }
}
... ... @@ -125,7 +128,6 @@ object RTCModule {
override fun onWebRtcAudioTrackError(errorMessage: String?) {
LKLog.e { "onWebRtcAudioTrackError: $errorMessage" }
}
}
val audioRecordStateCallback: JavaAudioDeviceModule.AudioRecordStateCallback = object :
JavaAudioDeviceModule.AudioRecordStateCallback {
... ... @@ -168,7 +170,9 @@ object RTCModule {
@Provides
@Singleton
fun eglBase(@Singleton memoryManager: CloseableManager): EglBase {
fun eglBase(
@Singleton memoryManager: CloseableManager,
): EglBase {
val eglBase = EglBase.create()
memoryManager.registerResource(eglBase) { eglBase.release() }
... ... @@ -188,7 +192,7 @@ object RTCModule {
eglContext: EglBase.Context,
@Named(InjectionNames.OVERRIDE_VIDEO_ENCODER_FACTORY)
@Nullable
videoEncoderFactoryOverride: VideoEncoderFactory?
videoEncoderFactoryOverride: VideoEncoderFactory?,
): VideoEncoderFactory {
return videoEncoderFactoryOverride ?: if (videoHwAccel) {
SimulcastVideoEncoderFactoryWrapper(
... ... @@ -211,7 +215,7 @@ object RTCModule {
eglContext: EglBase.Context,
@Named(InjectionNames.OVERRIDE_VIDEO_DECODER_FACTORY)
@Nullable
videoDecoderFactoryOverride: VideoDecoderFactory?
videoDecoderFactoryOverride: VideoDecoderFactory?,
): VideoDecoderFactory {
return videoDecoderFactoryOverride ?: if (videoHwAccel) {
WrappedVideoDecoderFactory(eglContext)
... ... @@ -253,4 +257,4 @@ object RTCModule {
/**
* @suppress
*/
object LibWebrtcInitialization
\ No newline at end of file
object LibWebrtcInitialization
... ...
... ... @@ -39,7 +39,7 @@ object WebModule {
fun okHttpClient(
@Named(InjectionNames.OVERRIDE_OKHTTP)
@Nullable
okHttpClientOverride: OkHttpClient?
okHttpClientOverride: OkHttpClient?,
): OkHttpClient {
OkHttpClient.Builder()
.pingInterval(20, TimeUnit.SECONDS)
... ... @@ -58,4 +58,4 @@ object WebModule {
fun networkInfo(context: Context): NetworkInfo {
return AndroidNetworkInfo(context)
}
}
\ No newline at end of file
}
... ...
/*
* 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.e2ee
import io.livekit.android.events.RoomEvent
... ... @@ -18,7 +34,7 @@ import org.webrtc.RtpReceiver
import org.webrtc.RtpSender
class E2EEManager
constructor(keyProvider: KeyProvider) {
constructor(keyProvider: KeyProvider) {
private var room: Room? = null
private var keyProvider: KeyProvider
private var frameCryptors = mutableMapOf<String, FrameCryptor>()
... ... @@ -70,10 +86,12 @@ constructor(keyProvider: KeyProvider) {
LKLog.i { "Receiver::onFrameCryptionStateChanged: $trackId, state: $state" }
emitEvent(
RoomEvent.TrackE2EEStateEvent(
room!!, publication.track!!, publication,
room!!,
publication.track!!,
publication,
participant,
state = e2eeStateFromFrameCryptoState(state)
)
state = e2eeStateFromFrameCryptoState(state),
),
)
}
}
... ... @@ -94,10 +112,12 @@ constructor(keyProvider: KeyProvider) {
LKLog.i { "Sender::onFrameCryptionStateChanged: $trackId, state: $state" }
emitEvent(
RoomEvent.TrackE2EEStateEvent(
room!!, publication.track!!, publication,
room!!,
publication.track!!,
publication,
participant,
state = e2eeStateFromFrameCryptoState(state)
)
state = e2eeStateFromFrameCryptoState(state),
),
)
}
}
... ... @@ -111,18 +131,22 @@ constructor(keyProvider: KeyProvider) {
FrameCryptionState.ENCRYPTIONFAILED -> E2EEState.ENCRYPTION_FAILED
FrameCryptionState.DECRYPTIONFAILED -> E2EEState.DECRYPTION_FAILED
FrameCryptionState.INTERNALERROR -> E2EEState.INTERNAL_ERROR
else -> { E2EEState.INTERNAL_ERROR}
else -> { E2EEState.INTERNAL_ERROR }
}
}
private fun addRtpSender(sender: RtpSender, participantId: String, trackId: String , kind: String): FrameCryptor {
private fun addRtpSender(sender: RtpSender, participantId: String, trackId: String, kind: String): FrameCryptor {
var pid = "$kind-sender-$participantId-$trackId"
var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpSender(
sender, pid, algorithm, keyProvider.rtcKeyProvider)
sender,
pid,
algorithm,
keyProvider.rtcKeyProvider,
)
frameCryptors[trackId] = frameCryptor
frameCryptor.setEnabled(enabled)
if(keyProvider.enableSharedKey) {
if (keyProvider.enableSharedKey) {
keyProvider.rtcKeyProvider?.setKey(pid, 0, keyProvider?.sharedKey)
frameCryptor.setKeyIndex(0)
}
... ... @@ -133,12 +157,16 @@ constructor(keyProvider: KeyProvider) {
private fun addRtpReceiver(receiver: RtpReceiver, participantId: String, trackId: String, kind: String): FrameCryptor {
var pid = "$kind-receiver-$participantId-$trackId"
var frameCryptor = FrameCryptorFactory.createFrameCryptorForRtpReceiver(
receiver, pid, algorithm, keyProvider.rtcKeyProvider)
receiver,
pid,
algorithm,
keyProvider.rtcKeyProvider,
)
frameCryptors[trackId] = frameCryptor
frameCryptor.setEnabled(enabled)
if(keyProvider.enableSharedKey) {
if (keyProvider.enableSharedKey) {
keyProvider.rtcKeyProvider?.setKey(pid, 0, keyProvider?.sharedKey)
frameCryptor.setKeyIndex(0)
}
... ... @@ -156,7 +184,7 @@ constructor(keyProvider: KeyProvider) {
var frameCryptor = item.value
var participantId = item.key
frameCryptor.setEnabled(enabled)
if(keyProvider.enableSharedKey) {
if (keyProvider.enableSharedKey) {
keyProvider.rtcKeyProvider?.setKey(participantId, 0, keyProvider?.sharedKey)
frameCryptor.setKeyIndex(0)
}
... ... @@ -169,11 +197,11 @@ constructor(keyProvider: KeyProvider) {
fun ratchetKey() {
for (participantId in frameCryptors.keys) {
var newKey = keyProvider.rtcKeyProvider?.ratchetKey(participantId, 0)
LKLog.d{ "ratchetKey: newKey: $newKey" }
LKLog.d { "ratchetKey: newKey: $newKey" }
}
}
fun cleanUp() {
fun cleanUp() {
for (frameCryptor in frameCryptors.values) {
frameCryptor.dispose()
}
... ...
/*
* 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.e2ee
import livekit.LivekitModels.Encryption
... ... @@ -15,7 +31,8 @@ constructor(
defaultRatchetWindowSize,
true,
defaultFaultTolerance,
), encryptionType: Encryption.Type = Encryption.Type.GCM
),
encryptionType: Encryption.Type = Encryption.Type.GCM,
) {
var keyProvider: KeyProvider
var encryptionType: Encryption.Type = Encryption.Type.NONE
... ...
/*
* 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.e2ee
enum class E2EEState {
NEW, // initial state
OK, // encryption or decryption succeeded
OK, // encryption or decryption succeeded
KEY_RATCHETED, // key ratcheted
MISSING_KEY, // missing key
ENCRYPTION_FAILED, // encryption failed
DECRYPTION_FAILED, // decryption failed
INTERNAL_ERROR // internal error
INTERNAL_ERROR, // internal error
}
... ...
/*
* 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.e2ee
import io.livekit.android.util.LKLog
... ... @@ -5,14 +21,14 @@ import org.webrtc.FrameCryptorFactory
import org.webrtc.FrameCryptorKeyProvider
class KeyInfo
constructor(var participantId: String, var keyIndex: Int, var key: String ) {
constructor(var participantId: String, var keyIndex: Int, var key: String) {
override fun toString(): String {
return "KeyInfo(participantId='$participantId', keyIndex=$keyIndex)"
}
}
public interface KeyProvider {
fun setKey(key: String, participantId: String?, keyIndex: Int? = 0)
public interface KeyProvider {
fun setKey(key: String, participantId: String?, keyIndex: Int? = 0)
fun ratchetKey(participantId: String, index: Int): ByteArray
val rtcKeyProvider: FrameCryptorKeyProvider
... ... @@ -28,7 +44,7 @@ constructor(
private var uncryptedMagicBytes: String,
private var ratchetWindowSize: Int,
override var enableSharedKey: Boolean = true,
private var failureTolerance:Int,
private var failureTolerance: Int,
) :
KeyProvider {
override var sharedKey: ByteArray? = null
... ... @@ -46,8 +62,8 @@ constructor(
return
}
if(participantId == null) {
LKLog.d{ "Please provide valid participantId for non-SharedKey mode." }
if (participantId == null) {
LKLog.d { "Please provide valid participantId for non-SharedKey mode." }
return
}
... ... @@ -72,7 +88,7 @@ constructor(
ratchetSalt.toByteArray(),
ratchetWindowSize,
uncryptedMagicBytes.toByteArray(),
failureTolerance
failureTolerance,
)
}
}
... ...
... ... @@ -49,4 +49,4 @@ class BroadcastEventBus<T> : EventListenable<T> {
}
fun readOnly(): EventListenable<T> = this
}
\ No newline at end of file
}
... ...
... ... @@ -16,4 +16,4 @@
package io.livekit.android.events
sealed class Event
\ No newline at end of file
sealed class Event
... ...
... ... @@ -24,4 +24,4 @@ interface EventListenable<out T> {
suspend inline fun <T> EventListenable<T>.collect(crossinline action: suspend (value: T) -> Unit) {
events.collect { value -> action(value) }
}
\ No newline at end of file
}
... ...
... ... @@ -58,7 +58,6 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
*/
class TrackUnmuted(participant: Participant, val publication: TrackPublication) : ParticipantEvent(participant)
// local participants
/**
* When a new track is published by the local participant.
... ... @@ -113,7 +112,7 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
class TrackUnsubscribed(
override val participant: RemoteParticipant,
val track: Track,
val publication: RemoteTrackPublication
val publication: RemoteTrackPublication,
) : ParticipantEvent(participant)
/**
... ... @@ -122,7 +121,7 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
class DataReceived(
override val participant: RemoteParticipant,
val data: ByteArray,
val topic: String?
val topic: String?,
) : ParticipantEvent(participant)
/**
... ... @@ -131,17 +130,16 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
class TrackStreamStateChanged(
override val participant: Participant,
val trackPublication: TrackPublication,
val streamState: Track.StreamState
val streamState: Track.StreamState,
) : ParticipantEvent(participant)
/**
* A remote track's subscription permissions have changed.
*/
class TrackSubscriptionPermissionChanged(
override val participant: RemoteParticipant,
val trackPublication: RemoteTrackPublication,
val subscriptionAllowed: Boolean
val subscriptionAllowed: Boolean,
) : ParticipantEvent(participant)
/**
... ... @@ -154,4 +152,4 @@ sealed class ParticipantEvent(open val participant: Participant) : Event() {
val newPermissions: ParticipantPermission?,
val oldPermissions: ParticipantPermission?,
) : ParticipantEvent(participant)
}
\ No newline at end of file
}
... ...
... ... @@ -62,7 +62,7 @@ sealed class RoomEvent(val room: Room) : Event() {
class RoomMetadataChanged(
room: Room,
val newMetadata: String?,
val prevMetadata: String?
val prevMetadata: String?,
) : RoomEvent(room)
// Participant callbacks
... ... @@ -74,13 +74,13 @@ sealed class RoomEvent(val room: Room) : Event() {
class ParticipantMetadataChanged(
room: Room,
val participant: Participant,
val prevMetadata: String?
val prevMetadata: String?,
) : RoomEvent(room)
class ParticipantNameChanged(
room: Room,
val participant: Participant,
val name: String?
val name: String?,
) : RoomEvent(room)
/**
... ... @@ -119,7 +119,7 @@ sealed class RoomEvent(val room: Room) : Event() {
room: Room,
val track: Track,
val publication: TrackPublication,
val participant: RemoteParticipant
val participant: RemoteParticipant,
) : RoomEvent(room)
/**
... ... @@ -129,7 +129,7 @@ sealed class RoomEvent(val room: Room) : Event() {
room: Room,
val sid: String,
val exception: Exception,
val participant: RemoteParticipant
val participant: RemoteParticipant,
) : RoomEvent(room)
/**
... ... @@ -140,7 +140,7 @@ sealed class RoomEvent(val room: Room) : Event() {
room: Room,
val track: Track,
val publications: TrackPublication,
val participant: RemoteParticipant
val participant: RemoteParticipant,
) : RoomEvent(room)
/**
... ... @@ -149,7 +149,7 @@ sealed class RoomEvent(val room: Room) : Event() {
class TrackStreamStateChanged(
room: Room,
val trackPublication: TrackPublication,
val streamState: Track.StreamState
val streamState: Track.StreamState,
) : RoomEvent(room)
/**
... ... @@ -159,7 +159,7 @@ sealed class RoomEvent(val room: Room) : Event() {
room: Room,
val participant: RemoteParticipant,
val trackPublication: RemoteTrackPublication,
val subscriptionAllowed: Boolean
val subscriptionAllowed: Boolean,
) : RoomEvent(room)
/**
... ... @@ -200,14 +200,14 @@ sealed class RoomEvent(val room: Room) : Event() {
class RecordingStatusChanged(room: Room, isRecording: Boolean) : RoomEvent(room)
/**
* The E2EE state of a track has changed.
*/
* The E2EE state of a track has changed.
*/
class TrackE2EEStateEvent(
room: Room,
val track: Track,
val publication: TrackPublication,
val participant: Participant,
var state: E2EEState
var state: E2EEState,
) : RoomEvent(room)
}
... ... @@ -219,7 +219,7 @@ enum class DisconnectReason {
PARTICIPANT_REMOVED,
ROOM_DELETED,
STATE_MISMATCH,
JOIN_FAILURE;
JOIN_FAILURE,
}
fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
... ... @@ -233,6 +233,7 @@ fun LivekitModels.DisconnectReason?.convert(): DisconnectReason {
LivekitModels.DisconnectReason.JOIN_FAILURE -> DisconnectReason.JOIN_FAILURE
LivekitModels.DisconnectReason.UNKNOWN_REASON,
LivekitModels.DisconnectReason.UNRECOGNIZED,
null -> DisconnectReason.UNKNOWN_REASON
null,
-> DisconnectReason.UNKNOWN_REASON
}
}
\ No newline at end of file
}
... ...
... ... @@ -22,4 +22,4 @@ sealed class TrackEvent(val track: Track) : Event() {
class VisibilityChanged(track: Track, val isVisible: Boolean) : TrackEvent(track)
class VideoDimensionsChanged(track: Track, val newDimensions: Track.Dimensions) : TrackEvent(track)
class StreamStateChanged(track: Track, val streamState: Track.StreamState) : TrackEvent(track)
}
\ No newline at end of file
}
... ...
... ... @@ -57,4 +57,4 @@ class CloseableManager : Closeable {
resources.values.forEach { it.close() }
resources.clear()
}
}
\ No newline at end of file
}
... ...
... ... @@ -28,4 +28,4 @@ internal class SurfaceTextureHelperCloser(private val surfaceTextureHelper: Surf
surfaceTextureHelper.dispose()
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,4 +31,4 @@ open class SurfaceViewRenderer : SurfaceViewRenderer, ViewVisibility.Notifier {
super.onVisibilityChanged(changedView, visibility)
viewVisibility?.recalculate()
}
}
\ No newline at end of file
}
... ...
... ... @@ -23,12 +23,16 @@ import org.webrtc.*
import org.webrtc.RendererCommon.*
import java.util.concurrent.CountDownLatch
/**
* Display the video stream on a TextureView.
*/
open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, VideoSink,
RendererEvents, ViewVisibility.Notifier {
open class TextureViewRenderer :
TextureView,
SurfaceHolder.Callback,
TextureView.SurfaceTextureListener,
VideoSink,
RendererEvents,
ViewVisibility.Notifier {
// Cached resource name.
private val resourceName: String
private val videoLayoutMeasure = VideoLayoutMeasure()
... ... @@ -68,6 +72,7 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
* `drawer`. It is allowed to call init() to reinitialize the renderer after a previous
* init()/release() cycle.
*/
/**
* Initialize this class, sharing resources with `sharedContext`. It is allowed to call init() to
* reinitialize the renderer after a previous init()/release() cycle.
... ... @@ -75,14 +80,15 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
@JvmOverloads
fun init(
sharedContext: EglBase.Context?,
rendererEvents: RendererEvents?, configAttributes: IntArray? = EglBase.CONFIG_PLAIN,
drawer: GlDrawer? = GlRectDrawer()
rendererEvents: RendererEvents?,
configAttributes: IntArray? = EglBase.CONFIG_PLAIN,
drawer: GlDrawer? = GlRectDrawer(),
) {
ThreadUtils.checkIsOnMainThread()
this.rendererEvents = rendererEvents
rotatedFrameWidth = 0
rotatedFrameHeight = 0
eglRenderer.init(sharedContext, this /* rendererEvents */, configAttributes, drawer)
eglRenderer.init(sharedContext, this, configAttributes, drawer)
}
/**
... ... @@ -105,7 +111,9 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
* @param drawer Custom drawer to use for this frame listener.
*/
fun addFrameListener(
listener: EglRenderer.FrameListener?, scale: Float, drawerParam: GlDrawer?
listener: EglRenderer.FrameListener?,
scale: Float,
drawerParam: GlDrawer?,
) {
eglRenderer.addFrameListener(listener, scale, drawerParam)
}
... ... @@ -155,12 +163,12 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
fun setScalingType(
scalingTypeMatchOrientation: ScalingType?,
scalingTypeMismatchOrientation: ScalingType?
scalingTypeMismatchOrientation: ScalingType?,
) {
ThreadUtils.checkIsOnMainThread()
videoLayoutMeasure.setScalingType(
scalingTypeMatchOrientation,
scalingTypeMismatchOrientation
scalingTypeMismatchOrientation,
)
requestLayout()
}
... ... @@ -221,14 +229,14 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
val width = Math.min(width, drawnFrameWidth)
val height = Math.min(height, drawnFrameHeight)
logD(
"updateSurfaceSize. Layout size: " + getWidth() + "x" + getHeight() + ", frame size: "
+ rotatedFrameWidth + "x" + rotatedFrameHeight + ", requested surface size: " + width
+ "x" + height + ", old surface size: " + surfaceWidth + "x" + surfaceHeight
"updateSurfaceSize. Layout size: " + getWidth() + "x" + getHeight() + ", frame size: " +
rotatedFrameWidth + "x" + rotatedFrameHeight + ", requested surface size: " + width +
"x" + height + ", old surface size: " + surfaceWidth + "x" + surfaceHeight,
)
if (width != surfaceWidth || height != surfaceHeight) {
surfaceWidth = width
surfaceHeight = height
adjustAspectRatio(surfaceWidth, surfaceHeight);
adjustAspectRatio(surfaceWidth, surfaceHeight)
}
} else {
surfaceHeight = 0
... ... @@ -257,8 +265,8 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
val xoff = (viewWidth - newWidth) / 2
val yoff = (viewHeight - newHeight) / 2
logD(
"video=" + videoWidth + "x" + videoHeight + " view=" + viewWidth + "x" + viewHeight
+ " newView=" + newWidth + "x" + newHeight + " off=" + xoff + "," + yoff
"video=" + videoWidth + "x" + videoHeight + " view=" + viewWidth + "x" + viewHeight +
" newView=" + newWidth + "x" + newHeight + " off=" + xoff + "," + yoff,
)
val txform = Matrix()
getTransform(txform)
... ... @@ -360,4 +368,4 @@ open class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureVie
super.onVisibilityChanged(changedView, visibility)
viewVisibility?.recalculate()
}
}
\ No newline at end of file
}
... ...
... ... @@ -20,5 +20,5 @@ enum class ConnectionState {
CONNECTING,
CONNECTED,
DISCONNECTED,
RECONNECTING;
}
\ No newline at end of file
RECONNECTING,
}
... ...
... ... @@ -31,4 +31,4 @@ constructor() {
var audioTrackPublishDefaults: AudioTrackPublishDefaults = AudioTrackPublishDefaults()
var videoTrackCaptureDefaults: LocalVideoTrackOptions = LocalVideoTrackOptions()
var videoTrackPublishDefaults: VideoTrackPublishDefaults = VideoTrackPublishDefaults()
}
\ No newline at end of file
}
... ...
... ... @@ -27,7 +27,7 @@ object DeviceManager {
enum class Kind {
// Only camera input currently, audio input/output only has one option atm.
CAMERA;
CAMERA,
}
private val defaultDevices = mutableMapOf<Kind, String>()
... ... @@ -44,19 +44,22 @@ object DeviceManager {
hasSetupListeners = true
val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
cameraManager.registerAvailabilityCallback(object : CameraManager.AvailabilityCallback() {
override fun onCameraAvailable(cameraId: String) {
notifyListeners(Kind.CAMERA)
}
cameraManager.registerAvailabilityCallback(
object : CameraManager.AvailabilityCallback() {
override fun onCameraAvailable(cameraId: String) {
notifyListeners(Kind.CAMERA)
}
override fun onCameraUnavailable(cameraId: String) {
notifyListeners(Kind.CAMERA)
}
override fun onCameraUnavailable(cameraId: String) {
notifyListeners(Kind.CAMERA)
}
override fun onCameraAccessPrioritiesChanged() {
notifyListeners(Kind.CAMERA)
}
}, Handler(Looper.getMainLooper()))
override fun onCameraAccessPrioritiesChanged() {
notifyListeners(Kind.CAMERA)
}
},
Handler(Looper.getMainLooper()),
)
}
fun getDefaultDevice(kind: Kind): String? {
... ... @@ -89,7 +92,7 @@ object DeviceManager {
fun registerOnDeviceAvailabilityChange(
kind: Kind,
listener: OnDeviceAvailabilityChangeListener
listener: OnDeviceAvailabilityChangeListener,
) {
if (listeners[kind] == null) {
listeners[kind] = mutableListOf()
... ... @@ -99,7 +102,7 @@ object DeviceManager {
fun unregisterOnDeviceAvailabilityChange(
kind: Kind,
listener: OnDeviceAvailabilityChangeListener
listener: OnDeviceAvailabilityChangeListener,
) {
listeners[kind]?.remove(listener)
}
... ... @@ -113,4 +116,4 @@ object DeviceManager {
interface OnDeviceAvailabilityChangeListener {
fun onDeviceAvailabilityChanged(kind: Kind)
}
}
\ No newline at end of file
}
... ...
... ... @@ -22,4 +22,4 @@ import kotlinx.serialization.Serializable
* @suppress
*/
@Serializable
data class IceCandidateJSON(val candidate: String, val sdpMLineIndex: Int, val sdpMid: String?)
\ No newline at end of file
data class IceCandidateJSON(val candidate: String, val sdpMLineIndex: Int, val sdpMid: String?)
... ...
... ... @@ -45,12 +45,12 @@ constructor(
@Assisted private val listener: Listener?,
@Named(InjectionNames.DISPATCHER_IO)
private val ioDispatcher: CoroutineDispatcher,
connectionFactory: PeerConnectionFactory
connectionFactory: PeerConnectionFactory,
) {
private val coroutineScope = CoroutineScope(ioDispatcher + SupervisorJob())
internal val peerConnection: PeerConnection = connectionFactory.createPeerConnection(
config,
pcObserver
pcObserver,
) ?: throw IllegalStateException("peer connection creation failed?")
private val pendingCandidates = mutableListOf<IceCandidate>()
private var restartingIce: Boolean = false
... ... @@ -76,7 +76,6 @@ constructor(
}
suspend fun setRemoteDescription(sd: SessionDescription): Either<Unit, String?> {
val result = peerConnection.setRemoteDescription(sd)
if (result is Either.Left) {
mutex.withLock {
... ... @@ -145,7 +144,6 @@ constructor(
listener?.onOffer(sdpOffer)
}
fun prepareForIceRestart() {
restartingIce = true
}
... ... @@ -163,7 +161,7 @@ constructor(
fun create(
config: PeerConnection.RTCConfiguration,
pcObserver: PeerConnection.Observer,
listener: Listener?
listener: Listener?,
): PeerConnectionTransport
}
}
\ No newline at end of file
}
... ...
... ... @@ -85,5 +85,4 @@ class PublisherTransportObserver(
override fun onAddTrack(p0: RtpReceiver?, p1: Array<out MediaStream>?) {
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,6 @@ import com.google.protobuf.ByteString
import io.livekit.android.ConnectOptions
import io.livekit.android.RoomOptions
import io.livekit.android.dagger.InjectionNames
import io.livekit.android.e2ee.E2EEOptions
import io.livekit.android.events.DisconnectReason
import io.livekit.android.events.convert
import io.livekit.android.room.participant.ParticipantTrackPermission
... ... @@ -39,7 +38,6 @@ import io.livekit.android.webrtc.toProtoSessionDescription
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import livekit.LivekitModels
import livekit.LivekitModels.Encryption
import livekit.LivekitRtc
import livekit.LivekitRtc.JoinResponse
import livekit.LivekitRtc.ReconnectResponse
... ... @@ -150,7 +148,7 @@ internal constructor(
url: String,
token: String,
options: ConnectOptions,
roomOptions: RoomOptions
roomOptions: RoomOptions,
): JoinResponse {
coroutineScope.close()
coroutineScope = CloseableCoroutineScope(SupervisorJob() + ioDispatcher)
... ... @@ -165,7 +163,7 @@ internal constructor(
url: String,
token: String,
options: ConnectOptions,
roomOptions: RoomOptions
roomOptions: RoomOptions,
): JoinResponse {
val joinResponse = client.join(url, token, options, roomOptions)
listener?.onJoinResponse(joinResponse)
... ... @@ -248,7 +246,7 @@ internal constructor(
reliableInit.ordered = true
reliableDataChannel = publisher.peerConnection.createDataChannel(
RELIABLE_DATA_CHANNEL_LABEL,
reliableInit
reliableInit,
)
reliableDataChannel!!.registerObserver(this)
val lossyInit = DataChannel.Init()
... ... @@ -256,7 +254,7 @@ internal constructor(
lossyInit.maxRetransmits = 0
lossyDataChannel = publisher.peerConnection.createDataChannel(
LOSSY_DATA_CHANNEL_LABEL,
lossyInit
lossyInit,
)
lossyDataChannel!!.registerObserver(this)
}
... ... @@ -268,7 +266,7 @@ internal constructor(
cid: String,
name: String,
kind: LivekitModels.TrackType,
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder()
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder(),
): LivekitModels.TrackInfo {
if (pendingTrackResolvers[cid] != null) {
throw TrackException.DuplicateTrackException("Track with same ID $cid has already been published!")
... ... @@ -282,7 +280,7 @@ internal constructor(
fun updateSubscriptionPermissions(
allParticipants: Boolean,
participantTrackPermissions: List<ParticipantTrackPermission>
participantTrackPermissions: List<ParticipantTrackPermission>,
) {
client.sendUpdateSubscriptionPermissions(allParticipants, participantTrackPermissions)
}
... ... @@ -348,7 +346,6 @@ internal constructor(
val forceFullReconnect = fullReconnectOnNext
fullReconnectOnNext = false
val job = coroutineScope.launch {
connectionState = ConnectionState.RECONNECTING
listener?.onEngineReconnecting()
... ... @@ -498,7 +495,6 @@ internal constructor(
this.negotiatePublisher()
}
val targetChannel = dataChannelForKind(kind) ?: throw IllegalArgumentException("Unknown data packet kind!")
if (targetChannel.state() == DataChannel.State.OPEN) {
return
... ... @@ -529,21 +525,21 @@ internal constructor(
add(
MediaConstraints.KeyValuePair(
MediaConstraintKeys.OFFER_TO_RECV_AUDIO,
MediaConstraintKeys.FALSE
)
MediaConstraintKeys.FALSE,
),
)
add(
MediaConstraints.KeyValuePair(
MediaConstraintKeys.OFFER_TO_RECV_VIDEO,
MediaConstraintKeys.FALSE
)
MediaConstraintKeys.FALSE,
),
)
if (connectionState == ConnectionState.RECONNECTING) {
add(
MediaConstraints.KeyValuePair(
MediaConstraintKeys.ICE_RESTART,
MediaConstraintKeys.TRUE
)
MediaConstraintKeys.TRUE,
),
)
}
}
... ... @@ -552,9 +548,8 @@ internal constructor(
private fun makeRTCConfig(
serverResponse: Either<JoinResponse, ReconnectResponse>,
connectOptions: ConnectOptions
connectOptions: ConnectOptions,
): RTCConfiguration {
// Convert protobuf ice servers
val serverIceServers = run {
val servers = mutableListOf<PeerConnection.IceServer>()
... ... @@ -664,7 +659,7 @@ internal constructor(
}
}
//---------------------------------- SignalClient.Listener --------------------------------------//
// ---------------------------------- SignalClient.Listener --------------------------------------//
override fun onAnswer(sessionDescription: SessionDescription) {
LKLog.v { "received server answer: ${sessionDescription.type}, ${publisher.peerConnection.signalingState()}" }
... ... @@ -686,8 +681,10 @@ internal constructor(
LKLog.v { "received server offer: ${sessionDescription.type}, ${subscriber.peerConnection.signalingState()}" }
coroutineScope.launch {
run<Unit> {
when (val outcome =
subscriber.setRemoteDescription(sessionDescription)) {
when (
val outcome =
subscriber.setRemoteDescription(sessionDescription)
) {
is Either.Right -> {
LKLog.e { "error setting remote description for answer: ${outcome.value} " }
return@launch
... ... @@ -821,7 +818,7 @@ internal constructor(
listener?.onLocalTrackUnpublished(trackUnpublished)
}
//--------------------------------- DataChannel.Observer ------------------------------------//
// --------------------------------- DataChannel.Observer ------------------------------------//
override fun onBufferedAmountChange(previousAmount: Long) {
}
... ... @@ -844,7 +841,8 @@ internal constructor(
}
LivekitModels.DataPacket.ValueCase.VALUE_NOT_SET,
null -> {
null,
-> {
LKLog.v { "invalid value for data packet" }
}
}
... ... @@ -852,7 +850,7 @@ internal constructor(
fun sendSyncState(
subscription: LivekitRtc.UpdateSubscription,
publishedTracks: List<LivekitRtc.TrackPublishedResponse>
publishedTracks: List<LivekitRtc.TrackPublishedResponse>,
) {
val answer = subscriber.peerConnection.localDescription?.toProtoSessionDescription()
... ... @@ -894,7 +892,7 @@ internal constructor(
enum class ReconnectType {
DEFAULT,
FORCE_SOFT_RECONNECT,
FORCE_FULL_RECONNECT;
FORCE_FULL_RECONNECT,
}
internal fun LivekitRtc.ICEServer.toWebrtc() = PeerConnection.IceServer.builder(urlsList)
... ... @@ -902,4 +900,4 @@ internal fun LivekitRtc.ICEServer.toWebrtc() = PeerConnection.IceServer.builder(
.setPassword(credential ?: "")
.setTlsAlpnProtocols(emptyList())
.setTlsEllipticCurves(emptyList())
.createIceServer()
\ No newline at end of file
.createIceServer()
... ...
... ... @@ -80,7 +80,7 @@ constructor(
CONNECTING,
CONNECTED,
DISCONNECTED,
RECONNECTING;
RECONNECTING,
}
/**
... ... @@ -90,7 +90,7 @@ constructor(
SPEAKER_UPDATE,
NODE_FAILURE,
MIGRATION,
SERVER_LEAVE;
SERVER_LEAVE,
}
@JvmInline
... ... @@ -224,7 +224,7 @@ constructor(
room = this@Room,
publication = it.publication,
participant = it.participant,
)
),
)
is ParticipantEvent.ParticipantPermissionsChanged -> emitWhenConnected(
... ... @@ -233,7 +233,7 @@ constructor(
participant = it.participant,
newPermissions = it.newPermissions,
oldPermissions = it.oldPermissions,
)
),
)
is ParticipantEvent.MetadataChanged -> {
... ... @@ -242,8 +242,8 @@ constructor(
RoomEvent.ParticipantMetadataChanged(
this@Room,
it.participant,
it.prevMetadata
)
it.prevMetadata,
),
)
}
... ... @@ -252,13 +252,13 @@ constructor(
RoomEvent.ParticipantNameChanged(
this@Room,
it.participant,
it.name
)
it.name,
),
)
}
else -> {
/* do nothing */
// do nothing
}
}
}
... ... @@ -267,9 +267,9 @@ constructor(
state = State.CONNECTING
connectOptions = options
if(roomOptions.e2eeOptions != null) {
if (roomOptions.e2eeOptions != null) {
e2eeManager = E2EEManager(roomOptions!!.e2eeOptions!!.keyProvider)
e2eeManager!!.setup(this, {event ->
e2eeManager!!.setup(this, { event ->
coroutineScope.launch {
emitWhenConnected(event)
}
... ... @@ -316,7 +316,6 @@ constructor(
* @suppress
*/
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)
... ... @@ -365,7 +364,7 @@ constructor(
@Synchronized
private fun getOrCreateRemoteParticipant(
sid: String,
info: LivekitModels.ParticipantInfo? = null
info: LivekitModels.ParticipantInfo? = null,
): RemoteParticipant {
var participant = remoteParticipants[sid]
if (participant != null) {
... ... @@ -389,7 +388,7 @@ constructor(
room = this@Room,
publication = it.publication,
participant = it.participant,
)
),
)
}
}
... ... @@ -398,8 +397,8 @@ constructor(
RoomEvent.TrackStreamStateChanged(
this@Room,
it.trackPublication,
it.streamState
)
it.streamState,
),
)
is ParticipantEvent.TrackSubscriptionPermissionChanged -> eventBus.postEvent(
... ... @@ -407,8 +406,8 @@ constructor(
this@Room,
it.participant,
it.trackPublication,
it.subscriptionAllowed
)
it.subscriptionAllowed,
),
)
is ParticipantEvent.MetadataChanged -> {
... ... @@ -417,8 +416,8 @@ constructor(
RoomEvent.ParticipantMetadataChanged(
this@Room,
it.participant,
it.prevMetadata
)
it.prevMetadata,
),
)
}
... ... @@ -428,7 +427,7 @@ constructor(
this@Room,
it.participant,
it.name,
)
),
)
}
... ... @@ -438,11 +437,11 @@ constructor(
participant = it.participant,
newPermissions = it.newPermissions,
oldPermissions = it.oldPermissions,
)
),
)
else -> {
/* do nothing */
// do nothing
}
}
}
... ... @@ -529,7 +528,7 @@ constructor(
private fun cleanupRoom() {
e2eeManager?.cleanUp()
localParticipant.cleanup()
remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
.forEach { sid -> handleParticipantDisconnect(sid) }
sid = null
... ... @@ -632,7 +631,7 @@ constructor(
fun create(context: Context): Room
}
//------------------------------------- NetworkCallback -------------------------------------//
// ------------------------------------- NetworkCallback -------------------------------------//
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
/**
* @suppress
... ... @@ -656,7 +655,7 @@ constructor(
}
}
//----------------------------------- RTCEngine.Listener ------------------------------------//
// ----------------------------------- RTCEngine.Listener ------------------------------------//
/**
* @suppress
... ... @@ -703,7 +702,7 @@ constructor(
trackSid!!,
autoManageVideo = adaptiveStream,
statsGetter = statsGetter,
receiver = receiver
receiver = receiver,
)
}
... ... @@ -864,7 +863,7 @@ constructor(
*/
override fun onFullReconnecting() {
localParticipant.prepareForFullReconnect()
remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
remoteParticipants.keys.toMutableSet() // copy keys to avoid concurrent modifications.
.forEach { sid -> handleParticipantDisconnect(sid) }
}
... ... @@ -895,7 +894,7 @@ constructor(
localParticipant.handleLocalTrackUnpublished(trackUnpublished)
}
//------------------------------- ParticipantListener --------------------------------//
// ------------------------------- ParticipantListener --------------------------------//
/**
* This is called for both Local and Remote participants
* @suppress
... ... @@ -928,11 +927,12 @@ constructor(
*/
override fun onTrackPublished(publication: LocalTrackPublication, participant: LocalParticipant) {
listener?.onTrackPublished(publication, participant, this)
if(e2eeManager != null) {
if (e2eeManager != null) {
e2eeManager!!.addPublishedTrack(publication.track!!, publication, participant, this)
}
eventBus.postEvent(RoomEvent.TrackPublished(this, publication, participant), coroutineScope)
}
/**
* @suppress
*/
... ... @@ -946,7 +946,7 @@ constructor(
*/
override fun onTrackSubscribed(track: Track, publication: RemoteTrackPublication, participant: RemoteParticipant) {
listener?.onTrackSubscribed(track, publication, participant, this)
if(e2eeManager != null) {
if (e2eeManager != null) {
e2eeManager!!.addSubscribedTrack(track, publication, participant, this)
}
eventBus.postEvent(RoomEvent.TrackSubscribed(this, track, publication, participant), coroutineScope)
... ... @@ -958,7 +958,7 @@ constructor(
override fun onTrackSubscriptionFailed(
sid: String,
exception: Exception,
participant: RemoteParticipant
participant: RemoteParticipant,
) {
listener?.onTrackSubscriptionFailed(sid, exception, participant, this)
eventBus.postEvent(RoomEvent.TrackSubscriptionFailed(this, sid, exception, participant), coroutineScope)
... ... @@ -970,7 +970,7 @@ constructor(
override fun onTrackUnsubscribed(
track: Track,
publication: RemoteTrackPublication,
participant: RemoteParticipant
participant: RemoteParticipant,
) {
listener?.onTrackUnsubscribed(track, publication, participant, this)
eventBus.postEvent(RoomEvent.TrackUnsubscribed(this, track, publication, participant), coroutineScope)
... ... @@ -982,7 +982,7 @@ constructor(
fun initVideoRenderer(viewRenderer: SurfaceViewRenderer) {
viewRenderer.init(eglBase.eglBaseContext, null)
viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
viewRenderer.setEnableHardwareScaler(false /* enabled */)
viewRenderer.setEnableHardwareScaler(false)
}
/**
... ... @@ -991,7 +991,7 @@ constructor(
fun initVideoRenderer(viewRenderer: TextureViewRenderer) {
viewRenderer.init(eglBase.eglBaseContext, null)
viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
viewRenderer.setEnableHardwareScaler(false /* enabled */)
viewRenderer.setEnableHardwareScaler(false)
}
private suspend fun emitWhenConnected(event: RoomEvent) {
... ... @@ -1171,4 +1171,4 @@ internal fun unpackStreamId(packed: String): Pair<String, String?> {
return Pair(packed, null)
}
return Pair(parts[0], parts[1])
}
\ No newline at end of file
}
... ...
... ... @@ -78,9 +78,11 @@ constructor(
// join will always return a JoinResponse.
// reconnect will return a ReconnectResponse or a Unit if a different response was received.
private var joinContinuation: CancellableContinuation<
Either<
JoinResponse,
Either<ReconnectResponse, Unit>>>? = null
Either<
JoinResponse,
Either<ReconnectResponse, Unit>,
>,
>? = null
private lateinit var coroutineScope: CloseableCoroutineScope
private val requestFlowJobLock = Object()
... ... @@ -121,7 +123,7 @@ constructor(
reconnect = true
this.participantSid = participantSid
},
lastRoomOptions ?: RoomOptions()
lastRoomOptions ?: RoomOptions(),
)
return (reconnectResponse as Either.Right).value
}
... ... @@ -130,7 +132,7 @@ constructor(
url: String,
token: String,
options: ConnectOptions,
roomOptions: RoomOptions
roomOptions: RoomOptions,
): Either<JoinResponse, Either<ReconnectResponse, Unit>> {
// Clean up any pre-existing connection.
close(reason = "Starting new connection")
... ... @@ -160,9 +162,8 @@ constructor(
token: String,
clientInfo: LivekitModels.ClientInfo,
options: ConnectOptions,
roomOptions: RoomOptions
roomOptions: RoomOptions,
): String {
val queryParams = mutableListOf<Pair<String, String>>()
queryParams.add(CONNECT_QUERY_TOKEN to token)
queryParams.add(CONNECT_QUERY_PROTOCOL to options.protocolVersion.value.toString())
... ... @@ -234,7 +235,7 @@ constructor(
startRequestQueue()
}
//--------------------------------- WebSocket Listener --------------------------------------//
// --------------------------------- WebSocket Listener --------------------------------------//
override fun onMessage(webSocket: WebSocket, text: String) {
LKLog.w { "received JSON message, unsupported in this version." }
}
... ... @@ -291,7 +292,7 @@ constructor(
// Handle websocket closure here.
handleWebSocketClose(
reason = reason ?: response?.toString() ?: t.localizedMessage ?: "websocket failure",
code = response?.code ?: CLOSE_REASON_WEBSOCKET_FAILURE
code = response?.code ?: CLOSE_REASON_WEBSOCKET_FAILURE,
)
}
}
... ... @@ -306,7 +307,7 @@ constructor(
pongJob?.cancel()
}
//------------------------------- End WebSocket Listener ------------------------------------//
// ------------------------------- End WebSocket Listener ------------------------------------//
private fun fromProtoSessionDescription(sd: LivekitRtc.SessionDescription): SessionDescription {
val rtcSdpType = when (sd.type) {
... ... @@ -340,7 +341,7 @@ constructor(
val iceCandidateJSON = IceCandidateJSON(
candidate = candidate.sdp,
sdpMid = candidate.sdpMid,
sdpMLineIndex = candidate.sdpMLineIndex
sdpMLineIndex = candidate.sdpMLineIndex,
)
val trickleRequest = LivekitRtc.TrickleRequest.newBuilder()
... ... @@ -375,7 +376,7 @@ constructor(
cid: String,
name: String,
type: LivekitModels.TrackType,
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder()
builder: LivekitRtc.AddTrackRequest.Builder = LivekitRtc.AddTrackRequest.newBuilder(),
) {
var encryptionType = lastRoomOptions?.e2eeOptions?.encryptionType ?: LivekitModels.Encryption.Type.NONE
val addTrackRequest = builder
... ... @@ -442,7 +443,7 @@ constructor(
fun sendUpdateSubscriptionPermissions(
allParticipants: Boolean,
participantTrackPermissions: List<ParticipantTrackPermission>
participantTrackPermissions: List<ParticipantTrackPermission>,
) {
val update = LivekitRtc.SubscriptionPermission.newBuilder()
.setAllParticipants(allParticipants)
... ... @@ -580,7 +581,7 @@ constructor(
val iceCandidate = IceCandidate(
iceCandidateJson.sdpMid,
iceCandidateJson.sdpMLineIndex,
iceCandidateJson.candidate
iceCandidateJson.candidate,
)
listener?.onTrickle(iceCandidate, response.trickle.target)
}
... ... @@ -658,7 +659,8 @@ constructor(
}
LivekitRtc.SignalResponse.MessageCase.MESSAGE_NOT_SET,
null -> {
null,
-> {
LKLog.v { "empty messageCase!" }
}
}
... ... @@ -721,7 +723,7 @@ constructor(
joinContinuation?.cancel()
joinContinuation = null
// TODO: support calling this from connect without wiping any queued requests.
//requestFlow.resetReplayCache()
// requestFlow.resetReplayCache()
responseFlow.resetReplayCache()
lastUrl = null
lastOptions = null
... ... @@ -804,4 +806,4 @@ enum class ProtocolVersion(val value: Int) {
v7(7),
v8(8),
v9(9),
}
\ No newline at end of file
}
... ...
... ... @@ -96,5 +96,4 @@ class SubscriberTransportObserver(
override fun onRenegotiationNeeded() {
}
}
\ No newline at end of file
}
... ...
... ... @@ -144,7 +144,7 @@ internal constructor(
name,
LocalVideoTrackOptions(isScreencast = true),
eglBase,
screencastVideoTrackFactory
screencastVideoTrackFactory,
)
}
... ... @@ -171,7 +171,7 @@ internal constructor(
*/
suspend fun setScreenShareEnabled(
enabled: Boolean,
mediaProjectionPermissionResultData: Intent? = null
mediaProjectionPermissionResultData: Intent? = null,
) {
setTrackEnabled(Track.Source.SCREEN_SHARE, enabled, mediaProjectionPermissionResultData)
}
... ... @@ -179,7 +179,7 @@ internal constructor(
private suspend fun setTrackEnabled(
source: Track.Source,
enabled: Boolean,
mediaProjectionPermissionResultData: Intent? = null
mediaProjectionPermissionResultData: Intent? = null,
) {
val pub = getTrackPublication(source)
... ... @@ -238,16 +238,16 @@ internal constructor(
track: LocalAudioTrack,
options: AudioTrackPublishOptions = AudioTrackPublishOptions(
null,
audioTrackPublishDefaults
audioTrackPublishDefaults,
),
publishListener: PublishListener? = null
publishListener: PublishListener? = null,
) {
val encodings = listOf(
RtpParameters.Encoding(null, true, null).apply {
if (options.audioBitrate != null && options.audioBitrate > 0) {
maxBitrateBps = options.audioBitrate
}
}
},
)
publishTrackImpl(
track = track,
... ... @@ -264,9 +264,8 @@ internal constructor(
suspend fun publishVideoTrack(
track: LocalVideoTrack,
options: VideoTrackPublishOptions = VideoTrackPublishOptions(null, videoTrackPublishDefaults),
publishListener: PublishListener? = null
publishListener: PublishListener? = null,
) {
val encodings = computeVideoEncodings(track.dimensions, options)
val videoLayers =
EncodingUtils.videoLayersFromEncodings(track.dimensions.width, track.dimensions.height, encodings)
... ... @@ -285,11 +284,10 @@ internal constructor(
addAllLayers(videoLayers)
},
encodings = encodings,
publishListener = publishListener
publishListener = publishListener,
)
}
/**
* @return true if the track publish was successful.
*/
... ... @@ -298,7 +296,7 @@ internal constructor(
options: TrackPublishOptions,
requestConfig: LivekitRtc.AddTrackRequest.Builder.() -> Unit,
encodings: List<RtpParameters.Encoding> = emptyList(),
publishListener: PublishListener? = null
publishListener: PublishListener? = null,
): Boolean {
if (localTrackPublications.any { it.track == track }) {
publishListener?.onPublishFailure(TrackException.PublishException("Track has already been published"))
... ... @@ -313,12 +311,12 @@ internal constructor(
cid = cid,
name = track.name,
kind = track.kind.toProto(),
builder = builder
builder = builder,
)
val transInit = RtpTransceiver.RtpTransceiverInit(
RtpTransceiver.RtpTransceiverDirection.SEND_ONLY,
listOf(this.sid),
encodings
encodings,
)
val transceiver = engine.publisher.peerConnection.addTransceiver(track.rtcTrack, transInit)
... ... @@ -393,7 +391,7 @@ internal constructor(
private fun computeVideoEncodings(
dimensions: Track.Dimensions,
options: VideoTrackPublishOptions
options: VideoTrackPublishOptions,
): List<RtpParameters.Encoding> {
val (width, height) = dimensions
var encoding = options.videoEncoding
... ... @@ -410,7 +408,6 @@ internal constructor(
val encodings = mutableListOf<RtpParameters.Encoding>()
if (simulcast) {
val presets = EncodingUtils.presetsForResolution(width, height)
val midPreset = presets[1]
val lowPreset = presets[0]
... ... @@ -456,7 +453,6 @@ internal constructor(
return encodings
}
/**
* Control who can subscribe to LocalParticipant's published tracks.
*
... ... @@ -476,7 +472,7 @@ internal constructor(
*/
fun setTrackSubscriptionPermissions(
allParticipantsAllowed: Boolean,
participantTrackPermissions: List<ParticipantTrackPermission> = emptyList()
participantTrackPermissions: List<ParticipantTrackPermission> = emptyList(),
) {
engine.updateSubscriptionPermissions(allParticipantsAllowed, participantTrackPermissions)
}
... ... @@ -605,7 +601,7 @@ internal constructor(
for (quality in qualities) {
val rid = EncodingUtils.ridForVideoQuality(quality.quality) ?: continue
val encoding = encodings.firstOrNull { it.rid == rid }
// use low quality layer settings for non-simulcasted streams
// use low quality layer settings for non-simulcasted streams
?: encodings.takeIf { it.size == 1 && quality.quality == LivekitModels.VideoQuality.LOW }?.first()
?: continue
if (encoding.active != quality.enabled) {
... ... @@ -732,7 +728,7 @@ data class VideoTrackPublishOptions(
) : BaseVideoTrackPublishOptions(), TrackPublishOptions {
constructor(
name: String? = null,
base: BaseVideoTrackPublishOptions
base: BaseVideoTrackPublishOptions,
) : this(
name,
base.videoEncoding,
... ... @@ -748,21 +744,21 @@ abstract class BaseAudioTrackPublishOptions {
data class AudioTrackPublishDefaults(
override val audioBitrate: Int? = 20_000,
override val dtx: Boolean = true
override val dtx: Boolean = true,
) : BaseAudioTrackPublishOptions()
data class AudioTrackPublishOptions(
override val name: String? = null,
override val audioBitrate: Int? = null,
override val dtx: Boolean = true
override val dtx: Boolean = true,
) : BaseAudioTrackPublishOptions(), TrackPublishOptions {
constructor(
name: String? = null,
base: BaseAudioTrackPublishOptions
base: BaseAudioTrackPublishOptions,
) : this(
name,
base.audioBitrate,
base.dtx
base.dtx,
)
}
... ... @@ -785,7 +781,7 @@ data class ParticipantTrackPermission(
/**
* The list of track ids that the target participant can subscribe to.
*/
val allowedTrackSids: List<String> = emptyList()
val allowedTrackSids: List<String> = emptyList(),
) {
init {
if (participantIdentity == null && participantSid == null) {
... ... @@ -806,11 +802,11 @@ data class ParticipantTrackPermission(
sealed class PublishRecord() {
data class AudioTrackPublishRecord(
val track: LocalAudioTrack,
val options: AudioTrackPublishOptions
val options: AudioTrackPublishOptions,
)
data class VideoTrackPublishRecord(
val track: LocalVideoTrack,
val options: VideoTrackPublishOptions
val options: VideoTrackPublishOptions,
)
}
\ No newline at end of file
}
... ...
... ... @@ -127,9 +127,9 @@ open class Participant(
ParticipantEvent.ParticipantPermissionsChanged(
this,
newPermissions,
oldPermissions
oldPermissions,
),
scope
scope,
)
}
}
... ... @@ -191,7 +191,7 @@ open class Participant(
// Re-emit when track changes
trackPublication::track.flow
.map { trackPublication to trackPublication.track }
}
},
) { trackPubs ->
trackPubs.toList()
}
... ... @@ -208,7 +208,7 @@ open class Participant(
stateFlow = ::tracks.flow
.map { it.filterValues { publication -> publication.kind == Track.Kind.AUDIO } }
.trackUpdateFlow()
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList())
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList()),
)
/**
... ... @@ -220,7 +220,7 @@ open class Participant(
stateFlow = ::tracks.flow
.map { it.filterValues { publication -> publication.kind == Track.Kind.VIDEO } }
.trackUpdateFlow()
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList())
.stateIn(delegateScope, SharingStarted.Eagerly, emptyList()),
)
/**
... ... @@ -340,7 +340,7 @@ open class Participant(
val trackPublication = tracks[trackEvent.track.sid] ?: return
eventBus.postEvent(
ParticipantEvent.TrackStreamStateChanged(this, trackPublication, trackEvent.streamState),
scope
scope,
)
}
... ... @@ -392,7 +392,6 @@ interface ParticipantListener {
*/
fun onTrackUnmuted(publication: TrackPublication, participant: Participant) {}
// local participants
/**
* When a new track is published by the local participant.
... ... @@ -428,7 +427,7 @@ interface ParticipantListener {
fun onTrackSubscriptionFailed(
sid: String,
exception: Exception,
participant: RemoteParticipant
participant: RemoteParticipant,
) {
}
... ... @@ -439,7 +438,7 @@ interface ParticipantListener {
fun onTrackUnsubscribed(
track: Track,
publication: RemoteTrackPublication,
participant: RemoteParticipant
participant: RemoteParticipant,
) {
}
... ... @@ -453,7 +452,8 @@ enum class ConnectionQuality {
EXCELLENT,
GOOD,
POOR,
UNKNOWN;
UNKNOWN,
;
companion object {
fun fromProto(proto: LivekitModels.ConnectionQuality): ConnectionQuality {
... ... @@ -485,4 +485,4 @@ data class ParticipantPermission(
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,7 +31,6 @@ import livekit.LivekitRtc
import org.webrtc.AudioTrack
import org.webrtc.MediaStreamTrack
import org.webrtc.RtpReceiver
import org.webrtc.RtpTransceiver
import org.webrtc.VideoTrack
class RemoteParticipant(
... ... @@ -135,7 +134,7 @@ class RemoteParticipant(
} else {
coroutineScope.launch {
delay(150)
addSubscribedMediaTrack(mediaTrack, sid, statsGetter,receiver = receiver, autoManageVideo, triesLeft - 1)
addSubscribedMediaTrack(mediaTrack, sid, statsGetter, receiver = receiver, autoManageVideo, triesLeft - 1)
}
}
return
... ...
... ... @@ -19,5 +19,4 @@ package io.livekit.android.room.track
import org.webrtc.AudioTrack
abstract class AudioTrack(name: String, override val rtcTrack: AudioTrack) :
Track(name, Kind.AUDIO, rtcTrack) {
}
Track(name, Kind.AUDIO, rtcTrack)
... ...
... ... @@ -19,4 +19,4 @@ package io.livekit.android.room.track
enum class DataPublishReliability {
RELIABLE,
LOSSY,
}
\ No newline at end of file
}
... ...
... ... @@ -55,7 +55,7 @@ constructor(
context,
eglBase,
defaultsManager,
videoTrackFactory
videoTrackFactory,
) {
private val serviceConnection = ScreenCaptureConnection(context)
... ... @@ -128,7 +128,7 @@ constructor(
name: String,
options: LocalVideoTrackOptions,
rootEglBase: EglBase,
screencastVideoTrackFactory: Factory
screencastVideoTrackFactory: Factory,
): LocalScreencastVideoTrack {
val source = peerConnectionFactory.createVideoSource(options.isScreencast)
val callback = MediaProjectionCallback()
... ... @@ -136,7 +136,7 @@ constructor(
capturer.initialize(
SurfaceTextureHelper.create("ScreenVideoCaptureThread", rootEglBase.eglBaseContext),
context,
source.capturerObserver
source.capturerObserver,
)
val track = peerConnectionFactory.createVideoTrack(UUID.randomUUID().toString(), source)
... ... @@ -146,16 +146,15 @@ constructor(
options = options,
name = name,
rtcTrack = track,
mediaProjectionCallback = callback
mediaProjectionCallback = callback,
)
}
private fun createScreenCapturer(
resultData: Intent,
callback: MediaProjectionCallback
callback: MediaProjectionCallback,
): ScreenCapturerAndroid {
return ScreenCapturerAndroid(resultData, callback)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,6 @@ import android.content.Context
import android.content.pm.PackageManager
import android.hardware.camera2.CameraManager
import androidx.core.content.ContextCompat
import com.github.ajalt.timberkt.Timber
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
... ... @@ -36,7 +35,6 @@ import org.webrtc.*
import org.webrtc.CameraVideoCapturer.CameraEventsHandler
import java.util.*
/**
* A representation of a local video track (generally input coming from camera or screen).
*
... ... @@ -116,7 +114,6 @@ constructor(
* this will switch to the next camera, if one is available.
*/
fun switchCamera(deviceId: String? = null, position: CameraPosition? = null) {
val cameraCapturer = capturer as? CameraVideoCapturer ?: run {
LKLog.w { "Attempting to switch camera on a non-camera video track!" }
return
... ... @@ -183,7 +180,6 @@ constructor(
override fun onCameraSwitchError(errorDescription: String?) {
LKLog.w { "switching camera failed: $errorDescription" }
}
}
if (targetDeviceId == null) {
LKLog.w { "No target camera found!" }
... ... @@ -197,7 +193,6 @@ constructor(
* Restart a track with new options.
*/
fun restartTrack(options: LocalVideoTrackOptions = defaultsManager.videoTrackCaptureDefaults.copy()) {
val oldCapturer = capturer
val oldSource = source
val oldRtcTrack = rtcTrack
... ... @@ -293,7 +288,6 @@ constructor(
trackFactory: Factory,
videoProcessor: VideoProcessor? = null,
): LocalVideoTrack {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED
) {
... ... @@ -450,5 +444,4 @@ constructor(
return null
}
}
}
... ...
... ... @@ -26,7 +26,7 @@ data class LocalVideoTrackOptions(
*/
val deviceId: String? = null,
val position: CameraPosition? = CameraPosition.FRONT,
val captureParams: VideoCaptureParameter = VideoPreset169.QHD.capture
val captureParams: VideoCaptureParameter = VideoPreset169.QHD.capture,
)
data class VideoCaptureParameter(
... ... @@ -56,7 +56,6 @@ data class VideoEncoding(
networkPriority = 1 // low, from priority.h in webrtc
bitratePriority = 1.0
}
}
}
}
... ... @@ -68,7 +67,7 @@ enum class VideoCodec(val codecName: String) {
enum class CameraPosition {
FRONT,
BACK
BACK,
}
interface VideoPreset {
... ... @@ -125,26 +124,30 @@ enum class VideoPreset169(
VideoCaptureParameter(320, 180, 15),
VideoEncoding(125_000, 15),
),
@Deprecated("VGA is deprecated, use H360 instead")
VGA(
VideoCaptureParameter(640, 360, 30),
VideoEncoding(400_000, 30),
),
@Deprecated("QHD is deprecated, use H540 instead")
QHD(
VideoCaptureParameter(960, 540, 30),
VideoEncoding(800_000, 30),
),
@Deprecated("HD is deprecated, use H720 instead")
HD(
VideoCaptureParameter(1280, 720, 30),
VideoEncoding(2_500_000, 30),
),
@Deprecated("FHD is deprecated, use H1080 instead")
FHD(
VideoCaptureParameter(1920, 1080, 30),
VideoEncoding(4_000_000, 30),
)
),
}
/**
... ... @@ -196,24 +199,28 @@ enum class VideoPreset43(
VideoCaptureParameter(240, 180, 15),
VideoEncoding(100_000, 15),
),
@Deprecated("VGA is deprecated, use H360 instead")
VGA(
VideoCaptureParameter(480, 360, 30),
VideoEncoding(320_000, 30),
),
@Deprecated("QHD is deprecated, use H540 instead")
QHD(
VideoCaptureParameter(720, 540, 30),
VideoEncoding(640_000, 30),
),
@Deprecated("HD is deprecated, use H720 instead")
HD(
VideoCaptureParameter(960, 720, 30),
VideoEncoding(2_000_000, 30),
),
@Deprecated("FHD is deprecated, use H1080 instead")
FHD(
VideoCaptureParameter(1440, 1080, 30),
VideoEncoding(3_200_000, 30),
)
}
\ No newline at end of file
),
}
... ...
... ... @@ -153,10 +153,10 @@ class RemoteTrackPublication(
* Will override previous calls to [setVideoDimensions].
*/
fun setVideoQuality(quality: LivekitModels.VideoQuality) {
if (isAutoManaged
|| !subscribed
|| quality == videoQuality
|| track !is VideoTrack
if (isAutoManaged ||
!subscribed ||
quality == videoQuality ||
track !is VideoTrack
) {
return
}
... ... @@ -171,10 +171,10 @@ class RemoteTrackPublication(
* Will override previous calls to [setVideoQuality].
*/
fun setVideoDimensions(dimensions: Track.Dimensions) {
if (isAutoManaged
|| !subscribed
|| videoDimensions == dimensions
|| track !is VideoTrack
if (isAutoManaged ||
!subscribed ||
videoDimensions == dimensions ||
track !is VideoTrack
) {
return
}
... ... @@ -188,10 +188,10 @@ class RemoteTrackPublication(
* Update the fps that the server will use for determining the video quality to send down.
*/
fun setVideoFps(fps: Int?) {
if (isAutoManaged
|| !subscribed
|| this.fps == fps
|| track !is VideoTrack
if (isAutoManaged ||
!subscribed ||
this.fps == fps ||
track !is VideoTrack
) {
return
}
... ... @@ -232,7 +232,7 @@ class RemoteTrackPublication(
disabled,
videoDimensions,
videoQuality,
fps
fps,
)
}
... ... @@ -250,6 +250,6 @@ class RemoteTrackPublication(
/**
* Not subscribed.
*/
UNSUBSCRIBED
UNSUBSCRIBED,
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,7 +25,6 @@ import io.livekit.android.room.track.video.ViewVisibility
import io.livekit.android.util.LKLog
import kotlinx.coroutines.*
import org.webrtc.RtpReceiver
import org.webrtc.RtpTransceiver
import org.webrtc.VideoSink
import javax.inject.Named
import kotlin.math.max
... ... @@ -136,4 +135,4 @@ class RemoteVideoTrack(
super.dispose()
coroutineScope.cancel()
}
}
\ No newline at end of file
}
... ...
... ... @@ -30,7 +30,7 @@ import org.webrtc.RTCStatsReport
abstract class Track(
name: String,
kind: Kind,
open val rtcTrack: MediaStreamTrack
open val rtcTrack: MediaStreamTrack,
) {
protected val eventBus = BroadcastEventBus<TrackEvent>()
val events = eventBus.readOnly()
... ... @@ -67,7 +67,8 @@ abstract class Track(
VIDEO("video"),
// unknown
UNRECOGNIZED("unrecognized");
UNRECOGNIZED("unrecognized"),
;
fun toProto(): LivekitModels.TrackType {
return when (this) {
... ... @@ -78,7 +79,7 @@ abstract class Track(
}
override fun toString(): String {
return value;
return value
}
companion object {
... ... @@ -96,8 +97,8 @@ abstract class Track(
CAMERA,
MICROPHONE,
SCREEN_SHARE,
UNKNOWN;
UNKNOWN,
;
fun toProto(): LivekitModels.TrackSource {
return when (this) {
... ... @@ -123,7 +124,8 @@ abstract class Track(
enum class StreamState {
ACTIVE,
PAUSED,
UNKNOWN;
UNKNOWN,
;
fun toProto(): LivekitRtc.StreamState {
return when (this) {
... ... @@ -159,7 +161,6 @@ abstract class Track(
}
}
sealed class TrackException(message: String? = null, cause: Throwable? = null) :
Exception(message, cause) {
class InvalidTrackTypeException(message: String? = null, cause: Throwable? = null) :
... ... @@ -176,4 +177,4 @@ sealed class TrackException(message: String? = null, cause: Throwable? = null) :
class PublishException(message: String? = null, cause: Throwable? = null) :
TrackException(message, cause)
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,7 @@ import org.webrtc.VideoTrack
abstract class VideoTrack(name: String, override val rtcTrack: VideoTrack) :
Track(name, Kind.VIDEO, rtcTrack) {
protected val sinks: MutableList<VideoSink> = ArrayList();
protected val sinks: MutableList<VideoSink> = ArrayList()
var enabled: Boolean
get() = rtcTrack.enabled()
... ...
... ... @@ -88,4 +88,4 @@ internal class ScreenCaptureConnection(private val context: Context) {
service = null
isBound = false
}
}
\ No newline at end of file
}
... ...
... ... @@ -67,7 +67,7 @@ open class ScreenCaptureService : Service() {
val channel = NotificationChannel(
DEFAULT_CHANNEL_ID,
"Screen Capture",
NotificationManager.IMPORTANCE_LOW
NotificationManager.IMPORTANCE_LOW,
)
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(channel)
... ... @@ -82,7 +82,6 @@ open class ScreenCaptureService : Service() {
return false
}
inner class ScreenCaptureBinder : Binder() {
val service: ScreenCaptureService
get() = this@ScreenCaptureService
... ... @@ -92,4 +91,4 @@ open class ScreenCaptureService : Service() {
const val DEFAULT_NOTIFICATION_ID = 2345
const val DEFAULT_CHANNEL_ID = "livekit_screen_capture"
}
}
\ No newline at end of file
}
... ...
... ... @@ -128,4 +128,4 @@ open class BitmapFrameCapturer : VideoCapturer {
}
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -77,5 +77,4 @@ internal class CameraEventsDispatchHandler : CameraEventsHandler {
handler.onCameraClosed()
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,7 +31,7 @@ internal interface VideoCapturerWithSize : VideoCapturer {
*/
internal abstract class CameraCapturerWithSize(
val cameraEventsDispatchHandler: CameraEventsDispatchHandler
val cameraEventsDispatchHandler: CameraEventsDispatchHandler,
) : VideoCapturerWithSize
/**
... ... @@ -60,4 +60,4 @@ internal class Camera2CapturerWithSize(
override fun findCaptureFormat(width: Int, height: Int): Size {
return Camera2Helper.findClosestCaptureFormat(cameraManager, deviceName, width, height)
}
}
\ No newline at end of file
}
... ...
... ... @@ -55,8 +55,8 @@ class ComposeVisibility : VideoSinkVisibility() {
private var lastSize = size()
override fun isVisible(): Boolean {
return (coordinates?.isAttached == true &&
coordinates?.size?.width != 0 &&
coordinates?.size?.height != 0)
coordinates?.size?.width != 0 &&
coordinates?.size?.height != 0)
}
override fun size(): Track.Dimensions {
... ... @@ -185,4 +185,4 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() {
view.viewVisibility = null
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -113,4 +113,4 @@ suspend fun PeerConnection.setLocalDescription(description: SessionDescription):
val observer = CoroutineSdpObserver()
this.setLocalDescription(observer, description)
return observer.awaitSet()
}
\ No newline at end of file
}
... ...
... ... @@ -40,7 +40,7 @@ internal object EncodingUtils {
VideoPreset169.H720,
VideoPreset169.H1080,
VideoPreset169.H1440,
VideoPreset169.H2160
VideoPreset169.H2160,
)
// Note: maintain order from smallest to biggest.
... ... @@ -82,7 +82,7 @@ internal object EncodingUtils {
fun videoLayersFromEncodings(
trackWidth: Int,
trackHeight: Int,
encodings: List<RtpParameters.Encoding>
encodings: List<RtpParameters.Encoding>,
): List<LivekitModels.VideoLayer> {
return if (encodings.isEmpty()) {
listOf(
... ... @@ -92,7 +92,7 @@ internal object EncodingUtils {
quality = LivekitModels.VideoQuality.HIGH
bitrate = 0
ssrc = 0
}.build()
}.build(),
)
} else {
encodings.map { encoding ->
... ... @@ -131,4 +131,4 @@ internal object EncodingUtils {
else -> null
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -30,4 +30,4 @@ object MediaConstraintKeys {
fun MediaConstraints.findConstraint(key: String): String? {
return mandatory.firstOrNull { it.key == key }?.value
?: optional.firstOrNull { it.key == key }?.value
}
\ No newline at end of file
}
... ...
... ... @@ -16,8 +16,6 @@
package io.livekit.android.room.util
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.track.Track
import org.webrtc.DataChannel
fun DataChannel.unpackedTrackLabel(): Triple<String, String, String> {
... ... @@ -35,6 +33,6 @@ fun DataChannel.unpackedTrackLabel(): Triple<String, String, String> {
trackSid = ""
name = ""
}
return Triple(participantSid, trackSid, name)
}
\ No newline at end of file
}
... ...
... ... @@ -74,4 +74,4 @@ enum class NetworkType(val protoName: String) {
BLUETOOTH("bluetooth"),
OTHER("other"),
UNKNOWN(""),
}
\ No newline at end of file
}
... ...
... ... @@ -30,7 +30,6 @@ internal class NetworkMonitor(private val context: Context) {
coroutineContext = SupervisorJob() + Dispatchers.IO
scope = CoroutineScope(coroutineContext)
scope.launch {
val uid = context.packageManager.getApplicationInfo(context.packageName, 0).uid
var prevTxBytes = TrafficStats.getUidTxBytes(uid)
... ... @@ -70,4 +69,4 @@ internal class NetworkMonitor(private val context: Context) {
fun stop() {
coroutineContext.cancel()
}
}
\ No newline at end of file
}
... ...
... ... @@ -27,4 +27,4 @@ internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, C
override fun close() {
coroutineContext.cancel()
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,7 @@ import kotlinx.coroutines.*
fun <T, R> debounce(
waitMs: Long = 300L,
coroutineScope: CoroutineScope,
destinationFunction: suspend (T) -> R
destinationFunction: suspend (T) -> R,
): (T) -> Unit {
var debounceJob: Deferred<R>? = null
return { param: T ->
... ... @@ -35,4 +35,4 @@ fun <T, R> debounce(
fun <R> ((Unit) -> R).invoke() {
this.invoke(Unit)
}
\ No newline at end of file
}
... ...
... ... @@ -19,4 +19,4 @@ package io.livekit.android.util
sealed class Either<out A, out B> {
class Left<A>(val value: A) : Either<A, Nothing>()
class Right<B>(val value: B) : Either<Nothing, B>()
}
\ No newline at end of file
}
... ...
... ... @@ -34,7 +34,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
/**
* A little circuitous but the way this works is:
* 1. [delegateRequested] set to true indicates that [delegate] should be filled.
... ... @@ -74,7 +73,7 @@ annotation class FlowObservable
internal class MutableStateFlowDelegate<T>
internal constructor(
private val flow: MutableStateFlow<T>,
private val onSetValue: ((newValue: T, oldValue: T) -> Unit)? = null
private val onSetValue: ((newValue: T, oldValue: T) -> Unit)? = null,
) : MutableStateFlow<T> by flow {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
... ... @@ -94,7 +93,7 @@ internal constructor(
@FlowObservable
internal class StateFlowDelegate<T>
internal constructor(
private val flow: StateFlow<T>
private val flow: StateFlow<T>,
) : StateFlow<T> by flow {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
... ... @@ -107,13 +106,13 @@ internal constructor(
internal fun <T> flowDelegate(
initialValue: T,
onSetValue: ((newValue: T, oldValue: T) -> Unit)? = null
onSetValue: ((newValue: T, oldValue: T) -> Unit)? = null,
): MutableStateFlowDelegate<T> {
return MutableStateFlowDelegate(MutableStateFlow(initialValue), onSetValue)
}
internal fun <T> flowDelegate(
stateFlow: StateFlow<T>
stateFlow: StateFlow<T>,
): StateFlowDelegate<T> {
return StateFlowDelegate(stateFlow)
}
\ No newline at end of file
}
... ...
/*
* 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.util
import io.livekit.android.util.LoggingLevel.*
... ... @@ -25,7 +41,6 @@ internal class LKLog {
companion object {
var loggingLevel = OFF
/** Log a verbose exception and a message that will be evaluated lazily when the message is printed */
@JvmStatic
inline fun v(t: Throwable? = null, message: () -> String) =
... ...
... ... @@ -25,7 +25,8 @@ enum class LoggingLevel {
WARN,
ERROR,
WTF,
OFF;
OFF,
;
fun toAndroidLogPriority(): Int {
return when (this) {
... ... @@ -39,4 +40,3 @@ enum class LoggingLevel {
}
}
}
... ...
... ... @@ -23,4 +23,4 @@ import okio.ByteString.Companion.toByteString
fun MessageLite.toOkioByteString(): ByteString {
val byteArray = toByteArray()
return byteArray.toByteString(0, byteArray.size)
}
\ No newline at end of file
}
... ...
... ... @@ -92,4 +92,4 @@ internal fun RTCConfiguration.copyFrom(config: RTCConfiguration) {
turnLoggingId = config.turnLoggingId
enableImplicitRollback = config.enableImplicitRollback
offerExtmapAllowMixed = config.offerExtmapAllowMixed
}
\ No newline at end of file
}
... ...
... ... @@ -98,8 +98,8 @@ fun RTCStatsReport.getFilteredStats(trackIdentifier: String): RTCStatsReport {
codecIds,
localCandidateId,
remoteCandidateId,
statsMap
)
statsMap,
),
)
val filteredStatsMap: MutableMap<String, RTCStats> = HashMap()
for (stats in filteredStats) {
... ... @@ -120,7 +120,6 @@ private fun getTrackStats(trackIdentifier: String, statsMap: Map<String, RTCStat
return null
}
private fun getStreamStats(trackId: String, statsMap: Map<String, RTCStats>): RTCStats? {
for (stats in statsMap.values) {
if (stats.type == "stream") {
... ... @@ -141,7 +140,7 @@ private fun getExtraStats(
codecIds: Set<String?>,
localCandidateId: String?,
remoteCandidateId: String?,
statsMap: Map<String, RTCStats>
statsMap: Map<String, RTCStats>,
): Set<RTCStats> {
val extraStats: MutableSet<RTCStats> = HashSet()
for (stats in statsMap.values) {
... ... @@ -185,4 +184,3 @@ fun createStatsGetter(peerConnection: PeerConnection, receiver: RtpReceiver): RT
{ statsCallback: RTCStatsCollectorCallback ->
peerConnection.getStats(receiver, statsCallback)
}
... ...
... ... @@ -25,4 +25,4 @@ fun SessionDescription.toProtoSessionDescription(): LivekitRtc.SessionDescriptio
sdBuilder.type = type.canonicalForm()
return sdBuilder.build()
}
\ No newline at end of file
}
... ...
/*
* 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.webrtc
import io.livekit.android.util.LKLog
... ... @@ -25,7 +41,7 @@ limitations under the License.
open class SimulcastVideoEncoderFactoryWrapper(
sharedContext: EglBase.Context?,
enableIntelVp8Encoder: Boolean,
enableH264HighProfile: Boolean
enableH264HighProfile: Boolean,
) : VideoEncoderFactory {
/**
... ... @@ -72,7 +88,6 @@ open class SimulcastVideoEncoderFactoryWrapper(
supportedCodecInfos.addAll(hardwareVideoEncoderFactory.supportedCodecs)
return supportedCodecInfos.toTypedArray()
}
}
/**
... ... @@ -89,12 +104,13 @@ open class SimulcastVideoEncoderFactoryWrapper(
override fun initEncode(
settings: VideoEncoder.Settings,
callback: VideoEncoder.Callback?
callback: VideoEncoder.Callback?,
): VideoCodecStatus {
streamSettings = settings
val future = executor.submit(Callable {
LKLog.i {
"""initEncode() thread=${Thread.currentThread().name} [${Thread.currentThread().id}]
val future = executor.submit(
Callable {
LKLog.i {
"""initEncode() thread=${Thread.currentThread().name} [${Thread.currentThread().id}]
| encoder=${encoder.implementationName}
| streamSettings:
| numberOfCores=${settings.numberOfCores}
... ... @@ -105,10 +121,11 @@ open class SimulcastVideoEncoderFactoryWrapper(
| automaticResizeOn=${settings.automaticResizeOn}
| numberOfSimulcastStreams=${settings.numberOfSimulcastStreams}
| lossNotification=${settings.capabilities.lossNotification}
""".trimMargin()
}
return@Callable encoder.initEncode(settings, callback)
})
""".trimMargin()
}
return@Callable encoder.initEncode(settings, callback)
},
)
return future.get()
}
... ... @@ -119,43 +136,51 @@ open class SimulcastVideoEncoderFactoryWrapper(
override fun encode(
frame: VideoFrame,
encodeInfo: VideoEncoder.EncodeInfo?
encodeInfo: VideoEncoder.EncodeInfo?,
): VideoCodecStatus {
val future = executor.submit(Callable {
//LKLog.d { "encode() buffer=${frame.buffer}, thread=${Thread.currentThread().name} " +
// "[${Thread.currentThread().id}]" }
if (streamSettings == null) {
return@Callable encoder.encode(frame, encodeInfo)
} else if (frame.buffer.width == streamSettings!!.width) {
return@Callable encoder.encode(frame, encodeInfo)
} else {
// The incoming buffer is different than the streamSettings received in initEncode()
// Need to scale.
val originalBuffer = frame.buffer
// TODO: Do we need to handle when the scale factor is weird?
val adaptedBuffer = originalBuffer.cropAndScale(
0, 0, originalBuffer.width, originalBuffer.height,
streamSettings!!.width, streamSettings!!.height
)
val adaptedFrame = VideoFrame(adaptedBuffer, frame.rotation, frame.timestampNs)
val result = encoder.encode(adaptedFrame, encodeInfo)
adaptedBuffer.release()
return@Callable result
}
})
val future = executor.submit(
Callable {
// LKLog.d { "encode() buffer=${frame.buffer}, thread=${Thread.currentThread().name} " +
// "[${Thread.currentThread().id}]" }
if (streamSettings == null) {
return@Callable encoder.encode(frame, encodeInfo)
} else if (frame.buffer.width == streamSettings!!.width) {
return@Callable encoder.encode(frame, encodeInfo)
} else {
// The incoming buffer is different than the streamSettings received in initEncode()
// Need to scale.
val originalBuffer = frame.buffer
// TODO: Do we need to handle when the scale factor is weird?
val adaptedBuffer = originalBuffer.cropAndScale(
0,
0,
originalBuffer.width,
originalBuffer.height,
streamSettings!!.width,
streamSettings!!.height,
)
val adaptedFrame = VideoFrame(adaptedBuffer, frame.rotation, frame.timestampNs)
val result = encoder.encode(adaptedFrame, encodeInfo)
adaptedBuffer.release()
return@Callable result
}
},
)
return future.get()
}
override fun setRateAllocation(
allocation: VideoEncoder.BitrateAllocation?,
frameRate: Int
frameRate: Int,
): VideoCodecStatus {
val future = executor.submit(Callable {
return@Callable encoder.setRateAllocation(
allocation,
frameRate
)
})
val future = executor.submit(
Callable {
return@Callable encoder.setRateAllocation(
allocation,
frameRate,
)
},
)
return future.get()
}
... ... @@ -202,7 +227,7 @@ open class SimulcastVideoEncoderFactoryWrapper(
if (encoder == null) {
return null
}
if(encoder is WrappedNativeVideoEncoder){
if (encoder is WrappedNativeVideoEncoder) {
return encoder
}
return StreamEncoderWrapper(encoder)
... ... @@ -213,14 +238,15 @@ open class SimulcastVideoEncoderFactoryWrapper(
}
}
private val primary: VideoEncoderFactory
private val fallback: VideoEncoderFactory
private val native: SimulcastVideoEncoderFactory
init {
val hardwareVideoEncoderFactory = HardwareVideoEncoderFactory(
sharedContext, enableIntelVp8Encoder, enableH264HighProfile
sharedContext,
enableIntelVp8Encoder,
enableH264HighProfile,
)
primary = StreamEncoderWrapperFactory(hardwareVideoEncoderFactory)
fallback = StreamEncoderWrapperFactory(FallbackFactory(primary))
... ... @@ -234,5 +260,4 @@ open class SimulcastVideoEncoderFactoryWrapper(
override fun getSupportedCodecs(): Array<VideoCodecInfo> {
return native.supportedCodecs
}
}
\ No newline at end of file
}
... ...
... ... @@ -32,14 +32,14 @@ internal class Camera1Helper {
fun findClosestCaptureFormat(
cameraId: Int,
width: Int,
height: Int
height: Int,
): Size {
return CameraEnumerationAndroid.getClosestSupportedSize(
getSupportedFormats(cameraId)
.map { Size(it.width, it.height) },
width,
height
height,
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -38,7 +38,7 @@ internal class Camera2Helper {
cameraManager: CameraManager,
cameraId: String?,
width: Int,
height: Int
height: Int,
): Size {
val sizes = getSupportedFormats(cameraManager, cameraId)
?.map { Size(it.width, it.height) }
... ... @@ -46,4 +46,4 @@ internal class Camera2Helper {
return CameraEnumerationAndroid.getClosestSupportedSize(sizes, width, height)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -112,4 +112,4 @@ abstract class MockE2ETest : BaseTest() {
fun simulateMessageFromServer(message: ByteString) {
wsFactory.listener.onMessage(wsFactory.ws, message)
}
}
\ No newline at end of file
}
... ...
... ... @@ -34,4 +34,4 @@ fun assertIsClassList(expectedClasses: List<Class<*>>, actual: List<*>) {
}
Assert.assertEquals(expectedClasses, klazzes)
}
\ No newline at end of file
}
... ...
... ... @@ -45,8 +45,7 @@ fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit?>): Flow<T> = flow {
emit(it)
}
}
} catch (e: CancellationException) {
//ignore
// ignore
}
}
\ No newline at end of file
}
... ...
... ... @@ -41,9 +41,9 @@ class TestCoroutineRule : TestRule {
dispatcher.scheduler.advanceUntilIdle() // run the remaining tasks
Assert.assertEquals(
timeAfterTest,
dispatcher.scheduler.currentTime
dispatcher.scheduler.currentTime,
) // will fail if there were tasks scheduled at a later moment
Dispatchers.resetMain()
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -20,5 +20,5 @@ import kotlinx.coroutines.CoroutineScope
class EventCollector<T : Event>(
eventListenable: EventListenable<T>,
coroutineScope: CoroutineScope
) : FlowCollector<T>(eventListenable.events, coroutineScope)
\ No newline at end of file
coroutineScope: CoroutineScope,
) : FlowCollector<T>(eventListenable.events, coroutineScope)
... ...
... ... @@ -25,7 +25,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
open class FlowCollector<T>(
private val flow: Flow<T>,
coroutineScope: CoroutineScope
coroutineScope: CoroutineScope,
) {
private val signal = MutableStateFlow<Unit?>(null)
private val collectEventsDeferred = coroutineScope.async {
... ... @@ -40,5 +40,4 @@ open class FlowCollector<T>(
signal.compareAndSet(null, Unit)
return collectEventsDeferred.await()
}
}
\ No newline at end of file
}
... ...
... ... @@ -44,4 +44,4 @@ class MockAudioStreamTrack(
override fun setVolume(volume: Double) {
}
}
\ No newline at end of file
}
... ...
... ... @@ -54,4 +54,4 @@ class MockDataChannel(private val label: String?) : DataChannel(1L) {
override fun dispose() {
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,7 @@ import android.view.Surface
import org.webrtc.EglBase
class MockEglBase(
private val eglContext: EglBase.Context = EglBase.Context { 0 }
private val eglContext: EglBase.Context = EglBase.Context { 0 },
) : EglBase {
override fun createSurface(p0: Surface?) {}
... ... @@ -51,4 +51,4 @@ class MockEglBase(
override fun swapBuffers() {}
override fun swapBuffers(p0: Long) {}
}
\ No newline at end of file
}
... ...
... ... @@ -21,7 +21,7 @@ import org.webrtc.MediaStream
import org.webrtc.VideoTrack
fun createMediaStreamId(participantSid: String, trackSid: String) =
"${participantSid}|${trackSid}"
"$participantSid|$trackSid"
class MockMediaStream(private val id: String = "id") : MediaStream(1L) {
... ... @@ -50,4 +50,4 @@ class MockMediaStream(private val id: String = "id") : MediaStream(1L) {
}
override fun getId(): String = id
}
\ No newline at end of file
}
... ...
... ... @@ -41,4 +41,4 @@ class MockMediaStreamTrack(
override fun dispose() {
}
}
\ No newline at end of file
}
... ...
... ... @@ -40,7 +40,7 @@ private class MockNativePeerConnectionFactory : NativePeerConnectionFactory {
class MockPeerConnection(
var rtcConfig: RTCConfiguration,
val observer: Observer?
val observer: Observer?,
) : PeerConnection(MockNativePeerConnectionFactory()) {
private var closed = false
... ... @@ -78,7 +78,6 @@ class MockPeerConnection(
observer?.onCreateSuccess(sdp)
}
override fun setAudioPlayout(playout: Boolean) {
}
... ... @@ -142,7 +141,7 @@ class MockPeerConnection(
override fun addTransceiver(
track: MediaStreamTrack,
init: RtpTransceiver.RtpTransceiverInit?
init: RtpTransceiver.RtpTransceiverInit?,
): RtpTransceiver {
val transceiver = MockRtpTransceiver.create(track, init ?: RtpTransceiver.RtpTransceiverInit())
transceivers.add(transceiver)
... ... @@ -155,7 +154,7 @@ class MockPeerConnection(
override fun addTransceiver(
mediaType: MediaStreamTrack.MediaType?,
init: RtpTransceiver.RtpTransceiverInit?
init: RtpTransceiver.RtpTransceiverInit?,
): RtpTransceiver {
return super.addTransceiver(mediaType, init)
}
... ... @@ -187,9 +186,9 @@ class MockPeerConnection(
if ((localDesc?.type == null && remoteDesc?.type == null) ||
(localDesc?.type == SessionDescription.Type.OFFER &&
remoteDesc?.type == SessionDescription.Type.ANSWER) ||
remoteDesc?.type == SessionDescription.Type.ANSWER) ||
(localDesc?.type == SessionDescription.Type.ANSWER &&
remoteDesc?.type == SessionDescription.Type.OFFER)
remoteDesc?.type == SessionDescription.Type.OFFER)
) {
return SignalingState.STABLE
}
... ... @@ -281,4 +280,4 @@ class MockPeerConnection(
}
override fun getNativePeerConnection(): Long = 0L
}
\ No newline at end of file
}
... ...
... ... @@ -23,4 +23,4 @@ object MockRtpReceiver {
fun create(): RtpReceiver {
return Mockito.mock(RtpReceiver::class.java)
}
}
\ No newline at end of file
}
... ...
... ... @@ -23,4 +23,4 @@ object MockRtpSender {
fun create(): RtpSender {
return Mockito.mock(RtpSender::class.java)
}
}
\ No newline at end of file
}
... ...
... ... @@ -40,4 +40,4 @@ class MockVideoCapturer : VideoCapturer {
override fun isScreencast(): Boolean {
return false
}
}
\ No newline at end of file
}
... ...
... ... @@ -18,5 +18,4 @@ package io.livekit.android.mock
import org.webrtc.VideoSource
class MockVideoSource(nativeSource: Long = 100) : VideoSource(nativeSource) {
}
\ No newline at end of file
class MockVideoSource(nativeSource: Long = 100) : VideoSource(nativeSource)
... ...
... ... @@ -25,7 +25,7 @@ import okio.IOException
class MockWebSocket(
private val request: Request,
private val listener: WebSocketListener,
private val onSend: ((ByteString) -> Unit)?
private val onSend: ((ByteString) -> Unit)?,
) : WebSocket {
var isClosed = false
... ... @@ -69,4 +69,4 @@ class MockWebSocket(
fun clearRequests() {
mutableSentRequests.clear()
}
}
\ No newline at end of file
}
... ...
... ... @@ -67,4 +67,4 @@ class MockWebSocketFactory : WebSocket.Factory {
}
var onOpen: ((MockWebSocketFactory) -> Unit)? = null
}
\ No newline at end of file
}
... ...
... ... @@ -75,11 +75,10 @@ object TestData {
build()
}
val REMOTE_SPEAKER_INFO = with(LivekitModels.SpeakerInfo.newBuilder()) {
sid = REMOTE_PARTICIPANT.sid
level = 1.0f
active = true
build()
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,4 +25,4 @@ import io.livekit.android.audio.NoAudioHandler
interface TestAudioHandlerModule {
@Binds
fun audioHandler(audioHandler: NoAudioHandler): AudioHandler
}
\ No newline at end of file
}
... ...
... ... @@ -27,7 +27,7 @@ import javax.inject.Named
@Module
class TestCoroutinesModule(
@OptIn(ExperimentalCoroutinesApi::class)
val coroutineDispatcher: CoroutineDispatcher = TestCoroutineDispatcher()
val coroutineDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
) {
@Provides
... ... @@ -45,4 +45,4 @@ class TestCoroutinesModule(
@Provides
@Named(InjectionNames.DISPATCHER_UNCONFINED)
fun unconfinedDispatcher() = coroutineDispatcher
}
\ No newline at end of file
}
... ...
... ... @@ -35,7 +35,7 @@ import javax.inject.Singleton
TestAudioHandlerModule::class,
JsonFormatModule::class,
MemoryModule::class,
]
],
)
internal interface TestLiveKitComponent : LiveKitComponent {
... ... @@ -47,7 +47,7 @@ internal interface TestLiveKitComponent : LiveKitComponent {
interface Factory {
fun create(
@BindsInstance appContext: Context,
coroutinesModule: TestCoroutinesModule = TestCoroutinesModule()
coroutinesModule: TestCoroutinesModule = TestCoroutinesModule(),
): TestLiveKitComponent
}
}
\ No newline at end of file
}
... ...
... ... @@ -26,7 +26,6 @@ import org.webrtc.*
import javax.inject.Named
import javax.inject.Singleton
@Module
object TestRTCModule {
... ... @@ -39,11 +38,10 @@ object TestRTCModule {
@Provides
fun eglContext(eglBase: EglBase): EglBase.Context = eglBase.eglBaseContext
@Provides
@Singleton
fun peerConnectionFactory(
appContext: Context
appContext: Context,
): PeerConnectionFactory {
WebRTCInitializer.initialize(appContext)
... ... @@ -61,4 +59,4 @@ object TestRTCModule {
@Provides
@Named(InjectionNames.OPTIONS_VIDEO_HW_ACCEL)
fun videoHwAccel() = true
}
\ No newline at end of file
}
... ...
... ... @@ -62,4 +62,4 @@ object TestWebModule {
override fun getNetworkType() = NetworkType.WIFI
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,7 +31,6 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.webrtc.PeerConnection
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class RTCEngineMockE2ETest : MockE2ETest() {
... ... @@ -58,7 +57,7 @@ class RTCEngineMockE2ETest : MockE2ETest() {
connect()
assertEquals(
SignalClientTest.OFFER.offer.sdp,
rtcEngine.subscriber.peerConnection.remoteDescription.description
rtcEngine.subscriber.peerConnection.remoteDescription.description,
)
val ws = wsFactory.ws
... ... @@ -126,16 +125,18 @@ class RTCEngineMockE2ETest : MockE2ETest() {
@Test
fun relayConfiguration() = runTest {
connect(with(SignalClientTest.JOIN.toBuilder()) {
join = with(join.toBuilder()) {
clientConfiguration = with(LivekitModels.ClientConfiguration.newBuilder()) {
forceRelay = LivekitModels.ClientConfigSetting.ENABLED
connect(
with(SignalClientTest.JOIN.toBuilder()) {
join = with(join.toBuilder()) {
clientConfiguration = with(LivekitModels.ClientConfiguration.newBuilder()) {
forceRelay = LivekitModels.ClientConfigSetting.ENABLED
build()
}
build()
}
build()
}
build()
})
},
)
val subPeerConnection = rtcEngine.subscriber.peerConnection as MockPeerConnection
assertEquals(PeerConnection.IceTransportsType.RELAY, subPeerConnection.rtcConfig.iceTransportsType)
... ... @@ -149,4 +150,4 @@ class RTCEngineMockE2ETest : MockE2ETest() {
val sid = wsFactory.request.url.queryParameter(SignalClient.CONNECT_QUERY_PARTICIPANT_SID)
assertEquals(SignalClientTest.JOIN.join.participant.sid, sid)
}
}
\ No newline at end of file
}
... ...
... ... @@ -16,5 +16,4 @@
package io.livekit.android.room
class RTCEngineTest {
}
\ No newline at end of file
class RTCEngineTest
... ...
... ... @@ -85,7 +85,6 @@ class RoomMockE2ETest : MockE2ETest() {
@Test
fun connectFailureProperlyContinues() = runTest {
var didThrowException = false
val job = coroutineRule.scope.launch {
try {
... ... @@ -115,18 +114,18 @@ class RoomMockE2ETest : MockE2ETest() {
assertEquals(
roomUpdate.roomUpdate.room.metadata,
room.metadata
room.metadata,
)
assertEquals(
roomUpdate.roomUpdate.room.activeRecording,
room.isRecording
room.isRecording,
)
assertIsClassList(
listOf(
RoomEvent.RoomMetadataChanged::class.java,
RoomEvent.RecordingStatusChanged::class.java,
),
events
events,
)
}
... ... @@ -136,7 +135,7 @@ class RoomMockE2ETest : MockE2ETest() {
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.CONNECTION_QUALITY.toOkioByteString()
SignalClientTest.CONNECTION_QUALITY.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
... ... @@ -159,7 +158,7 @@ class RoomMockE2ETest : MockE2ETest() {
RoomEvent.TrackPublished::class.java,
RoomEvent.TrackPublished::class.java,
),
events
events,
)
}
... ... @@ -168,13 +167,13 @@ class RoomMockE2ETest : MockE2ETest() {
connect()
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString(),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_DISCONNECT.toOkioByteString()
SignalClientTest.PARTICIPANT_DISCONNECT.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
... ... @@ -184,7 +183,7 @@ class RoomMockE2ETest : MockE2ETest() {
RoomEvent.TrackUnpublished::class.java,
RoomEvent.ParticipantDisconnected::class.java,
),
events
events,
)
}
... ... @@ -195,7 +194,7 @@ class RoomMockE2ETest : MockE2ETest() {
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.ACTIVE_SPEAKER_UPDATE.toOkioByteString()
SignalClientTest.ACTIVE_SPEAKER_UPDATE.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
... ... @@ -209,7 +208,7 @@ class RoomMockE2ETest : MockE2ETest() {
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString(),
)
// We intentionally don't emit if the track isn't subscribed, so need to
... ... @@ -221,15 +220,15 @@ class RoomMockE2ETest : MockE2ETest() {
MockMediaStream(
id = createMediaStreamId(
TestData.REMOTE_PARTICIPANT.sid,
TestData.REMOTE_AUDIO_TRACK.sid
)
)
)
TestData.REMOTE_AUDIO_TRACK.sid,
),
),
),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.STREAM_STATE_UPDATE.toOkioByteString()
SignalClientTest.STREAM_STATE_UPDATE.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
... ... @@ -246,7 +245,7 @@ class RoomMockE2ETest : MockE2ETest() {
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString(),
)
room.onAddTrack(
MockRtpReceiver.create(),
... ... @@ -255,15 +254,15 @@ class RoomMockE2ETest : MockE2ETest() {
MockMediaStream(
id = createMediaStreamId(
TestData.REMOTE_PARTICIPANT.sid,
TestData.REMOTE_AUDIO_TRACK.sid
)
)
)
TestData.REMOTE_AUDIO_TRACK.sid,
),
),
),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.SUBSCRIPTION_PERMISSION_UPDATE.toOkioByteString()
SignalClientTest.SUBSCRIPTION_PERMISSION_UPDATE.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
... ... @@ -306,13 +305,12 @@ class RoomMockE2ETest : MockE2ETest() {
val eventCollector = EventCollector(room.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.LEAVE.toOkioByteString()
SignalClientTest.LEAVE.toOkioByteString(),
)
val events = eventCollector.stopCollecting()
assertEquals(1, events.size)
assertEquals(true, events[0] is RoomEvent.Disconnected)
}
@Test
... ... @@ -322,8 +320,8 @@ class RoomMockE2ETest : MockE2ETest() {
room.localParticipant.publishAudioTrack(
LocalAudioTrack(
"",
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid)
)
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
... ... @@ -347,10 +345,10 @@ class RoomMockE2ETest : MockE2ETest() {
MockMediaStream(
id = createMediaStreamId(
TestData.REMOTE_PARTICIPANT.sid,
TestData.REMOTE_AUDIO_TRACK.sid
)
)
)
TestData.REMOTE_AUDIO_TRACK.sid,
),
),
),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
... ... @@ -363,9 +361,9 @@ class RoomMockE2ETest : MockE2ETest() {
RoomEvent.TrackUnpublished::class.java,
RoomEvent.TrackUnpublished::class.java,
RoomEvent.ParticipantDisconnected::class.java,
RoomEvent.Disconnected::class.java
RoomEvent.Disconnected::class.java,
),
events
events,
)
Assert.assertTrue(room.remoteParticipants.isEmpty())
}
... ... @@ -401,4 +399,4 @@ class RoomMockE2ETest : MockE2ETest() {
connect()
Assert.assertEquals(room.state, Room.State.CONNECTED)
}
}
\ No newline at end of file
}
... ...
... ... @@ -81,7 +81,6 @@ class RoomReconnectionMockE2ETest : MockE2ETest() {
@Test
fun softReconnectConfiguration() = runTest {
room.setReconnectionType(ReconnectType.FORCE_SOFT_RECONNECT)
connect()
prepareForReconnect()
... ... @@ -97,7 +96,6 @@ class RoomReconnectionMockE2ETest : MockE2ETest() {
val sentIceServers = SignalClientTest.RECONNECT.reconnect.iceServersList
.map { server -> server.toWebrtc() }
assertEquals(sentIceServers, rtcConfig.iceServers)
}
@Test
... ... @@ -109,8 +107,8 @@ class RoomReconnectionMockE2ETest : MockE2ETest() {
room.localParticipant.publishAudioTrack(
LocalAudioTrack(
"",
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid)
)
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
),
)
prepareForReconnect()
... ... @@ -132,4 +130,4 @@ class RoomReconnectionMockE2ETest : MockE2ETest() {
println(sentRequests)
Assert.assertTrue(sentAddTrack)
}
}
\ No newline at end of file
}
... ...
... ... @@ -32,13 +32,13 @@ import org.robolectric.ParameterizedRobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(ParameterizedRobolectricTestRunner::class)
class RoomReconnectionTypesMockE2ETest(
private val reconnectType: ReconnectType
private val reconnectType: ReconnectType,
) : MockE2ETest() {
companion object {
// parameters are provided as arrays, allowing more than one parameter
@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters(name = "Input: {0}")
// parameters are provided as arrays, allowing more than one parameter
fun params() = listOf(
ReconnectType.FORCE_SOFT_RECONNECT,
ReconnectType.FORCE_FULL_RECONNECT,
... ... @@ -87,7 +87,7 @@ class RoomReconnectionTypesMockE2ETest(
RoomEvent.Reconnecting::class.java,
RoomEvent.Reconnected::class.java,
),
events
events,
)
assertEquals(
... ... @@ -96,7 +96,7 @@ class RoomReconnectionTypesMockE2ETest(
Room.State.RECONNECTING,
Room.State.CONNECTED,
),
states
states,
)
}
... ... @@ -121,7 +121,7 @@ class RoomReconnectionTypesMockE2ETest(
RoomEvent.Reconnecting::class.java,
RoomEvent.Reconnected::class.java,
),
events
events,
)
assertEquals(
... ... @@ -130,7 +130,7 @@ class RoomReconnectionTypesMockE2ETest(
Room.State.RECONNECTING,
Room.State.CONNECTED,
),
states
states,
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -88,7 +88,7 @@ class RoomTest {
defaultDispatcher = coroutineRule.dispatcher,
ioDispatcher = coroutineRule.dispatcher,
audioHandler = NoAudioHandler(),
closeableManager = CloseableManager()
closeableManager = CloseableManager(),
)
}
... ... @@ -99,7 +99,6 @@ class RoomTest {
room.onJoinResponse(SignalClientTest.JOIN.join)
SignalClientTest.JOIN.join
}
}
rtcEngine.stub {
onBlocking { rtcEngine.client }
... ... @@ -140,7 +139,7 @@ class RoomTest {
RoomEvent.RoomMetadataChanged::class.java,
RoomEvent.RecordingStatusChanged::class.java,
),
events
events,
)
}
... ... @@ -187,7 +186,6 @@ class RoomTest {
@Test
fun onDisconnect() = runTest {
connect()
val eventCollector = EventCollector(room.events, coroutineRule.scope)
room.disconnect()
... ... @@ -204,4 +202,4 @@ class RoomTest {
assertNull(room.name)
assertFalse(room.isRecording)
}
}
\ No newline at end of file
}
... ...
... ... @@ -64,7 +64,7 @@ class SignalClientTest : BaseTest() {
ioDispatcher = coroutineRule.dispatcher,
networkInfo = object : NetworkInfo {
override fun getNetworkType() = NetworkType.WIFI
}
},
)
client.listener = listener
}
... ... @@ -180,7 +180,6 @@ class SignalClientTest : BaseTest() {
*/
@Test
fun queuedResponses() = runTest {
val inOrder = inOrder(listener)
val job = async {
client.join(EXAMPLE_URL, "")
... ... @@ -263,7 +262,6 @@ class SignalClientTest : BaseTest() {
@Test
fun pingTest() = runTest {
val joinResponseWithPing = with(JOIN.toBuilder()) {
join = with(join.toBuilder()) {
pingInterval = 10
... ... @@ -283,13 +281,15 @@ class SignalClientTest : BaseTest() {
assertFalse(originalWs.isClosed)
testScheduler.advanceTimeBy(15 * 1000)
assertTrue(originalWs.sentRequests.any { requestString ->
val sentRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(requestString.toPBByteString())
.build()
assertTrue(
originalWs.sentRequests.any { requestString ->
val sentRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(requestString.toPBByteString())
.build()
return@any sentRequest.hasPing()
})
return@any sentRequest.hasPing()
},
)
client.onMessage(wsFactory.ws, PONG.toOkioByteString())
... ... @@ -299,7 +299,6 @@ class SignalClientTest : BaseTest() {
@Test
fun pingTimeoutTest() = runTest {
val joinResponseWithPing = with(JOIN.toBuilder()) {
join = with(join.toBuilder()) {
pingInterval = 10
... ... @@ -336,12 +335,14 @@ class SignalClientTest : BaseTest() {
}
participant = TestData.LOCAL_PARTICIPANT
subscriberPrimary = true
addIceServers(with(ICEServer.newBuilder()) {
addUrls("stun:stun.join.com:19302")
username = "username"
credential = "credential"
build()
})
addIceServers(
with(ICEServer.newBuilder()) {
addUrls("stun:stun.join.com:19302")
username = "username"
credential = "credential"
build()
},
)
serverVersion = "0.15.2"
build()
}
... ... @@ -350,12 +351,14 @@ class SignalClientTest : BaseTest() {
val RECONNECT = with(LivekitRtc.SignalResponse.newBuilder()) {
reconnect = with(LivekitRtc.ReconnectResponse.newBuilder()) {
addIceServers(with(ICEServer.newBuilder()) {
addUrls("stun:stun.reconnect.com:19302")
username = "username"
credential = "credential"
build()
})
addIceServers(
with(ICEServer.newBuilder()) {
addUrls("stun:stun.reconnect.com:19302")
username = "username"
credential = "credential"
build()
},
)
clientConfiguration = with(ClientConfiguration.newBuilder()) {
forceRelay = LivekitModels.ClientConfigSetting.ENABLED
build()
... ... @@ -414,9 +417,9 @@ class SignalClientTest : BaseTest() {
.setCanPublishData(false)
.setHidden(false)
.setRecorder(false)
.build()
.build(),
)
.build()
.build(),
)
build()
}
... ... @@ -477,14 +480,15 @@ class SignalClientTest : BaseTest() {
build()
}
val CONNECTION_QUALITY = with(LivekitRtc.SignalResponse.newBuilder()) {
connectionQuality = with(LivekitRtc.ConnectionQualityUpdate.newBuilder()) {
addUpdates(with(LivekitRtc.ConnectionQualityInfo.newBuilder()) {
participantSid = JOIN.join.participant.sid
quality = LivekitModels.ConnectionQuality.EXCELLENT
build()
})
addUpdates(
with(LivekitRtc.ConnectionQualityInfo.newBuilder()) {
participantSid = JOIN.join.participant.sid
quality = LivekitModels.ConnectionQuality.EXCELLENT
build()
},
)
build()
}
build()
... ... @@ -492,12 +496,14 @@ class SignalClientTest : BaseTest() {
val STREAM_STATE_UPDATE = with(LivekitRtc.SignalResponse.newBuilder()) {
streamStateUpdate = with(LivekitRtc.StreamStateUpdate.newBuilder()) {
addStreamStates(with(LivekitRtc.StreamStateInfo.newBuilder()) {
participantSid = TestData.REMOTE_PARTICIPANT.sid
trackSid = TestData.REMOTE_AUDIO_TRACK.sid
state = LivekitRtc.StreamState.ACTIVE
build()
})
addStreamStates(
with(LivekitRtc.StreamStateInfo.newBuilder()) {
participantSid = TestData.REMOTE_PARTICIPANT.sid
trackSid = TestData.REMOTE_AUDIO_TRACK.sid
state = LivekitRtc.StreamState.ACTIVE
build()
},
)
build()
}
build()
... ... @@ -531,4 +537,4 @@ class SignalClientTest : BaseTest() {
build()
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -55,8 +55,8 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
room.localParticipant.publishAudioTrack(
LocalAudioTrack(
"",
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid)
)
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
),
)
room.disconnect()
... ... @@ -118,7 +118,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.LOCAL_PARTICIPANT_METADATA_CHANGED.toOkioByteString()
SignalClientTest.LOCAL_PARTICIPANT_METADATA_CHANGED.toOkioByteString(),
)
val roomEvents = roomEventsCollector.stopCollecting()
... ... @@ -134,7 +134,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
RoomEvent.ParticipantMetadataChanged::class.java,
RoomEvent.ParticipantNameChanged::class.java,
),
roomEvents
roomEvents,
)
assertIsClassList(
... ... @@ -142,7 +142,7 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
ParticipantEvent.MetadataChanged::class.java,
ParticipantEvent.NameChanged::class.java,
),
participantEvents
participantEvents,
)
}
... ... @@ -154,14 +154,14 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
isScreencast = false,
deviceId = null,
position = null,
captureParams = VideoCaptureParameter(width = 0, height = 0, maxFps = 0)
captureParams = VideoCaptureParameter(width = 0, height = 0, maxFps = 0),
),
rtcTrack = MockVideoStreamTrack(),
peerConnectionFactory = component.peerConnectionFactory(),
context = context,
eglBase = MockEglBase(),
defaultsManager = DefaultsManager(),
trackFactory = mock(LocalVideoTrack.Factory::class.java)
trackFactory = mock(LocalVideoTrack.Factory::class.java),
)
@Test
... ... @@ -174,11 +174,13 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
val peerConnection = component.rtcEngine().publisher.peerConnection
val transceiver = peerConnection.transceivers.first()
Mockito.verify(transceiver).setCodecPreferences(argThat { codecs ->
val preferredCodec = codecs.first()
return@argThat preferredCodec.name.lowercase() == "h264" &&
Mockito.verify(transceiver).setCodecPreferences(
argThat { codecs ->
val preferredCodec = codecs.first()
return@argThat preferredCodec.name.lowercase() == "h264" &&
preferredCodec.parameters["profile-level-id"] == "42e01f"
})
},
)
}
@Test
... ... @@ -191,9 +193,11 @@ class LocalParticipantMockE2ETest : MockE2ETest() {
val peerConnection = component.rtcEngine().publisher.peerConnection
val transceiver = peerConnection.transceivers.first()
Mockito.verify(transceiver).setCodecPreferences(argThat { codecs ->
val preferredCodec = codecs.first()
return@argThat preferredCodec.name.lowercase() == "vp8"
})
Mockito.verify(transceiver).setCodecPreferences(
argThat { codecs ->
val preferredCodec = codecs.first()
return@argThat preferredCodec.name.lowercase() == "vp8"
},
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,7 +31,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class ParticipantMockE2ETest : MockE2ETest() {
... ... @@ -44,8 +43,8 @@ class ParticipantMockE2ETest : MockE2ETest() {
room.localParticipant.publishAudioTrack(
LocalAudioTrack(
"",
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid)
)
MockAudioStreamTrack(id = SignalClientTest.LOCAL_TRACK_PUBLISHED.trackPublished.cid),
),
)
val eventCollector = EventCollector(room.events, coroutineRule.scope)
... ... @@ -76,7 +75,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString(),
)
val remoteParticipant = room.remoteParticipants.values.first()
... ... @@ -84,7 +83,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
val participantEventsCollector = EventCollector(remoteParticipant.events, coroutineRule.scope)
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.toOkioByteString()
SignalClientTest.REMOTE_PARTICIPANT_METADATA_CHANGED.toOkioByteString(),
)
val roomEvents = roomEventsCollector.stopCollecting()
val participantEvents = participantEventsCollector.stopCollecting()
... ... @@ -98,7 +97,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
RoomEvent.ParticipantMetadataChanged::class.java,
RoomEvent.ParticipantNameChanged::class.java,
),
roomEvents
roomEvents,
)
assertIsClassList(
... ... @@ -106,8 +105,7 @@ class ParticipantMockE2ETest : MockE2ETest() {
ParticipantEvent.MetadataChanged::class.java,
ParticipantEvent.NameChanged::class.java,
),
participantEvents
participantEvents,
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -182,4 +182,4 @@ class ParticipantTest {
.setMimeType("audio/mpeg")
.build()
}
}
\ No newline at end of file
}
... ...
... ... @@ -23,21 +23,21 @@ class ParticipantTrackPermissionTest {
fun requireSidOrIdentity() {
ParticipantTrackPermission(
participantIdentity = null,
participantSid = null
participantSid = null,
)
}
@Test
fun sidConstructionDoesntThrow() {
ParticipantTrackPermission(
participantSid = "sid"
participantSid = "sid",
)
}
@Test
fun identyConstructionDoesntThrow() {
ParticipantTrackPermission(
participantIdentity = "identity"
participantIdentity = "identity",
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -33,7 +33,6 @@ import org.mockito.Mockito
@OptIn(ExperimentalCoroutinesApi::class)
class RemoteParticipantTest : BaseTest() {
lateinit var signalClient: SignalClient
lateinit var participant: RemoteParticipant
... ... @@ -67,7 +66,6 @@ class RemoteParticipantTest : BaseTest() {
@Test
fun tracksFlow() = runTest {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
.build()
... ... @@ -83,7 +81,6 @@ class RemoteParticipantTest : BaseTest() {
@Test
fun audioTracksFlow() = runTest {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
.addTracks(TRACK_INFO)
.build()
... ... @@ -110,7 +107,6 @@ class RemoteParticipantTest : BaseTest() {
assertNull(participant.getTrackPublication(TRACK_INFO.sid))
}
@Test
fun unpublishTrackRemoves() {
val newTrackInfo = LivekitModels.ParticipantInfo.newBuilder(INFO)
... ... @@ -124,7 +120,6 @@ class RemoteParticipantTest : BaseTest() {
assertNull(participant.getTrackPublication(TRACK_INFO.sid))
}
companion object {
val INFO = LivekitModels.ParticipantInfo.newBuilder()
.setSid("sid")
... ...
... ... @@ -31,7 +31,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class RemoteTrackPublicationTest : MockE2ETest() {
... ... @@ -44,7 +43,7 @@ class RemoteTrackPublicationTest : MockE2ETest() {
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString(),
)
room.onAddTrack(
... ... @@ -54,10 +53,10 @@ class RemoteTrackPublicationTest : MockE2ETest() {
MockMediaStream(
id = createMediaStreamId(
TestData.REMOTE_PARTICIPANT.sid,
TestData.REMOTE_VIDEO_TRACK.sid
)
)
)
TestData.REMOTE_VIDEO_TRACK.sid,
),
),
),
)
advanceUntilIdle()
... ... @@ -80,4 +79,4 @@ class RemoteTrackPublicationTest : MockE2ETest() {
assertEquals(100, lastRequest.trackSetting.fps)
assertEquals(VideoQuality.LOW, lastRequest.trackSetting.quality)
}
}
\ No newline at end of file
}
... ...
... ... @@ -41,7 +41,7 @@ class RemoteVideoTrackTest : BaseTest() {
rtcTrack = MockVideoStreamTrack(),
autoManageVideo = true,
dispatcher = coroutineRule.dispatcher,
receiver = MockRtpReceiver.create()
receiver = MockRtpReceiver.create(),
)
}
... ... @@ -134,7 +134,7 @@ private class EmptyVideoSink : VideoSink {
private class CustomVisibility(
visible: Boolean = false,
size: Track.Dimensions = Track.Dimensions(0, 0)
size: Track.Dimensions = Track.Dimensions(0, 0),
) : VideoSinkVisibility() {
var visible = visible
... ... @@ -152,5 +152,4 @@ private class CustomVisibility(
override fun isVisible() = visible
override fun size() = size
}
\ No newline at end of file
}
... ...
... ... @@ -16,5 +16,4 @@
package io.livekit.android.room.util
class EncodingUtilsTest {
}
\ No newline at end of file
class EncodingUtilsTest
... ...
... ... @@ -21,4 +21,4 @@ import okio.ByteString.Companion.toByteString
fun com.google.protobuf.ByteString.toOkioByteString() = toByteArray().toByteString()
fun okio.ByteString.toPBByteString() = ByteString.copyFrom(toByteArray())
\ No newline at end of file
fun okio.ByteString.toPBByteString() = ByteString.copyFrom(toByteArray())
... ...
... ... @@ -45,7 +45,6 @@ class LoggingRule : TestRule {
println(t.toString())
}
}
}
override fun apply(base: Statement, description: Description?) = object : Statement() {
... ... @@ -58,4 +57,4 @@ class LoggingRule : TestRule {
LiveKit.loggingLevel = oldLoggingLevel
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -107,4 +107,4 @@ class RTCConfigurationTest : BaseTest() {
field.set(config, newValue)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,7 +25,7 @@ class MockPeerConnectionFactory : PeerConnectionFactory(1L) {
rtcConfig: PeerConnection.RTCConfiguration,
constraints: MediaConstraints?,
observer: PeerConnection.Observer?,
sslCertificateVerifier: SSLCertificateVerifier?
sslCertificateVerifier: SSLCertificateVerifier?,
): PeerConnection {
return MockPeerConnection(rtcConfig, observer)
}
... ... @@ -64,7 +64,7 @@ class MockPeerConnectionFactory : PeerConnectionFactory(1L) {
parameters = mapOf("profile-level-id" to "42e01f")
},
),
emptyList()
emptyList(),
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -24,7 +24,7 @@ import org.webrtc.RtpTransceiver.RtpTransceiverDirection
object MockRtpTransceiver {
fun create(
track: MediaStreamTrack,
init: RtpTransceiver.RtpTransceiverInit = RtpTransceiver.RtpTransceiverInit()
init: RtpTransceiver.RtpTransceiverInit = RtpTransceiver.RtpTransceiverInit(),
): RtpTransceiver {
val mock = Mockito.mock(RtpTransceiver::class.java)
... ... @@ -60,4 +60,4 @@ object MockRtpTransceiver {
return mock
}
}
\ No newline at end of file
}
... ...
... ... @@ -22,4 +22,4 @@ object NativeLibraryLoaderTestHelper {
NativeLibrary.initialize({ true }, "")
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -28,4 +28,4 @@ object WebRTCInitializer {
// do nothing. this is expected.
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -33,4 +33,4 @@ dependencies {
}
test {
environment "LINT_TEST_KOTLINC", ""
}
\ No newline at end of file
}
... ...
... ... @@ -32,7 +32,6 @@ import org.jetbrains.uast.tryResolve
class FlowDelegateUsageDetector : Detector(), SourceCodeScanner {
override fun visitReference(context: JavaContext, reference: UReferenceExpression, referenced: PsiElement) {
// Check if we're actually trying to access the flow delegate
val referencedMethod = referenced as? PsiMethod ?: return
if (referenced.name != GET_FLOW || referencedMethod.containingClass?.qualifiedName != FLOW_DELEGATE) {
... ... @@ -55,7 +54,7 @@ class FlowDelegateUsageDetector : Detector(), SourceCodeScanner {
}
is PsiField -> {
val receiverClass = (receiver.type as? PsiClassType)?.resolve()
println("${receiverClass}, ${receiverClass?.annotations?.fold("") { total, next -> "$total, ${next.text}" }}")
println("$receiverClass, ${receiverClass?.annotations?.fold("") { total, next -> "$total, ${next.text}" }}")
receiverClass?.hasAnnotation(FLOW_OBSERVABLE_ANNOTATION) ?: false
}
else -> {
... ... @@ -73,7 +72,6 @@ class FlowDelegateUsageDetector : Detector(), SourceCodeScanner {
override fun getApplicableReferenceNames(): List<String>? =
listOf("flow")
companion object {
// The name of the method for the flow accessor
... ... @@ -85,7 +83,9 @@ class FlowDelegateUsageDetector : Detector(), SourceCodeScanner {
private const val FLOW_OBSERVABLE_ANNOTATION = "io.livekit.android.util.FlowObservable"
private const val DEFAULT_MSG =
"Incorrect flow property usage: Only properties marked with the @FlowObservable annotation can be observed using `io.livekit.android.util.flow`. Improper usage will result in a NullPointerException."
"Incorrect flow property usage: Only properties marked with the @FlowObservable " +
"annotation can be observed using `io.livekit.android.util.flow`. " +
"Improper usage will result in a NullPointerException."
private val IMPLEMENTATION =
Implementation(FlowDelegateUsageDetector::class.java, Scope.JAVA_FILE_SCOPE)
... ... @@ -106,4 +106,4 @@ class FlowDelegateUsageDetector : Detector(), SourceCodeScanner {
implementation = IMPLEMENTATION
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -20,4 +20,4 @@ class IssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf(MediaTrackEqualsDetector.ISSUE, FlowDelegateUsageDetector.ISSUE)
}
\ No newline at end of file
}
... ...
@file:Suppress("UnstableApiUsage") // We know that Lint API's aren't final.
package io.livekit.lint
import com.android.tools.lint.client.api.UElementHandler
... ... @@ -36,8 +37,8 @@ class MediaTrackEqualsDetector : Detector(), SourceCodeScanner {
?.get(0)
?.getExpressionType()
?: return
if (left is PsiClassType && right is PsiClassType
&& (left.canonicalText == MEDIA_STREAM_TRACK || right.canonicalText == MEDIA_STREAM_TRACK)
if (left is PsiClassType && right is PsiClassType &&
(left.canonicalText == MEDIA_STREAM_TRACK || right.canonicalText == MEDIA_STREAM_TRACK)
) {
val message = DEFAULT_MSG
val location = context.getLocation(node)
... ... @@ -52,8 +53,8 @@ class MediaTrackEqualsDetector : Detector(), SourceCodeScanner {
) {
val left = node.leftOperand.getExpressionType() ?: return
val right = node.rightOperand.getExpressionType() ?: return
if (left is PsiClassType && right is PsiClassType
&& (left.canonicalText == MEDIA_STREAM_TRACK || right.canonicalText == MEDIA_STREAM_TRACK)
if (left is PsiClassType && right is PsiClassType &&
(left.canonicalText == MEDIA_STREAM_TRACK || right.canonicalText == MEDIA_STREAM_TRACK)
) {
val message = DEFAULT_MSG
val location = node.operatorIdentifier?.let {
... ... @@ -90,4 +91,4 @@ class MediaTrackEqualsDetector : Detector(), SourceCodeScanner {
implementation = IMPLEMENTATION
)
}
}
\ No newline at end of file
}
... ...
... ... @@ -245,7 +245,6 @@ fun flowAccess(): TestFile {
}
fun stateFlow(): TestFile {
return kotlin(
"""
package kotlinx.coroutines.flow
... ... @@ -255,4 +254,4 @@ fun stateFlow(): TestFile {
class MutableStateFlow<T>(override var value: T) : StateFlow<T>
"""
).indented()
}
\ No newline at end of file
}
... ...
... ... @@ -245,4 +245,4 @@ fun mediaStreamTrack(): TestFile {
}
"""
).indented()
}
\ No newline at end of file
}
... ...
... ... @@ -51,4 +51,4 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
\ No newline at end of file
}
... ...
package io.livekit.android.sample.basic
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ... @@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.livekit.android.sample.basic", appContext.packageName)
}
}
\ No newline at end of file
}
... ...
... ... @@ -37,12 +37,10 @@ class MainActivity : AppCompatActivity() {
}
private fun connectToRoom() {
val url = "wss://your_host"
val token = "your_token"
lifecycleScope.launch {
// Setup event handling.
launch {
room.events.collect { event ->
... ... @@ -121,4 +119,4 @@ class MainActivity : AppCompatActivity() {
super.onDestroy()
room.disconnect()
}
}
\ No newline at end of file
}
... ...
package io.livekit.android.sample.basic
import org.junit.Test
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
... ... @@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
}
... ...
# sample-app-common
Contains code common to `sample-app` and `sample-app-compose`.
\ No newline at end of file
Contains code common to `sample-app` and `sample-app-compose`.
... ...
... ... @@ -69,4 +69,4 @@ dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
\ No newline at end of file
}
... ...
package io.livekit.android.sample
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ... @@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.livekit.android.sample", appContext.packageName)
}
}
\ No newline at end of file
}
... ...
... ... @@ -61,10 +61,10 @@ class CallViewModel(
val participants = room::remoteParticipants.flow
.map { remoteParticipants ->
listOf<Participant>(room.localParticipant) +
remoteParticipants
.keys
.sortedBy { it }
.mapNotNull { remoteParticipants[it] }
remoteParticipants
.keys
.sortedBy { it }
.mapNotNull { remoteParticipants[it] }
}
private val mutableError = MutableStateFlow<Throwable?>(null)
... ... @@ -139,7 +139,6 @@ class CallViewModel(
connectToRoom()
}
// Start a foreground service to keep the call from being interrupted if the
// app goes into the background.
val foregroundServiceIntent = Intent(application, ForegroundService::class.java)
... ... @@ -163,7 +162,6 @@ class CallViewModel(
}
}
}
}
private suspend fun connectToRoom() {
... ... @@ -190,7 +188,6 @@ class CallViewModel(
}
private fun handlePrimarySpeaker(participantsList: List<Participant>, speakers: List<Participant>, room: Room?) {
var speaker = mutablePrimarySpeaker.value
// If speaker is local participant (due to defaults),
... ... @@ -333,4 +330,4 @@ class CallViewModel(
private fun <T> LiveData<T>.hide(): LiveData<T> = this
private fun <T> MutableStateFlow<T>.hide(): StateFlow<T> = this
private fun <T> Flow<T>.hide(): Flow<T> = this
\ No newline at end of file
private fun <T> Flow<T>.hide(): Flow<T> = this
... ...
... ... @@ -53,4 +53,4 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
const val TOKEN = BuildConfig.DEFAULT_TOKEN
const val E2EE_KEY = "12345678"
}
}
\ No newline at end of file
}
... ...
... ... @@ -31,7 +31,7 @@ open class ForegroundService : Service() {
startForeground(DEFAULT_NOTIFICATION_ID, actualNotification)
return START_NOT_STICKY
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val channel = NotificationChannel(
... ... @@ -51,4 +51,4 @@ open class ForegroundService : Service() {
const val DEFAULT_NOTIFICATION_ID = 3456
const val DEFAULT_CHANNEL_ID = "livekit_example_foreground"
}
}
\ No newline at end of file
}
... ...
... ... @@ -39,4 +39,4 @@ fun ComponentActivity.requestNeededPermissions(onPermissionsGranted: (() -> Unit
} else {
onPermissionsGranted?.invoke()
}
}
\ No newline at end of file
}
... ...
... ... @@ -3,4 +3,4 @@ package io.livekit.android.sample.util
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
fun <T> MutableLiveData<T>.hide(): LiveData<T> = this
\ No newline at end of file
fun <T> MutableLiveData<T>.hide(): LiveData<T> = this
... ...
# sample-app-compose
A sample video conferencing app for LiveKit made using Jetpack Compose.
\ No newline at end of file
A sample video conferencing app for LiveKit made using Jetpack Compose.
... ...
... ... @@ -76,4 +76,4 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
\ No newline at end of file
}
... ...
package io.livekit.android.composesample
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ... @@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.livekit.android.composesample", appContext.packageName)
}
}
\ No newline at end of file
}
... ...
... ... @@ -61,11 +61,10 @@ class CallActivity : AppCompatActivity() {
viewModel.startScreenCapture(data)
}
@OptIn(ExperimentalMaterialApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
// Setup compose view.
setContent {
... ... @@ -211,7 +210,6 @@ class CallActivity : AppCompatActivity() {
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
val controlSize = 40.dp
val controlPadding = 4.dp
Row(
... ... @@ -458,4 +456,4 @@ class CallActivity : AppCompatActivity() {
val e2eeKey: String,
val e2eeOn: Boolean
) : Parcelable
}
\ No newline at end of file
}
... ...
... ... @@ -32,7 +32,6 @@ fun ParticipantItem(
modifier: Modifier = Modifier,
isSpeaking: Boolean,
) {
val identity by participant::identity.flow.collectAsState()
val audioTracks by participant::audioTracks.flow.collectAsState()
val identityBarPadding = 4.dp
... ... @@ -123,4 +122,4 @@ fun ParticipantItem(
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -10,4 +10,4 @@ class SampleApplication : Application() {
super.onCreate()
LiveKit.loggingLevel = LoggingLevel.VERBOSE
}
}
\ No newline at end of file
}
... ...
... ... @@ -72,4 +72,4 @@ fun VideoItemTrackSelector(
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,4 +25,4 @@ fun <VM> createViewModelFactoryFactory(
?: throw IllegalArgumentException("Unknown viewmodel class!")
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -52,4 +52,4 @@ fun DebugMenuDialog(
}
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -50,4 +50,4 @@ fun SelectAudioDeviceDialog(
}
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -6,4 +6,4 @@ val BlueMain = Color(0xFF007DFF)
val BlueDark = Color(0xFF0058B3)
val BlueLight = Color(0xFF66B1FF)
val NoVideoIconTint = Color(0xFF5A8BFF)
val NoVideoBackground = Color(0xFF00153C)
\ No newline at end of file
val NoVideoBackground = Color(0xFF00153C)
... ...
... ... @@ -8,4 +8,4 @@ val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
\ No newline at end of file
)
... ...
... ... @@ -29,7 +29,7 @@ private val LightColorPalette = lightColors(
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
*/
)
@Composable
... ... @@ -49,4 +49,4 @@ fun AppTheme(
shapes = Shapes,
content = content
)
}
\ No newline at end of file
}
... ...
... ... @@ -24,5 +24,5 @@ val Typography = Typography(
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)
\ No newline at end of file
*/
)
... ...
package io.livekit.android.composesample
import org.junit.Test
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
... ... @@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
}
... ...
... ... @@ -7,4 +7,4 @@ and video frames are passed into a `VideoFileRenderer` object, where they are th
`android.media.MediaCodec`and saved using a `android.media.MediaMuxer` into a video file.
Videos are saved to the app's external files directory (
normally `/sdcard/Android/data/io.livekit.android.sample.record/files/Movies`).
\ No newline at end of file
normally `/sdcard/Android/data/io.livekit.android.sample.record/files/Movies`).
... ...
... ... @@ -60,4 +60,4 @@ dependencies {
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
\ No newline at end of file
}
... ...
package io.livekit.android.sample.record
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ... @@ -21,4 +19,4 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.livekit.android.sample.record", appContext.packageName)
}
}
\ No newline at end of file
}
... ...
... ... @@ -78,12 +78,10 @@ class MainActivity : ComponentActivity() {
}
private fun connectToRoom() {
val url = "wss://www.example.com"
val token = ""
lifecycleScope.launch {
// Connect to server.
room.connect(
url,
... ... @@ -124,4 +122,3 @@ class MainActivity : ComponentActivity() {
connected.value = false
}
}
... ...
... ... @@ -5,4 +5,4 @@ import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
\ No newline at end of file
val Teal200 = Color(0xFF03DAC5)
... ...
... ... @@ -8,4 +8,4 @@ val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
\ No newline at end of file
)
... ...
... ... @@ -24,7 +24,7 @@ private val LightColorPalette = lightColors(
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
*/
)
@Composable
... ... @@ -41,4 +41,4 @@ fun LivekitandroidTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Co
shapes = Shapes,
content = content
)
}
\ No newline at end of file
}
... ...
... ... @@ -24,5 +24,5 @@ val Typography = Typography(
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)
\ No newline at end of file
*/
)
... ...
package io.livekit.android.sample.record
import org.junit.Test
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
... ... @@ -14,4 +13,4 @@ class ExampleUnitTest {
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
\ No newline at end of file
}
... ...
# sample-app
A sample video conferencing app for LiveKit.
\ No newline at end of file
A sample video conferencing app for LiveKit.
... ...
package io.livekit.android
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
... ...
... ... @@ -13,7 +13,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.xwray.groupie.GroupieAdapter
import io.livekit.android.e2ee.E2EEOptions
import io.livekit.android.sample.databinding.CallActivityBinding
import io.livekit.android.sample.dialog.showDebugMenuDialog
import io.livekit.android.sample.dialog.showSelectAudioDeviceDialog
... ... @@ -42,7 +41,7 @@ class CallActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
binding = CallActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
... ... @@ -80,16 +79,22 @@ class CallActivity : AppCompatActivity() {
viewModel.cameraEnabled.observe(this) { enabled ->
binding.camera.setOnClickListener { viewModel.setCameraEnabled(!enabled) }
binding.camera.setImageResource(
if (enabled) R.drawable.outline_videocam_24
else R.drawable.outline_videocam_off_24
if (enabled) {
R.drawable.outline_videocam_24
} else {
R.drawable.outline_videocam_off_24
}
)
binding.flipCamera.isEnabled = enabled
}
viewModel.micEnabled.observe(this) { enabled ->
binding.mic.setOnClickListener { viewModel.setMicEnabled(!enabled) }
binding.mic.setImageResource(
if (enabled) R.drawable.outline_mic_24
else R.drawable.outline_mic_off_24
if (enabled) {
R.drawable.outline_mic_24
} else {
R.drawable.outline_mic_off_24
}
)
}
... ... @@ -103,8 +108,11 @@ class CallActivity : AppCompatActivity() {
}
}
binding.screenShare.setImageResource(
if (enabled) R.drawable.baseline_cast_connected_24
else R.drawable.baseline_cast_24
if (enabled) {
R.drawable.baseline_cast_connected_24
} else {
R.drawable.baseline_cast_24
}
)
}
... ... @@ -178,4 +186,4 @@ class CallActivity : AppCompatActivity() {
@Parcelize
data class BundleArgs(val url: String, val token: String, val e2ee: Boolean, val e2eeKey: String) : Parcelable
}
\ No newline at end of file
}
... ...
package io.livekit.android.sample
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import io.livekit.android.sample.databinding.MainActivityBinding
import io.livekit.android.sample.util.requestNeededPermissions
class MainActivity : AppCompatActivity() {
val viewModel by viewModels<MainViewModel>()
... ... @@ -49,7 +43,6 @@ class MainActivity : AppCompatActivity() {
}
saveButton.setOnClickListener {
viewModel.setSavedUrl(url.editText?.text?.toString() ?: "")
viewModel.setSavedToken(token.editText?.text?.toString() ?: "")
viewModel.setSavedE2EEOn(e2eeEnabled.isChecked)
... ... @@ -81,4 +74,4 @@ class MainActivity : AppCompatActivity() {
requestNeededPermissions()
}
}
\ No newline at end of file
}
... ...
... ... @@ -2,7 +2,6 @@
package io.livekit.android.sample
import android.view.View
import com.github.ajalt.timberkt.Timber
import com.xwray.groupie.viewbinding.BindableItem
... ... @@ -42,7 +41,6 @@ class ParticipantItem(
}
override fun bind(viewBinding: ParticipantItemBinding, position: Int) {
ensureCoroutineScope()
coroutineScope?.launch {
participant::identity.flow.collect { identity ->
... ... @@ -152,10 +150,11 @@ class ParticipantItem(
}
override fun getLayout(): Int =
if (speakerView)
if (speakerView) {
R.layout.speaker_view
else
} else {
R.layout.participant_item
}
}
private fun View.visibleOrGone(visible: Boolean) {
... ... @@ -192,4 +191,4 @@ private inline fun <T, R> Flow<T?>.flatMapLatestOrNull(
transform(it)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -10,4 +10,4 @@ class SampleApplication : Application() {
super.onCreate()
LiveKit.loggingLevel = LoggingLevel.VERBOSE
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,4 +25,4 @@ fun <VM> createViewModelFactoryFactory(
?: throw IllegalArgumentException("Unknown viewmodel class!")
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -6,7 +6,6 @@ import android.app.AlertDialog
import android.widget.ArrayAdapter
import io.livekit.android.sample.CallViewModel
fun Activity.showDebugMenuDialog(callViewModel: CallViewModel) {
val builder = with(AlertDialog.Builder(this)) {
setTitle("Debug Menu")
... ... @@ -23,4 +22,4 @@ fun Activity.showDebugMenuDialog(callViewModel: CallViewModel) {
}
}
builder.show()
}
\ No newline at end of file
}
... ...
... ... @@ -6,7 +6,6 @@ import android.app.AlertDialog
import android.widget.ArrayAdapter
import io.livekit.android.sample.CallViewModel
fun Activity.showSelectAudioDeviceDialog(callViewModel: CallViewModel) {
val builder = with(AlertDialog.Builder(this)) {
setTitle("Select Audio Device")
... ... @@ -21,4 +20,4 @@ fun Activity.showSelectAudioDeviceDialog(callViewModel: CallViewModel) {
}
}
builder.show()
}
\ No newline at end of file
}
... ...
package io.livekit.android
import org.junit.Test
import org.junit.Assert.*
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
... ...
# video-encode-decode-test
Tests for checking various video codec capabilities in simulcast.
\ No newline at end of file
Tests for checking various video codec capabilities in simulcast.
... ...
... ... @@ -101,4 +101,4 @@ dependencies {
androidTestImplementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
androidTestImplementation "io.jsonwebtoken:jjwt-jackson:0.11.2"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
\ No newline at end of file
}
... ...
... ... @@ -13,7 +13,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
*
* @param intentFactory A lambda that provides a Context that can used to create an intent. A intent needs to be returned.
*/
inline fun <A: ComponentActivity> createAndroidIntentComposeRule(intentFactory: (context: Context) -> Intent) : AndroidComposeTestRule<ActivityScenarioRule<A>, A> {
inline fun <A : ComponentActivity> createAndroidIntentComposeRule(intentFactory: (context: Context) -> Intent): AndroidComposeTestRule<ActivityScenarioRule<A>, A> {
val context = ApplicationProvider.getApplicationContext<Context>()
val intent = intentFactory(context)
... ... @@ -34,4 +34,4 @@ fun <A : ComponentActivity> ActivityScenarioRule<A>.getActivity(): A {
scenario.onActivity { activity = it }
return activity ?: throw IllegalStateException("Activity was not set in the ActivityScenarioRule!")
}
\ No newline at end of file
}
... ...
... ... @@ -28,4 +28,4 @@ fun Int.toAlphaNumeric(): Char {
return 'A' + offset
}
throw IllegalArgumentException()
}
\ No newline at end of file
}
... ...
... ... @@ -24,7 +24,7 @@ abstract class VideoTest {
private fun createToken(name: String) = Jwts.builder()
.setIssuer(API_KEY)
.setExpiration(Date(System.currentTimeMillis() + 1000 * 60 * 60 /* 1hour */))
.setExpiration(Date(System.currentTimeMillis() + 1000 * 60 * 60))
.setNotBefore(Date(0))
.setSubject(name)
.setId(name)
... ... @@ -56,5 +56,4 @@ abstract class VideoTest {
composeTestRule.onAllNodesWithTag(VIDEO_FRAME_INDICATOR).fetchSemanticsNodes(false, "").isNotEmpty()
}
}
}
... ...
... ... @@ -29,4 +29,4 @@ class OfficialCodecSupportTest {
fun isH264Supported() {
Assert.assertTrue(defaultVideoEncoderFactory.supportedCodecs.any { it.name == "H264" })
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,4 +21,4 @@ class H264DefaultVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -20,4 +20,4 @@ class NoWhitelistDefaultVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,4 +21,4 @@ class VP8DefaultVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,4 +21,4 @@ class H264SimulcastVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -20,4 +20,4 @@ class NoWhitelistSimulcastVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -21,4 +21,4 @@ class VP8DefaultVideoTest : VideoTest() {
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -18,7 +18,6 @@ import androidx.constraintlayout.compose.Dimension
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import com.google.accompanist.pager.ExperimentalPagerApi
import io.livekit.android.composesample.ui.theme.AppTheme
import kotlinx.parcelize.Parcelize
... ... @@ -33,7 +32,6 @@ class CallActivity : AppCompatActivity() {
val viewModelProvider = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val args = intent.getParcelableExtra<BundleArgs>(KEY_ARGS)
?: throw NullPointerException("args is null!")
... ... @@ -87,7 +85,8 @@ class CallActivity : AppCompatActivity() {
bottom.linkTo(bottomConnectionItem.top)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
}) {
}
) {
ConnectionItem(viewModel = viewModel1)
}
... ... @@ -104,7 +103,8 @@ class CallActivity : AppCompatActivity() {
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
height = Dimension.fillToConstraints
}) {
}
) {
ConnectionItem(viewModel = viewModel2)
}
}
... ... @@ -128,4 +128,4 @@ class CallActivity : AppCompatActivity() {
val useDefaultVideoEncoderFactory: Boolean = false,
val codecWhiteList: List<String>? = null,
) : Parcelable
}
\ No newline at end of file
}
... ...
... ... @@ -7,8 +7,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import com.github.ajalt.timberkt.Timber
import io.livekit.android.LiveKit
import io.livekit.android.RoomOptions
import io.livekit.android.LiveKitOverrides
import io.livekit.android.RoomOptions
import io.livekit.android.room.Room
import io.livekit.android.room.participant.Participant
import io.livekit.android.room.participant.VideoTrackPublishDefaults
... ... @@ -36,10 +36,10 @@ class CallViewModel(
room::remoteParticipants.flow
.map { remoteParticipants ->
listOf<Participant>(room.localParticipant) +
remoteParticipants
.keys
.sortedBy { it }
.mapNotNull { remoteParticipants[it] }
remoteParticipants
.keys
.sortedBy { it }
.mapNotNull { remoteParticipants[it] }
}
} else {
flowOf(emptyList())
... ... @@ -98,7 +98,6 @@ class CallViewModel(
localParticipant.publishVideoTrack(videoTrack)
}
mutableRoom.value = room
} catch (e: Throwable) {
mutableError.value = e
}
... ... @@ -114,4 +113,4 @@ class CallViewModel(
private fun <T> LiveData<T>.hide(): LiveData<T> = this
private fun <T> MutableStateFlow<T>.hide(): StateFlow<T> = this
private fun <T> Flow<T>.hide(): Flow<T> = this
\ No newline at end of file
private fun <T> Flow<T>.hide(): Flow<T> = this
... ...
... ... @@ -11,7 +11,6 @@ import io.livekit.android.room.participant.Participant
*/
@Composable
fun ConnectionItem(viewModel: CallViewModel) {
val room by viewModel.room.collectAsState()
val participants by viewModel.participants.collectAsState(initial = emptyList())
if (room != null) {
... ... @@ -25,4 +24,4 @@ fun RoomItem(room: Room, participants: List<Participant>) {
if (remoteParticipant != null) {
ParticipantItem(room = room, participant = remoteParticipant, isSpeaking = false)
}
}
\ No newline at end of file
}
... ...
... ... @@ -51,7 +51,6 @@ class DummyVideoCapturer(@ColorInt var color: Int) : VideoCapturer {
}
private fun createFrame(): VideoFrame {
val captureTimeNs = TimeUnit.MILLISECONDS.toNanos(SystemClock.elapsedRealtime())
val buffer = JavaI420Buffer.allocate(this.frameWidth, this.frameHeight)
... ...
... ... @@ -176,5 +176,4 @@ class MainActivity : ComponentActivity() {
requestPermissionLauncher.launch(neededPermissions)
}
}
}
... ...
... ... @@ -45,6 +45,5 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
""
const val TOKEN2 =
""
}
}
\ No newline at end of file
}
... ...
... ... @@ -32,7 +32,6 @@ fun ParticipantItem(
modifier: Modifier = Modifier,
isSpeaking: Boolean,
) {
val identity by participant::identity.flow.collectAsState()
val videoTracks by participant::videoTracks.flow.collectAsState()
val audioTracks by participant::audioTracks.flow.collectAsState()
... ... @@ -124,4 +123,4 @@ fun ParticipantItem(
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -10,4 +10,4 @@ class SampleApplication : Application() {
super.onCreate()
LiveKit.loggingLevel = LoggingLevel.VERBOSE
}
}
\ No newline at end of file
}
... ...
... ... @@ -35,7 +35,6 @@ fun VideoItem(
videoTrack: VideoTrack,
modifier: Modifier = Modifier
) {
val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
var videoView: TextureViewRenderer? by remember { mutableStateOf(null) }
... ... @@ -112,6 +111,7 @@ fun VideoItem(
}
const val VIDEO_FRAME_INDICATOR = "frame_indicator"
/**
* This widget primarily serves as a way to observe changes in [videoTracks].
*/
... ... @@ -144,4 +144,4 @@ fun VideoItemTrackSelector(
)
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -25,4 +25,4 @@ fun <VM> createViewModelFactoryFactory(
?: throw IllegalArgumentException("Unknown viewmodel class!")
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -22,4 +22,4 @@ class WhitelistDefaultVideoEncoderFactory(
return filteredCodecs
}
}
\ No newline at end of file
}
... ...
... ... @@ -2,4 +2,4 @@ package io.livekit.android.videoencodedecode
interface WhitelistEncoderFactory {
var codecWhitelist: List<String>?
}
\ No newline at end of file
}
... ...
... ... @@ -22,4 +22,4 @@ class WhitelistSimulcastVideoEncoderFactory(
return filteredCodecs
}
}
\ No newline at end of file
}
... ...
... ... @@ -45,4 +45,4 @@ fun DebugMenuDialog(
}
}
}
}
\ No newline at end of file
}
... ...
... ... @@ -6,4 +6,4 @@ val BlueMain = Color(0xFF007DFF)
val BlueDark = Color(0xFF0058B3)
val BlueLight = Color(0xFF66B1FF)
val NoVideoIconTint = Color(0xFF5A8BFF)
val NoVideoBackground = Color(0xFF00153C)
\ No newline at end of file
val NoVideoBackground = Color(0xFF00153C)
... ...
... ... @@ -8,4 +8,4 @@ val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)
\ No newline at end of file
)
... ...
... ... @@ -29,7 +29,7 @@ private val LightColorPalette = lightColors(
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
*/
)
@Composable
... ... @@ -49,4 +49,4 @@ fun AppTheme(
shapes = Shapes,
content = content
)
}
\ No newline at end of file
}
... ...
... ... @@ -24,5 +24,5 @@ val Typography = Typography(
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
*/
)
\ No newline at end of file
*/
)
... ...