Committed by
GitHub
Fix VirtualBackgroundVideoProcessor not responding to changing backgroundImage (#752)
* Fix VirtualBackgroundVideoProcessor not responding to changes in backgroundImage * lint fixes * spotless
正在显示
43 个修改的文件
包含
335 行增加
和
174 行删除
.changeset/olive-bottles-repeat.md
0 → 100644
| @@ -45,6 +45,17 @@ class MainActivity : AppCompatActivity() { | @@ -45,6 +45,17 @@ class MainActivity : AppCompatActivity() { | ||
| 45 | enableButton.setText(if (state) "Disable" else "Enable") | 45 | enableButton.setText(if (state) "Disable" else "Enable") |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | + val enableBackgroundButton = findViewById<Button>(R.id.buttonBackground) | ||
| 49 | + enableBackgroundButton.setOnClickListener { | ||
| 50 | + val state = viewModel.toggleVirtualBackground() | ||
| 51 | + enableBackgroundButton.setText(if (state) "Disable Background" else "Enable Background") | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + val flipCameraButton = findViewById<Button>(R.id.buttonFlip) | ||
| 55 | + flipCameraButton.setOnClickListener { | ||
| 56 | + viewModel.flipCamera() | ||
| 57 | + } | ||
| 58 | + | ||
| 48 | val renderer = findViewById<TextureViewRenderer>(R.id.renderer) | 59 | val renderer = findViewById<TextureViewRenderer>(R.id.renderer) |
| 49 | viewModel.room.initVideoRenderer(renderer) | 60 | viewModel.room.initVideoRenderer(renderer) |
| 50 | viewModel.track.observe(this) { track -> | 61 | viewModel.track.observe(this) { track -> |
| @@ -87,7 +98,7 @@ fun ComponentActivity.requestNeededPermissions(onPermissionsGranted: (() -> Unit | @@ -87,7 +98,7 @@ fun ComponentActivity.requestNeededPermissions(onPermissionsGranted: (() -> Unit | ||
| 87 | } | 98 | } |
| 88 | } | 99 | } |
| 89 | 100 | ||
| 90 | - val neededPermissions = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA) | 101 | + val neededPermissions = listOf(Manifest.permission.CAMERA) |
| 91 | .filter { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_DENIED } | 102 | .filter { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_DENIED } |
| 92 | .toTypedArray() | 103 | .toTypedArray() |
| 93 | 104 |
| @@ -54,10 +54,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | @@ -54,10 +54,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | ||
| 54 | eglBase = eglBase, | 54 | eglBase = eglBase, |
| 55 | ), | 55 | ), |
| 56 | ) | 56 | ) |
| 57 | + | ||
| 58 | + private val virtualBackground = (AppCompatResources.getDrawable(application, R.drawable.background) as BitmapDrawable).bitmap | ||
| 59 | + | ||
| 57 | private var blur = 16f | 60 | private var blur = 16f |
| 58 | private val processor = VirtualBackgroundVideoProcessor(eglBase, Dispatchers.IO, initialBlurRadius = blur).apply { | 61 | private val processor = VirtualBackgroundVideoProcessor(eglBase, Dispatchers.IO, initialBlurRadius = blur).apply { |
| 59 | - val drawable = AppCompatResources.getDrawable(application, R.drawable.background) as BitmapDrawable | ||
| 60 | - backgroundImage = drawable.bitmap | 62 | + backgroundImage = virtualBackground |
| 61 | } | 63 | } |
| 62 | 64 | ||
| 63 | private var cameraProvider: CameraCapturerUtils.CameraProvider? = null | 65 | private var cameraProvider: CameraCapturerUtils.CameraProvider? = null |
| @@ -119,4 +121,24 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | @@ -119,4 +121,24 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { | ||
| 119 | blur += 5 | 121 | blur += 5 |
| 120 | processor.updateBlurRadius(blur) | 122 | processor.updateBlurRadius(blur) |
| 121 | } | 123 | } |
| 124 | + | ||
| 125 | + fun toggleVirtualBackground(): Boolean { | ||
| 126 | + if (processor.backgroundImage != virtualBackground) { | ||
| 127 | + processor.backgroundImage = virtualBackground | ||
| 128 | + return true | ||
| 129 | + } else { | ||
| 130 | + processor.backgroundImage = null | ||
| 131 | + return false | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + fun flipCamera() { | ||
| 136 | + val videoTrack = track.value ?: return | ||
| 137 | + val newPosition = when (videoTrack.options.position) { | ||
| 138 | + CameraPosition.FRONT -> CameraPosition.BACK | ||
| 139 | + CameraPosition.BACK -> CameraPosition.FRONT | ||
| 140 | + else -> CameraPosition.FRONT | ||
| 141 | + } | ||
| 142 | + videoTrack.switchCamera(position = newPosition) | ||
| 143 | + } | ||
| 122 | } | 144 | } |
| @@ -10,12 +10,32 @@ | @@ -10,12 +10,32 @@ | ||
| 10 | android:layout_width="match_parent" | 10 | android:layout_width="match_parent" |
| 11 | android:layout_height="match_parent" /> | 11 | android:layout_height="match_parent" /> |
| 12 | 12 | ||
| 13 | - <Button | ||
| 14 | - android:id="@+id/button" | 13 | + <LinearLayout |
| 15 | android:layout_width="wrap_content" | 14 | android:layout_width="wrap_content" |
| 16 | android:layout_height="wrap_content" | 15 | android:layout_height="wrap_content" |
| 17 | - android:layout_margin="10dp" | ||
| 18 | - android:text="Disable" /> | 16 | + android:orientation="vertical"> |
| 17 | + | ||
| 18 | + <Button | ||
| 19 | + android:id="@+id/button" | ||
| 20 | + android:layout_width="wrap_content" | ||
| 21 | + android:layout_height="wrap_content" | ||
| 22 | + android:layout_margin="10dp" | ||
| 23 | + android:text="Disable" /> | ||
| 24 | + | ||
| 25 | + <Button | ||
| 26 | + android:id="@+id/buttonBackground" | ||
| 27 | + android:layout_width="wrap_content" | ||
| 28 | + android:layout_height="wrap_content" | ||
| 29 | + android:layout_margin="10dp" | ||
| 30 | + android:text="Disable Background" /> | ||
| 31 | + | ||
| 32 | + <Button | ||
| 33 | + android:id="@+id/buttonFlip" | ||
| 34 | + android:layout_width="wrap_content" | ||
| 35 | + android:layout_height="wrap_content" | ||
| 36 | + android:layout_margin="10dp" | ||
| 37 | + android:text="Flip Camera" /> | ||
| 38 | + </LinearLayout> | ||
| 19 | 39 | ||
| 20 | <Button | 40 | <Button |
| 21 | android:id="@+id/buttonIncreaseBlur" | 41 | android:id="@+id/buttonIncreaseBlur" |
| @@ -26,7 +26,6 @@ import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_O | @@ -26,7 +26,6 @@ import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_O | ||
| 26 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON | 26 | import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON |
| 27 | import android.hardware.camera2.CaptureRequest | 27 | import android.hardware.camera2.CaptureRequest |
| 28 | import android.os.Build | 28 | import android.os.Build |
| 29 | -import android.os.Build.VERSION | ||
| 30 | import android.os.Handler | 29 | import android.os.Handler |
| 31 | import android.util.Range | 30 | import android.util.Range |
| 32 | import android.util.Size | 31 | import android.util.Size |
| @@ -345,7 +344,7 @@ internal constructor( | @@ -345,7 +344,7 @@ internal constructor( | ||
| 345 | if (id == deviceId) return CameraDeviceId(id, null) | 344 | if (id == deviceId) return CameraDeviceId(id, null) |
| 346 | 345 | ||
| 347 | // Then check if deviceId is a physical camera ID in a logical camera | 346 | // Then check if deviceId is a physical camera ID in a logical camera |
| 348 | - if (VERSION.SDK_INT >= Build.VERSION_CODES.P) { | 347 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { |
| 349 | val characteristic = cameraManager.getCameraCharacteristics(id) | 348 | val characteristic = cameraManager.getCameraCharacteristics(id) |
| 350 | 349 | ||
| 351 | for (physicalId in characteristic.physicalCameraIds) { | 350 | for (physicalId in characteristic.physicalCameraIds) { |
| @@ -23,7 +23,12 @@ import android.os.Build | @@ -23,7 +23,12 @@ import android.os.Build | ||
| 23 | import android.os.Handler | 23 | import android.os.Handler |
| 24 | import android.os.HandlerThread | 24 | import android.os.HandlerThread |
| 25 | import android.os.Looper | 25 | import android.os.Looper |
| 26 | -import com.twilio.audioswitch.* | 26 | +import com.twilio.audioswitch.AbstractAudioSwitch |
| 27 | +import com.twilio.audioswitch.AudioDevice | ||
| 28 | +import com.twilio.audioswitch.AudioDeviceChangeListener | ||
| 29 | +import com.twilio.audioswitch.AudioSwitch | ||
| 30 | +import com.twilio.audioswitch.LegacyAudioSwitch | ||
| 31 | +import io.livekit.android.room.Room | ||
| 27 | import io.livekit.android.util.LKLog | 32 | import io.livekit.android.util.LKLog |
| 28 | import javax.inject.Inject | 33 | import javax.inject.Inject |
| 29 | import javax.inject.Singleton | 34 | import javax.inject.Singleton |
| @@ -234,7 +234,6 @@ constructor( | @@ -234,7 +234,6 @@ constructor( | ||
| 234 | AudioFormat.ENCODING_PCM_8BIT -> 1 | 234 | AudioFormat.ENCODING_PCM_8BIT -> 1 |
| 235 | AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2 | 235 | AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2 |
| 236 | AudioFormat.ENCODING_PCM_FLOAT -> 4 | 236 | AudioFormat.ENCODING_PCM_FLOAT -> 4 |
| 237 | - AudioFormat.ENCODING_INVALID -> throw IllegalArgumentException("Bad audio format $audioFormat") | ||
| 238 | else -> throw IllegalArgumentException("Bad audio format $audioFormat") | 237 | else -> throw IllegalArgumentException("Bad audio format $audioFormat") |
| 239 | } | 238 | } |
| 240 | } | 239 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -58,7 +58,7 @@ constructor( | @@ -58,7 +58,7 @@ constructor( | ||
| 58 | this.peerConnectionFactory = peerConnectionFactory | 58 | this.peerConnectionFactory = peerConnectionFactory |
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | - public fun keyProvider(): KeyProvider { | 61 | + fun keyProvider(): KeyProvider { |
| 62 | return this.keyProvider | 62 | return this.keyProvider |
| 63 | } | 63 | } |
| 64 | 64 | ||
| @@ -70,16 +70,16 @@ constructor( | @@ -70,16 +70,16 @@ constructor( | ||
| 70 | this.enabled = true | 70 | this.enabled = true |
| 71 | this.room = room | 71 | this.room = room |
| 72 | this.emitEvent = emitEvent | 72 | this.emitEvent = emitEvent |
| 73 | - this.room?.localParticipant?.trackPublications?.forEach() { item -> | 73 | + this.room?.localParticipant?.trackPublications?.forEach { item -> |
| 74 | var participant = this.room!!.localParticipant | 74 | var participant = this.room!!.localParticipant |
| 75 | var publication = item.value | 75 | var publication = item.value |
| 76 | if (publication.track != null) { | 76 | if (publication.track != null) { |
| 77 | addPublishedTrack(publication.track!!, publication, participant, room) | 77 | addPublishedTrack(publication.track!!, publication, participant, room) |
| 78 | } | 78 | } |
| 79 | } | 79 | } |
| 80 | - this.room?.remoteParticipants?.forEach() { item -> | 80 | + this.room?.remoteParticipants?.forEach { item -> |
| 81 | var participant = item.value | 81 | var participant = item.value |
| 82 | - participant.trackPublications.forEach() { item -> | 82 | + participant.trackPublications.forEach { item -> |
| 83 | var publication = item.value | 83 | var publication = item.value |
| 84 | if (publication.track != null) { | 84 | if (publication.track != null) { |
| 85 | addSubscribedTrack(publication.track!!, publication, participant, room) | 85 | addSubscribedTrack(publication.track!!, publication, participant, room) |
| @@ -88,7 +88,7 @@ constructor( | @@ -88,7 +88,7 @@ constructor( | ||
| 88 | } | 88 | } |
| 89 | } | 89 | } |
| 90 | 90 | ||
| 91 | - public fun addSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) { | 91 | + fun addSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) { |
| 92 | var rtpReceiver: RtpReceiver? = when (publication.track!!) { | 92 | var rtpReceiver: RtpReceiver? = when (publication.track!!) { |
| 93 | is RemoteAudioTrack -> (publication.track!! as RemoteAudioTrack).receiver | 93 | is RemoteAudioTrack -> (publication.track!! as RemoteAudioTrack).receiver |
| 94 | is RemoteVideoTrack -> (publication.track!! as RemoteVideoTrack).receiver | 94 | is RemoteVideoTrack -> (publication.track!! as RemoteVideoTrack).receiver |
| @@ -111,7 +111,7 @@ constructor( | @@ -111,7 +111,7 @@ constructor( | ||
| 111 | } | 111 | } |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | - public fun removeSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) { | 114 | + fun removeSubscribedTrack(track: Track, publication: TrackPublication, participant: RemoteParticipant, room: Room) { |
| 115 | var trackId = publication.sid | 115 | var trackId = publication.sid |
| 116 | var participantId = participant.identity | 116 | var participantId = participant.identity |
| 117 | var frameCryptor = frameCryptors.get(trackId to participantId) | 117 | var frameCryptor = frameCryptors.get(trackId to participantId) |
| @@ -122,7 +122,7 @@ constructor( | @@ -122,7 +122,7 @@ constructor( | ||
| 122 | } | 122 | } |
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | - public fun addPublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) { | 125 | + fun addPublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) { |
| 126 | var rtpSender: RtpSender? = when (publication.track!!) { | 126 | var rtpSender: RtpSender? = when (publication.track!!) { |
| 127 | is LocalAudioTrack -> (publication.track!! as LocalAudioTrack)?.sender | 127 | is LocalAudioTrack -> (publication.track!! as LocalAudioTrack)?.sender |
| 128 | is LocalVideoTrack -> (publication.track!! as LocalVideoTrack)?.sender | 128 | is LocalVideoTrack -> (publication.track!! as LocalVideoTrack)?.sender |
| @@ -146,7 +146,7 @@ constructor( | @@ -146,7 +146,7 @@ constructor( | ||
| 146 | } | 146 | } |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | - public fun removePublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) { | 149 | + fun removePublishedTrack(track: Track, publication: TrackPublication, participant: LocalParticipant, room: Room) { |
| 150 | var trackId = publication.sid | 150 | var trackId = publication.sid |
| 151 | var participantId = participant.identity | 151 | var participantId = participant.identity |
| 152 | var frameCryptor = frameCryptors.get(trackId to participantId) | 152 | var frameCryptor = frameCryptors.get(trackId to participantId) |
| @@ -202,7 +202,7 @@ constructor( | @@ -202,7 +202,7 @@ constructor( | ||
| 202 | * Enable or disable E2EE | 202 | * Enable or disable E2EE |
| 203 | * @param enabled | 203 | * @param enabled |
| 204 | */ | 204 | */ |
| 205 | - public fun enableE2EE(enabled: Boolean) { | 205 | + fun enableE2EE(enabled: Boolean) { |
| 206 | this.enabled = enabled | 206 | this.enabled = enabled |
| 207 | for (item in frameCryptors.entries) { | 207 | for (item in frameCryptors.entries) { |
| 208 | var frameCryptor = item.value | 208 | var frameCryptor = item.value |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -25,9 +25,8 @@ internal const val defaultFailureTolerance = -1 | @@ -25,9 +25,8 @@ internal const val defaultFailureTolerance = -1 | ||
| 25 | internal const val defaultKeyRingSize = 16 | 25 | internal const val defaultKeyRingSize = 16 |
| 26 | internal const val defaultDiscardFrameWhenCryptorNotReady = false | 26 | internal const val defaultDiscardFrameWhenCryptorNotReady = false |
| 27 | 27 | ||
| 28 | -class E2EEOptions | ||
| 29 | -constructor( | ||
| 30 | - keyProvider: KeyProvider = BaseKeyProvider( | 28 | +class E2EEOptions( |
| 29 | + var keyProvider: KeyProvider = BaseKeyProvider( | ||
| 31 | defaultRatchetSalt, | 30 | defaultRatchetSalt, |
| 32 | defaultMagicBytes, | 31 | defaultMagicBytes, |
| 33 | defaultRatchetWindowSize, | 32 | defaultRatchetWindowSize, |
| @@ -38,11 +37,9 @@ constructor( | @@ -38,11 +37,9 @@ constructor( | ||
| 38 | ), | 37 | ), |
| 39 | encryptionType: Encryption.Type = Encryption.Type.GCM, | 38 | encryptionType: Encryption.Type = Encryption.Type.GCM, |
| 40 | ) { | 39 | ) { |
| 41 | - var keyProvider: KeyProvider | ||
| 42 | var encryptionType: Encryption.Type = Encryption.Type.NONE | 40 | var encryptionType: Encryption.Type = Encryption.Type.NONE |
| 43 | 41 | ||
| 44 | init { | 42 | init { |
| 45 | - this.keyProvider = keyProvider | ||
| 46 | this.encryptionType = encryptionType | 43 | this.encryptionType = encryptionType |
| 47 | } | 44 | } |
| 48 | } | 45 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -20,14 +20,13 @@ import io.livekit.android.util.LKLog | @@ -20,14 +20,13 @@ import io.livekit.android.util.LKLog | ||
| 20 | import livekit.org.webrtc.FrameCryptorFactory | 20 | import livekit.org.webrtc.FrameCryptorFactory |
| 21 | import livekit.org.webrtc.FrameCryptorKeyProvider | 21 | import livekit.org.webrtc.FrameCryptorKeyProvider |
| 22 | 22 | ||
| 23 | -class KeyInfo | ||
| 24 | -constructor(var participantId: String, var keyIndex: Int, var key: String) { | 23 | +class KeyInfo(var participantId: String, var keyIndex: Int, var key: String) { |
| 25 | override fun toString(): String { | 24 | override fun toString(): String { |
| 26 | return "KeyInfo(participantId='$participantId', keyIndex=$keyIndex)" | 25 | return "KeyInfo(participantId='$participantId', keyIndex=$keyIndex)" |
| 27 | } | 26 | } |
| 28 | } | 27 | } |
| 29 | 28 | ||
| 30 | -public interface KeyProvider { | 29 | +interface KeyProvider { |
| 31 | fun setSharedKey(key: String, keyIndex: Int? = 0): Boolean | 30 | fun setSharedKey(key: String, keyIndex: Int? = 0): Boolean |
| 32 | fun ratchetSharedKey(keyIndex: Int? = 0): ByteArray | 31 | fun ratchetSharedKey(keyIndex: Int? = 0): ByteArray |
| 33 | fun exportSharedKey(keyIndex: Int? = 0): ByteArray | 32 | fun exportSharedKey(keyIndex: Int? = 0): ByteArray |
| @@ -41,17 +40,30 @@ public interface KeyProvider { | @@ -41,17 +40,30 @@ public interface KeyProvider { | ||
| 41 | var enableSharedKey: Boolean | 40 | var enableSharedKey: Boolean |
| 42 | } | 41 | } |
| 43 | 42 | ||
| 44 | -class BaseKeyProvider | ||
| 45 | -constructor( | ||
| 46 | - private var ratchetSalt: String = defaultRatchetSalt, | ||
| 47 | - private var uncryptedMagicBytes: String = defaultMagicBytes, | ||
| 48 | - private var ratchetWindowSize: Int = defaultRatchetWindowSize, | 43 | +class BaseKeyProvider( |
| 44 | + ratchetSalt: String = defaultRatchetSalt, | ||
| 45 | + uncryptedMagicBytes: String = defaultMagicBytes, | ||
| 46 | + ratchetWindowSize: Int = defaultRatchetWindowSize, | ||
| 49 | override var enableSharedKey: Boolean = true, | 47 | override var enableSharedKey: Boolean = true, |
| 50 | - private var failureTolerance: Int = defaultFailureTolerance, | ||
| 51 | - private var keyRingSize: Int = defaultKeyRingSize, | ||
| 52 | - private var discardFrameWhenCryptorNotReady: Boolean = defaultDiscardFrameWhenCryptorNotReady, | 48 | + failureTolerance: Int = defaultFailureTolerance, |
| 49 | + keyRingSize: Int = defaultKeyRingSize, | ||
| 50 | + discardFrameWhenCryptorNotReady: Boolean = defaultDiscardFrameWhenCryptorNotReady, | ||
| 53 | ) : KeyProvider { | 51 | ) : KeyProvider { |
| 52 | + override val rtcKeyProvider: FrameCryptorKeyProvider | ||
| 53 | + | ||
| 54 | private var keys: MutableMap<String, MutableMap<Int, String>> = mutableMapOf() | 54 | private var keys: MutableMap<String, MutableMap<Int, String>> = mutableMapOf() |
| 55 | + | ||
| 56 | + init { | ||
| 57 | + this.rtcKeyProvider = FrameCryptorFactory.createFrameCryptorKeyProvider( | ||
| 58 | + enableSharedKey, | ||
| 59 | + ratchetSalt.toByteArray(), | ||
| 60 | + ratchetWindowSize, | ||
| 61 | + uncryptedMagicBytes.toByteArray(), | ||
| 62 | + failureTolerance, | ||
| 63 | + keyRingSize, | ||
| 64 | + discardFrameWhenCryptorNotReady, | ||
| 65 | + ) | ||
| 66 | + } | ||
| 55 | override fun setSharedKey(key: String, keyIndex: Int?): Boolean { | 67 | override fun setSharedKey(key: String, keyIndex: Int?): Boolean { |
| 56 | return rtcKeyProvider.setSharedKey(keyIndex ?: 0, key.toByteArray()) | 68 | return rtcKeyProvider.setSharedKey(keyIndex ?: 0, key.toByteArray()) |
| 57 | } | 69 | } |
| @@ -100,18 +112,4 @@ constructor( | @@ -100,18 +112,4 @@ constructor( | ||
| 100 | override fun setSifTrailer(trailer: ByteArray) { | 112 | override fun setSifTrailer(trailer: ByteArray) { |
| 101 | rtcKeyProvider.setSifTrailer(trailer) | 113 | rtcKeyProvider.setSifTrailer(trailer) |
| 102 | } | 114 | } |
| 103 | - | ||
| 104 | - override val rtcKeyProvider: FrameCryptorKeyProvider | ||
| 105 | - | ||
| 106 | - init { | ||
| 107 | - this.rtcKeyProvider = FrameCryptorFactory.createFrameCryptorKeyProvider( | ||
| 108 | - enableSharedKey, | ||
| 109 | - ratchetSalt.toByteArray(), | ||
| 110 | - ratchetWindowSize, | ||
| 111 | - uncryptedMagicBytes.toByteArray(), | ||
| 112 | - failureTolerance, | ||
| 113 | - keyRingSize, | ||
| 114 | - discardFrameWhenCryptorNotReady, | ||
| 115 | - ) | ||
| 116 | - } | ||
| 117 | } | 115 | } |
| @@ -22,8 +22,18 @@ import android.view.TextureView | @@ -22,8 +22,18 @@ import android.view.TextureView | ||
| 22 | import android.view.View | 22 | import android.view.View |
| 23 | import io.livekit.android.room.track.video.ViewVisibility | 23 | import io.livekit.android.room.track.video.ViewVisibility |
| 24 | import io.livekit.android.util.LKLog | 24 | import io.livekit.android.util.LKLog |
| 25 | -import livekit.org.webrtc.* | ||
| 26 | -import livekit.org.webrtc.RendererCommon.* | 25 | +import livekit.org.webrtc.EglBase |
| 26 | +import livekit.org.webrtc.EglRenderer | ||
| 27 | +import livekit.org.webrtc.GlRectDrawer | ||
| 28 | +import livekit.org.webrtc.Logging | ||
| 29 | +import livekit.org.webrtc.RendererCommon.GlDrawer | ||
| 30 | +import livekit.org.webrtc.RendererCommon.RendererEvents | ||
| 31 | +import livekit.org.webrtc.RendererCommon.ScalingType | ||
| 32 | +import livekit.org.webrtc.RendererCommon.VideoLayoutMeasure | ||
| 33 | +import livekit.org.webrtc.SurfaceEglRenderer | ||
| 34 | +import livekit.org.webrtc.ThreadUtils | ||
| 35 | +import livekit.org.webrtc.VideoFrame | ||
| 36 | +import livekit.org.webrtc.VideoSink | ||
| 27 | import java.util.concurrent.CountDownLatch | 37 | import java.util.concurrent.CountDownLatch |
| 28 | 38 | ||
| 29 | /** | 39 | /** |
| @@ -119,7 +129,7 @@ open class TextureViewRenderer : | @@ -119,7 +129,7 @@ open class TextureViewRenderer : | ||
| 119 | * It should be lightweight and must not call removeFrameListener. | 129 | * It should be lightweight and must not call removeFrameListener. |
| 120 | * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is | 130 | * @param scale The scale of the Bitmap passed to the callback, or 0 if no Bitmap is |
| 121 | * required. | 131 | * required. |
| 122 | - * @param drawer Custom drawer to use for this frame listener. | 132 | + * @param drawerParam Custom drawer to use for this frame listener. |
| 123 | */ | 133 | */ |
| 124 | fun addFrameListener( | 134 | fun addFrameListener( |
| 125 | listener: EglRenderer.FrameListener?, | 135 | listener: EglRenderer.FrameListener?, |
| @@ -578,7 +578,7 @@ internal constructor( | @@ -578,7 +578,7 @@ internal constructor( | ||
| 578 | } | 578 | } |
| 579 | 579 | ||
| 580 | LKLog.v { "ws reconnected, restarting ICE" } | 580 | LKLog.v { "ws reconnected, restarting ICE" } |
| 581 | - listener?.onSignalConnected(!isFullReconnect) | 581 | + listener?.onSignalConnected(true) |
| 582 | 582 | ||
| 583 | // trigger publisher reconnect | 583 | // trigger publisher reconnect |
| 584 | // only restart publisher if it's needed | 584 | // only restart publisher if it's needed |
| @@ -833,11 +833,9 @@ internal constructor( | @@ -833,11 +833,9 @@ internal constructor( | ||
| 833 | 833 | ||
| 834 | val rtcConfig = connectOptions.rtcConfig?.copy()?.apply { | 834 | val rtcConfig = connectOptions.rtcConfig?.copy()?.apply { |
| 835 | val mergedServers = iceServers.toMutableList() | 835 | val mergedServers = iceServers.toMutableList() |
| 836 | - if (connectOptions.iceServers != null) { | ||
| 837 | - connectOptions.iceServers.forEach { server -> | ||
| 838 | - if (!mergedServers.contains(server)) { | ||
| 839 | - mergedServers.add(server) | ||
| 840 | - } | 836 | + connectOptions.iceServers?.forEach { server -> |
| 837 | + if (!mergedServers.contains(server)) { | ||
| 838 | + mergedServers.add(server) | ||
| 841 | } | 839 | } |
| 842 | } | 840 | } |
| 843 | 841 | ||
| @@ -1088,9 +1086,7 @@ internal constructor( | @@ -1088,9 +1086,7 @@ internal constructor( | ||
| 1088 | abortPendingPublishTracks() | 1086 | abortPendingPublishTracks() |
| 1089 | 1087 | ||
| 1090 | if (leave.hasRegions()) { | 1088 | if (leave.hasRegions()) { |
| 1091 | - regionUrlProvider?.let { | ||
| 1092 | - it.setServerReportedRegions(RegionSettings.fromProto(leave.regions)) | ||
| 1093 | - } | 1089 | + regionUrlProvider?.setServerReportedRegions(RegionSettings.fromProto(leave.regions)) |
| 1094 | } | 1090 | } |
| 1095 | 1091 | ||
| 1096 | when { | 1092 | when { |
| @@ -1242,8 +1238,7 @@ internal constructor( | @@ -1242,8 +1238,7 @@ internal constructor( | ||
| 1242 | } | 1238 | } |
| 1243 | } | 1239 | } |
| 1244 | 1240 | ||
| 1245 | - val dataChannelInfos = LivekitModels.DataPacket.Kind.values() | ||
| 1246 | - .toList() | 1241 | + val dataChannelInfos = LivekitModels.DataPacket.Kind.entries |
| 1247 | .filterNot { it == LivekitModels.DataPacket.Kind.UNRECOGNIZED } | 1242 | .filterNot { it == LivekitModels.DataPacket.Kind.UNRECOGNIZED } |
| 1248 | .mapNotNull { kind -> dataChannelForKind(kind) } | 1243 | .mapNotNull { kind -> dataChannelForKind(kind) } |
| 1249 | .map { dataChannel -> | 1244 | .map { dataChannel -> |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -87,9 +87,13 @@ constructor( | @@ -87,9 +87,13 @@ constructor( | ||
| 87 | if (!response.isSuccessful) { | 87 | if (!response.isSuccessful) { |
| 88 | throw RoomException.ConnectException("Could not fetch region settings: ${response.code} ${response.message}") | 88 | throw RoomException.ConnectException("Could not fetch region settings: ${response.code} ${response.message}") |
| 89 | } | 89 | } |
| 90 | - return@use response.body?.string() ?: return null | 90 | + return@use response.body?.string() |
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | + if (bodyString == null) { | ||
| 94 | + throw RoomException.ConnectException("Could not fetch region settings: empty response body!") | ||
| 95 | + } | ||
| 96 | + | ||
| 93 | return json.decodeFromString<RegionSettings>(bodyString).also { | 97 | return json.decodeFromString<RegionSettings>(bodyString).also { |
| 94 | regionSettings = it | 98 | regionSettings = it |
| 95 | lastUpdateAt = SystemClock.elapsedRealtime() | 99 | lastUpdateAt = SystemClock.elapsedRealtime() |
| @@ -319,7 +319,7 @@ internal constructor( | @@ -319,7 +319,7 @@ internal constructor( | ||
| 319 | * @param screenCaptureParams When enabling the screenshare, this must be provided with | 319 | * @param screenCaptureParams When enabling the screenshare, this must be provided with |
| 320 | * [ScreenCaptureParams.mediaProjectionPermissionResultData] containing resultData returned from launching | 320 | * [ScreenCaptureParams.mediaProjectionPermissionResultData] containing resultData returned from launching |
| 321 | * [MediaProjectionManager.createScreenCaptureIntent()](https://developer.android.com/reference/android/media/projection/MediaProjectionManager#createScreenCaptureIntent()). | 321 | * [MediaProjectionManager.createScreenCaptureIntent()](https://developer.android.com/reference/android/media/projection/MediaProjectionManager#createScreenCaptureIntent()). |
| 322 | - * @throws IllegalArgumentException if attempting to enable screenshare without [mediaProjectionPermissionResultData] | 322 | + * @throws IllegalArgumentException if attempting to enable screenshare without [screenCaptureParams] |
| 323 | * @see Room.screenShareTrackCaptureDefaults | 323 | * @see Room.screenShareTrackCaptureDefaults |
| 324 | * @see Room.screenShareTrackPublishDefaults | 324 | * @see Room.screenShareTrackPublishDefaults |
| 325 | * @see ScreenAudioCapturer | 325 | * @see ScreenAudioCapturer |
| @@ -429,9 +429,7 @@ open class Participant( | @@ -429,9 +429,7 @@ open class Participant( | ||
| 429 | 429 | ||
| 430 | other as Participant | 430 | other as Participant |
| 431 | 431 | ||
| 432 | - if (sid != other.sid) return false | ||
| 433 | - | ||
| 434 | - return true | 432 | + return sid == other.sid |
| 435 | } | 433 | } |
| 436 | 434 | ||
| 437 | override fun hashCode(): Int { | 435 | override fun hashCode(): Int { |
| @@ -38,7 +38,6 @@ import io.livekit.android.room.util.EncodingUtils | @@ -38,7 +38,6 @@ import io.livekit.android.room.util.EncodingUtils | ||
| 38 | import io.livekit.android.util.FlowObservable | 38 | import io.livekit.android.util.FlowObservable |
| 39 | import io.livekit.android.util.LKLog | 39 | import io.livekit.android.util.LKLog |
| 40 | import io.livekit.android.util.flowDelegate | 40 | import io.livekit.android.util.flowDelegate |
| 41 | -import livekit.LivekitModels | ||
| 42 | import livekit.LivekitRtc | 41 | import livekit.LivekitRtc |
| 43 | import livekit.LivekitRtc.SubscribedCodec | 42 | import livekit.LivekitRtc.SubscribedCodec |
| 44 | import livekit.org.webrtc.CameraVideoCapturer | 43 | import livekit.org.webrtc.CameraVideoCapturer |
| @@ -357,7 +356,7 @@ constructor( | @@ -357,7 +356,7 @@ constructor( | ||
| 357 | val rid = EncodingUtils.ridForVideoQuality(quality.quality) ?: continue | 356 | val rid = EncodingUtils.ridForVideoQuality(quality.quality) ?: continue |
| 358 | val encoding = encodings.firstOrNull { it.rid == rid } | 357 | val encoding = encodings.firstOrNull { it.rid == rid } |
| 359 | // use low quality layer settings for non-simulcasted streams | 358 | // use low quality layer settings for non-simulcasted streams |
| 360 | - ?: encodings.takeIf { it.size == 1 && quality.quality == LivekitModels.VideoQuality.LOW }?.first() | 359 | + ?: encodings.takeIf { it.size == 1 && quality.quality == ProtoVideoQuality.LOW }?.first() |
| 361 | ?: continue | 360 | ?: continue |
| 362 | if (encoding.active != quality.enabled) { | 361 | if (encoding.active != quality.enabled) { |
| 363 | hasChanged = true | 362 | hasChanged = true |
| @@ -229,5 +229,5 @@ sealed class TrackException(message: String? = null, cause: Throwable? = null) : | @@ -229,5 +229,5 @@ sealed class TrackException(message: String? = null, cause: Throwable? = null) : | ||
| 229 | TrackException(message, cause) | 229 | TrackException(message, cause) |
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | -public const val KIND_AUDIO = "audio" | ||
| 233 | -public const val KIND_VIDEO = "video" | 232 | +const val KIND_AUDIO = "audio" |
| 233 | +const val KIND_VIDEO = "video" |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -32,7 +32,7 @@ import kotlin.coroutines.resume | @@ -32,7 +32,7 @@ import kotlin.coroutines.resume | ||
| 32 | * Handles connecting to a [ScreenCaptureService]. | 32 | * Handles connecting to a [ScreenCaptureService]. |
| 33 | */ | 33 | */ |
| 34 | internal class ScreenCaptureConnection(private val context: Context) { | 34 | internal class ScreenCaptureConnection(private val context: Context) { |
| 35 | - public var isBound = false | 35 | + var isBound = false |
| 36 | private set | 36 | private set |
| 37 | private var service: ScreenCaptureService? = null | 37 | private var service: ScreenCaptureService? = null |
| 38 | private val queuedConnects = mutableSetOf<Continuation<Unit>>() | 38 | private val queuedConnects = mutableSetOf<Continuation<Unit>>() |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -20,7 +20,6 @@ import android.app.Notification | @@ -20,7 +20,6 @@ import android.app.Notification | ||
| 20 | import android.app.NotificationChannel | 20 | import android.app.NotificationChannel |
| 21 | import android.app.NotificationManager | 21 | import android.app.NotificationManager |
| 22 | import android.app.Service | 22 | import android.app.Service |
| 23 | -import android.content.Context | ||
| 24 | import android.content.Intent | 23 | import android.content.Intent |
| 25 | import android.os.Binder | 24 | import android.os.Binder |
| 26 | import android.os.Build | 25 | import android.os.Build |
| @@ -69,7 +68,7 @@ open class ScreenCaptureService : Service() { | @@ -69,7 +68,7 @@ open class ScreenCaptureService : Service() { | ||
| 69 | "Screen Capture", | 68 | "Screen Capture", |
| 70 | NotificationManager.IMPORTANCE_LOW, | 69 | NotificationManager.IMPORTANCE_LOW, |
| 71 | ) | 70 | ) |
| 72 | - val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | 71 | + val service = getSystemService(NOTIFICATION_SERVICE) as NotificationManager |
| 73 | service.createNotificationChannel(channel) | 72 | service.createNotificationChannel(channel) |
| 74 | } | 73 | } |
| 75 | 74 |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -23,7 +23,6 @@ import android.view.View | @@ -23,7 +23,6 @@ import android.view.View | ||
| 23 | import android.view.ViewTreeObserver | 23 | import android.view.ViewTreeObserver |
| 24 | import androidx.annotation.CallSuper | 24 | import androidx.annotation.CallSuper |
| 25 | import io.livekit.android.room.track.Track | 25 | import io.livekit.android.room.track.Track |
| 26 | -import io.livekit.android.room.track.video.ViewVisibility.Notifier | ||
| 27 | import java.util.Observable | 26 | import java.util.Observable |
| 28 | 27 | ||
| 29 | /** | 28 | /** |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -26,8 +26,6 @@ import kotlin.coroutines.resume | @@ -26,8 +26,6 @@ import kotlin.coroutines.resume | ||
| 26 | 26 | ||
| 27 | /** | 27 | /** |
| 28 | * Returns an RTCStatsReport with all the relevant information pertaining to a track. | 28 | * Returns an RTCStatsReport with all the relevant information pertaining to a track. |
| 29 | - * | ||
| 30 | - * @param trackIdentifier track, sender, or receiver id | ||
| 31 | */ | 29 | */ |
| 32 | fun RTCStatsReport.getFilteredStats(track: MediaStreamTrack): RTCStatsReport { | 30 | fun RTCStatsReport.getFilteredStats(track: MediaStreamTrack): RTCStatsReport { |
| 33 | return getFilteredStats(track.id()) | 31 | return getFilteredStats(track.id()) |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -256,7 +256,7 @@ class MockPeerConnection( | @@ -256,7 +256,7 @@ class MockPeerConnection( | ||
| 256 | if (currentOrdinal < newOrdinal) { | 256 | if (currentOrdinal < newOrdinal) { |
| 257 | // Ensure that we move through each state. | 257 | // Ensure that we move through each state. |
| 258 | for (ordinal in ((currentOrdinal + 1)..newOrdinal)) { | 258 | for (ordinal in ((currentOrdinal + 1)..newOrdinal)) { |
| 259 | - iceConnectionState = IceConnectionState.values()[ordinal] | 259 | + iceConnectionState = IceConnectionState.entries[ordinal] |
| 260 | } | 260 | } |
| 261 | } else { | 261 | } else { |
| 262 | iceConnectionState = newState | 262 | iceConnectionState = newState |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -48,7 +48,7 @@ class MockWebSocket( | @@ -48,7 +48,7 @@ class MockWebSocket( | ||
| 48 | isClosed = true | 48 | isClosed = true |
| 49 | listener.onClosing(this, code, reason ?: "") | 49 | listener.onClosing(this, code, reason ?: "") |
| 50 | listener.onClosed(this, code, reason ?: "") | 50 | listener.onClosed(this, code, reason ?: "") |
| 51 | - return willClose | 51 | + return true |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | override fun queueSize(): Long = 0 | 54 | override fun queueSize(): Long = 0 |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -20,7 +20,7 @@ import com.google.protobuf.ByteString | @@ -20,7 +20,7 @@ import com.google.protobuf.ByteString | ||
| 20 | import livekit.LivekitRtc | 20 | import livekit.LivekitRtc |
| 21 | import okio.ByteString.Companion.toByteString | 21 | import okio.ByteString.Companion.toByteString |
| 22 | 22 | ||
| 23 | -fun com.google.protobuf.ByteString.toOkioByteString() = toByteArray().toByteString() | 23 | +fun ByteString.toOkioByteString() = toByteArray().toByteString() |
| 24 | 24 | ||
| 25 | fun okio.ByteString.toPBByteString() = ByteString.copyFrom(toByteArray()) | 25 | fun okio.ByteString.toPBByteString() = ByteString.copyFrom(toByteArray()) |
| 26 | 26 |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -97,8 +97,7 @@ class MixerAudioBufferCallbackTest { | @@ -97,8 +97,7 @@ class MixerAudioBufferCallbackTest { | ||
| 97 | AudioFormat.ENCODING_PCM_FLOAT -> { | 97 | AudioFormat.ENCODING_PCM_FLOAT -> { |
| 98 | byteBuffer.asFloatBuffer().put(0, INCREMENT.toFloat()) | 98 | byteBuffer.asFloatBuffer().put(0, INCREMENT.toFloat()) |
| 99 | } | 99 | } |
| 100 | - | ||
| 101 | - AudioFormat.ENCODING_INVALID -> throw IllegalArgumentException("Bad audio format $audioFormat") | 100 | + else -> throw IllegalArgumentException("Bad audio format $audioFormat") |
| 102 | } | 101 | } |
| 103 | 102 | ||
| 104 | return BufferResponse(byteBuffer) | 103 | return BufferResponse(byteBuffer) |
| @@ -111,7 +110,6 @@ private fun getBytesPerSample(audioFormat: Int): Int { | @@ -111,7 +110,6 @@ private fun getBytesPerSample(audioFormat: Int): Int { | ||
| 111 | AudioFormat.ENCODING_PCM_8BIT -> 1 | 110 | AudioFormat.ENCODING_PCM_8BIT -> 1 |
| 112 | AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2 | 111 | AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_IEC61937, AudioFormat.ENCODING_DEFAULT -> 2 |
| 113 | AudioFormat.ENCODING_PCM_FLOAT -> 4 | 112 | AudioFormat.ENCODING_PCM_FLOAT -> 4 |
| 114 | - AudioFormat.ENCODING_INVALID -> throw IllegalArgumentException("Bad audio format $audioFormat") | ||
| 115 | else -> throw IllegalArgumentException("Bad audio format $audioFormat") | 113 | else -> throw IllegalArgumentException("Bad audio format $audioFormat") |
| 116 | } | 114 | } |
| 117 | } | 115 | } |
| @@ -58,10 +58,10 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -58,10 +58,10 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 58 | val collector = FlowCollector(room::state.flow, coroutineRule.scope) | 58 | val collector = FlowCollector(room::state.flow, coroutineRule.scope) |
| 59 | connect() | 59 | connect() |
| 60 | val events = collector.stopCollecting() | 60 | val events = collector.stopCollecting() |
| 61 | - Assert.assertEquals(3, events.size) | ||
| 62 | - Assert.assertEquals(Room.State.DISCONNECTED, events[0]) | ||
| 63 | - Assert.assertEquals(Room.State.CONNECTING, events[1]) | ||
| 64 | - Assert.assertEquals(Room.State.CONNECTED, events[2]) | 61 | + assertEquals(3, events.size) |
| 62 | + assertEquals(Room.State.DISCONNECTED, events[0]) | ||
| 63 | + assertEquals(Room.State.CONNECTING, events[1]) | ||
| 64 | + assertEquals(Room.State.CONNECTED, events[2]) | ||
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | @Test | 67 | @Test |
| @@ -155,9 +155,9 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -155,9 +155,9 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 155 | ) | 155 | ) |
| 156 | val events = eventCollector.stopCollecting() | 156 | val events = eventCollector.stopCollecting() |
| 157 | 157 | ||
| 158 | - Assert.assertEquals(ConnectionQuality.EXCELLENT, room.localParticipant.connectionQuality) | ||
| 159 | - Assert.assertEquals(1, events.size) | ||
| 160 | - Assert.assertEquals(true, events[0] is RoomEvent.ConnectionQualityChanged) | 158 | + assertEquals(ConnectionQuality.EXCELLENT, room.localParticipant.connectionQuality) |
| 159 | + assertEquals(1, events.size) | ||
| 160 | + assertEquals(true, events[0] is RoomEvent.ConnectionQualityChanged) | ||
| 161 | } | 161 | } |
| 162 | 162 | ||
| 163 | @Test | 163 | @Test |
| @@ -252,8 +252,8 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -252,8 +252,8 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 252 | ) | 252 | ) |
| 253 | val events = eventCollector.stopCollecting() | 253 | val events = eventCollector.stopCollecting() |
| 254 | 254 | ||
| 255 | - Assert.assertEquals(1, events.size) | ||
| 256 | - Assert.assertEquals(true, events[0] is RoomEvent.ActiveSpeakersChanged) | 255 | + assertEquals(1, events.size) |
| 256 | + assertEquals(true, events[0] is RoomEvent.ActiveSpeakersChanged) | ||
| 257 | } | 257 | } |
| 258 | 258 | ||
| 259 | @Test | 259 | @Test |
| @@ -286,11 +286,11 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -286,11 +286,11 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 286 | ) | 286 | ) |
| 287 | val events = eventCollector.stopCollecting() | 287 | val events = eventCollector.stopCollecting() |
| 288 | 288 | ||
| 289 | - Assert.assertEquals(1, events.size) | ||
| 290 | - Assert.assertEquals(true, events[0] is RoomEvent.TrackStreamStateChanged) | 289 | + assertEquals(1, events.size) |
| 290 | + assertEquals(true, events[0] is RoomEvent.TrackStreamStateChanged) | ||
| 291 | 291 | ||
| 292 | val event = events[0] as RoomEvent.TrackStreamStateChanged | 292 | val event = events[0] as RoomEvent.TrackStreamStateChanged |
| 293 | - Assert.assertEquals(Track.StreamState.ACTIVE, event.streamState) | 293 | + assertEquals(Track.StreamState.ACTIVE, event.streamState) |
| 294 | } | 294 | } |
| 295 | 295 | ||
| 296 | @Test | 296 | @Test |
| @@ -320,13 +320,13 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -320,13 +320,13 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 320 | ) | 320 | ) |
| 321 | val events = eventCollector.stopCollecting() | 321 | val events = eventCollector.stopCollecting() |
| 322 | 322 | ||
| 323 | - Assert.assertEquals(1, events.size) | ||
| 324 | - Assert.assertEquals(true, events[0] is RoomEvent.TrackSubscriptionPermissionChanged) | 323 | + assertEquals(1, events.size) |
| 324 | + assertEquals(true, events[0] is RoomEvent.TrackSubscriptionPermissionChanged) | ||
| 325 | 325 | ||
| 326 | val event = events[0] as RoomEvent.TrackSubscriptionPermissionChanged | 326 | val event = events[0] as RoomEvent.TrackSubscriptionPermissionChanged |
| 327 | - Assert.assertEquals(TestData.REMOTE_PARTICIPANT.sid, event.participant.sid.value) | ||
| 328 | - Assert.assertEquals(TestData.REMOTE_AUDIO_TRACK.sid, event.trackPublication.sid) | ||
| 329 | - Assert.assertEquals(false, event.subscriptionAllowed) | 327 | + assertEquals(TestData.REMOTE_PARTICIPANT.sid, event.participant.sid.value) |
| 328 | + assertEquals(TestData.REMOTE_AUDIO_TRACK.sid, event.trackPublication.sid) | ||
| 329 | + assertEquals(false, event.subscriptionAllowed) | ||
| 330 | } | 330 | } |
| 331 | 331 | ||
| 332 | @Test | 332 | @Test |
| @@ -495,6 +495,6 @@ class RoomMockE2ETest : MockE2ETest() { | @@ -495,6 +495,6 @@ class RoomMockE2ETest : MockE2ETest() { | ||
| 495 | connect() | 495 | connect() |
| 496 | room.disconnect() | 496 | room.disconnect() |
| 497 | connect() | 497 | connect() |
| 498 | - Assert.assertEquals(room.state, Room.State.CONNECTED) | 498 | + assertEquals(room.state, Room.State.CONNECTED) |
| 499 | } | 499 | } |
| 500 | } | 500 | } |
| @@ -25,7 +25,6 @@ import io.livekit.android.test.util.toPBByteString | @@ -25,7 +25,6 @@ import io.livekit.android.test.util.toPBByteString | ||
| 25 | import kotlinx.coroutines.ExperimentalCoroutinesApi | 25 | import kotlinx.coroutines.ExperimentalCoroutinesApi |
| 26 | import livekit.LivekitRtc | 26 | import livekit.LivekitRtc |
| 27 | import livekit.org.webrtc.PeerConnection | 27 | import livekit.org.webrtc.PeerConnection |
| 28 | -import org.junit.Assert | ||
| 29 | import org.junit.Assert.assertEquals | 28 | import org.junit.Assert.assertEquals |
| 30 | import org.junit.Assert.assertTrue | 29 | import org.junit.Assert.assertTrue |
| 31 | import org.junit.Test | 30 | import org.junit.Test |
| @@ -76,7 +75,7 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { | @@ -76,7 +75,7 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { | ||
| 76 | return@any sentRequest.hasSyncState() | 75 | return@any sentRequest.hasSyncState() |
| 77 | } | 76 | } |
| 78 | 77 | ||
| 79 | - Assert.assertTrue(sentSyncState) | 78 | + assertTrue(sentSyncState) |
| 80 | } | 79 | } |
| 81 | 80 | ||
| 82 | @Test | 81 | @Test |
| @@ -146,6 +145,6 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { | @@ -146,6 +145,6 @@ class RoomReconnectionMockE2ETest : MockE2ETest() { | ||
| 146 | } | 145 | } |
| 147 | 146 | ||
| 148 | println(sentRequests) | 147 | println(sentRequests) |
| 149 | - Assert.assertTrue(sentAddTrack) | 148 | + assertTrue(sentAddTrack) |
| 150 | } | 149 | } |
| 151 | } | 150 | } |
| @@ -14,11 +14,11 @@ | @@ -14,11 +14,11 @@ | ||
| 14 | * limitations under the License. | 14 | * limitations under the License. |
| 15 | */ | 15 | */ |
| 16 | 16 | ||
| 17 | -package io.livekit.android.room.participant | 17 | +package io.livekit.android.room.rpc |
| 18 | 18 | ||
| 19 | import com.google.protobuf.ByteString | 19 | import com.google.protobuf.ByteString |
| 20 | import io.livekit.android.room.RTCEngine | 20 | import io.livekit.android.room.RTCEngine |
| 21 | -import io.livekit.android.room.rpc.RpcManager | 21 | +import io.livekit.android.room.participant.Participant |
| 22 | import io.livekit.android.rpc.RpcError | 22 | import io.livekit.android.rpc.RpcError |
| 23 | import io.livekit.android.test.MockE2ETest | 23 | import io.livekit.android.test.MockE2ETest |
| 24 | import io.livekit.android.test.mock.MockDataChannel | 24 | import io.livekit.android.test.mock.MockDataChannel |
| @@ -140,7 +140,7 @@ class VirtualBackgroundTransformer( | @@ -140,7 +140,7 @@ class VirtualBackgroundTransformer( | ||
| 140 | if (backgroundImage != null) { | 140 | if (backgroundImage != null) { |
| 141 | val bgTextureFrameBuffer = bgTextureFrameBuffers.first | 141 | val bgTextureFrameBuffer = bgTextureFrameBuffers.first |
| 142 | 142 | ||
| 143 | - if (backgroundImageNeedsUploading || true) { | 143 | + if (backgroundImageNeedsUploading) { |
| 144 | val byteBuffer = ByteBuffer.allocateDirect(backgroundImage.byteCount) | 144 | val byteBuffer = ByteBuffer.allocateDirect(backgroundImage.byteCount) |
| 145 | backgroundImage.copyPixelsToBuffer(byteBuffer) | 145 | backgroundImage.copyPixelsToBuffer(byteBuffer) |
| 146 | byteBuffer.rewind() | 146 | byteBuffer.rewind() |
| @@ -42,6 +42,7 @@ import livekit.org.webrtc.SurfaceTextureHelper | @@ -42,6 +42,7 @@ import livekit.org.webrtc.SurfaceTextureHelper | ||
| 42 | import livekit.org.webrtc.VideoFrame | 42 | import livekit.org.webrtc.VideoFrame |
| 43 | import livekit.org.webrtc.VideoSink | 43 | import livekit.org.webrtc.VideoSink |
| 44 | import java.util.concurrent.Semaphore | 44 | import java.util.concurrent.Semaphore |
| 45 | +import kotlin.math.roundToInt | ||
| 45 | 46 | ||
| 46 | /** | 47 | /** |
| 47 | * A virtual background video processor for the local camera video stream. | 48 | * A virtual background video processor for the local camera video stream. |
| @@ -90,6 +91,9 @@ class VirtualBackgroundVideoProcessor( | @@ -90,6 +91,9 @@ class VirtualBackgroundVideoProcessor( | ||
| 90 | */ | 91 | */ |
| 91 | var enabled: Boolean = true | 92 | var enabled: Boolean = true |
| 92 | 93 | ||
| 94 | + /** | ||
| 95 | + * A virtual background image to use. | ||
| 96 | + */ | ||
| 93 | var backgroundImage: Bitmap? = null | 97 | var backgroundImage: Bitmap? = null |
| 94 | set(value) { | 98 | set(value) { |
| 95 | field = value | 99 | field = value |
| @@ -191,39 +195,42 @@ class VirtualBackgroundVideoProcessor( | @@ -191,39 +195,42 @@ class VirtualBackgroundVideoProcessor( | ||
| 191 | frame.retain() | 195 | frame.retain() |
| 192 | surfaceTextureHelper.handler.post { | 196 | surfaceTextureHelper.handler.post { |
| 193 | val backgroundImage = this.backgroundImage | 197 | val backgroundImage = this.backgroundImage |
| 194 | - if (backgroundImageNeedsUpdating && backgroundImage != null) { | ||
| 195 | - val imageAspect = backgroundImage.width / backgroundImage.height.toFloat() | ||
| 196 | - val targetAspect = frame.rotatedWidth / frame.rotatedHeight.toFloat() | ||
| 197 | - var sx = 0 | ||
| 198 | - var sy = 0 | ||
| 199 | - var sWidth = backgroundImage.width | ||
| 200 | - var sHeight = backgroundImage.height | ||
| 201 | - | ||
| 202 | - if (imageAspect > targetAspect) { | ||
| 203 | - sWidth = Math.round(backgroundImage.height * targetAspect) | ||
| 204 | - sx = Math.round((backgroundImage.width - sWidth) / 2f) | 198 | + if (backgroundImageNeedsUpdating) { |
| 199 | + if (backgroundImage == null) { | ||
| 200 | + backgroundTransformer.backgroundImage = null | ||
| 201 | + backgroundImageNeedsUpdating = false | ||
| 205 | } else { | 202 | } else { |
| 206 | - sHeight = Math.round(backgroundImage.width / targetAspect) | ||
| 207 | - sy = Math.round((backgroundImage.height - sHeight) / 2f) | 203 | + val imageAspect = backgroundImage.width / backgroundImage.height.toFloat() |
| 204 | + val targetAspect = frame.rotatedWidth / frame.rotatedHeight.toFloat() | ||
| 205 | + var sx = 0 | ||
| 206 | + var sy = 0 | ||
| 207 | + var sWidth = backgroundImage.width | ||
| 208 | + var sHeight = backgroundImage.height | ||
| 209 | + | ||
| 210 | + if (imageAspect > targetAspect) { | ||
| 211 | + sWidth = (backgroundImage.height * targetAspect).roundToInt() | ||
| 212 | + sx = ((backgroundImage.width - sWidth) / 2f).roundToInt() | ||
| 213 | + } else { | ||
| 214 | + sHeight = (backgroundImage.width / targetAspect).roundToInt() | ||
| 215 | + sy = ((backgroundImage.height - sHeight) / 2f).roundToInt() | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + val matrix = Matrix() | ||
| 219 | + | ||
| 220 | + matrix.postRotate(-frame.rotation.toFloat()) | ||
| 221 | + | ||
| 222 | + val resizedImage = Bitmap.createBitmap( | ||
| 223 | + backgroundImage, | ||
| 224 | + sx, | ||
| 225 | + sy, | ||
| 226 | + sWidth, | ||
| 227 | + sHeight, | ||
| 228 | + matrix, | ||
| 229 | + true, | ||
| 230 | + ) | ||
| 231 | + backgroundTransformer.backgroundImage = resizedImage | ||
| 232 | + backgroundImageNeedsUpdating = false | ||
| 208 | } | 233 | } |
| 209 | - | ||
| 210 | - val diffAspect = targetAspect / imageAspect | ||
| 211 | - | ||
| 212 | - val matrix = Matrix() | ||
| 213 | - | ||
| 214 | - matrix.postRotate(-frame.rotation.toFloat()) | ||
| 215 | - | ||
| 216 | - val resizedImage = Bitmap.createBitmap( | ||
| 217 | - backgroundImage, | ||
| 218 | - sx, | ||
| 219 | - sy, | ||
| 220 | - sWidth, | ||
| 221 | - sHeight, | ||
| 222 | - matrix, | ||
| 223 | - true, | ||
| 224 | - ) | ||
| 225 | - backgroundTransformer.backgroundImage = resizedImage | ||
| 226 | - backgroundImageNeedsUpdating = false | ||
| 227 | } | 234 | } |
| 228 | 235 | ||
| 229 | lastMask?.let { | 236 | lastMask?.let { |
| @@ -61,7 +61,7 @@ public class LKGlTextureFrameBuffer { | @@ -61,7 +61,7 @@ public class LKGlTextureFrameBuffer { | ||
| 61 | textureId = GlUtil.generateTexture(GLES30.GL_TEXTURE_2D); | 61 | textureId = GlUtil.generateTexture(GLES30.GL_TEXTURE_2D); |
| 62 | } | 62 | } |
| 63 | if (frameBufferId == 0) { | 63 | if (frameBufferId == 0) { |
| 64 | - final int frameBuffers[] = new int[1]; | 64 | + final int[] frameBuffers = new int[1]; |
| 65 | GLES30.glGenFramebuffers(1, frameBuffers, 0); | 65 | GLES30.glGenFramebuffers(1, frameBuffers, 0); |
| 66 | frameBufferId = frameBuffers[0]; | 66 | frameBufferId = frameBuffers[0]; |
| 67 | } | 67 | } |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2024 LiveKit, Inc. | 2 | + * Copyright 2024-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -19,7 +19,6 @@ package io.livekit.android.sample.service | @@ -19,7 +19,6 @@ package io.livekit.android.sample.service | ||
| 19 | import android.app.NotificationChannel | 19 | import android.app.NotificationChannel |
| 20 | import android.app.NotificationManager | 20 | import android.app.NotificationManager |
| 21 | import android.app.Service | 21 | import android.app.Service |
| 22 | -import android.content.Context | ||
| 23 | import android.content.Intent | 22 | import android.content.Intent |
| 24 | import android.os.Build | 23 | import android.os.Build |
| 25 | import android.os.IBinder | 24 | import android.os.IBinder |
| @@ -61,7 +60,7 @@ open class ForegroundService : Service() { | @@ -61,7 +60,7 @@ open class ForegroundService : Service() { | ||
| 61 | "Foreground", | 60 | "Foreground", |
| 62 | NotificationManager.IMPORTANCE_LOW | 61 | NotificationManager.IMPORTANCE_LOW |
| 63 | ) | 62 | ) |
| 64 | - val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | 63 | + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager |
| 65 | notificationManager.createNotificationChannel(channel) | 64 | notificationManager.createNotificationChannel(channel) |
| 66 | } | 65 | } |
| 67 | 66 |
| @@ -16,7 +16,6 @@ | @@ -16,7 +16,6 @@ | ||
| 16 | 16 | ||
| 17 | package io.livekit.android.composesample | 17 | package io.livekit.android.composesample |
| 18 | 18 | ||
| 19 | -import android.app.Activity | ||
| 20 | import android.media.projection.MediaProjectionManager | 19 | import android.media.projection.MediaProjectionManager |
| 21 | import android.os.Bundle | 20 | import android.os.Bundle |
| 22 | import android.os.Parcelable | 21 | import android.os.Parcelable |
| @@ -100,7 +99,7 @@ class CallActivity : AppCompatActivity() { | @@ -100,7 +99,7 @@ class CallActivity : AppCompatActivity() { | ||
| 100 | ) { result -> | 99 | ) { result -> |
| 101 | val resultCode = result.resultCode | 100 | val resultCode = result.resultCode |
| 102 | val data = result.data | 101 | val data = result.data |
| 103 | - if (resultCode != Activity.RESULT_OK || data == null) { | 102 | + if (resultCode != RESULT_OK || data == null) { |
| 104 | return@registerForActivityResult | 103 | return@registerForActivityResult |
| 105 | } | 104 | } |
| 106 | viewModel.startScreenCapture(data) | 105 | viewModel.startScreenCapture(data) |
| @@ -16,7 +16,6 @@ | @@ -16,7 +16,6 @@ | ||
| 16 | 16 | ||
| 17 | package io.livekit.android.sample | 17 | package io.livekit.android.sample |
| 18 | 18 | ||
| 19 | -import android.app.Activity | ||
| 20 | import android.media.projection.MediaProjectionManager | 19 | import android.media.projection.MediaProjectionManager |
| 21 | import android.os.Bundle | 20 | import android.os.Bundle |
| 22 | import android.os.Parcelable | 21 | import android.os.Parcelable |
| @@ -63,7 +62,7 @@ class CallActivity : AppCompatActivity() { | @@ -63,7 +62,7 @@ class CallActivity : AppCompatActivity() { | ||
| 63 | ) { result -> | 62 | ) { result -> |
| 64 | val resultCode = result.resultCode | 63 | val resultCode = result.resultCode |
| 65 | val data = result.data | 64 | val data = result.data |
| 66 | - if (resultCode != Activity.RESULT_OK || data == null) { | 65 | + if (resultCode != RESULT_OK || data == null) { |
| 67 | return@registerForActivityResult | 66 | return@registerForActivityResult |
| 68 | } | 67 | } |
| 69 | viewModel.startScreenCapture(data) | 68 | viewModel.startScreenCapture(data) |
| 1 | +/* | ||
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 1 | package io.livekit.android.videoencodedecode | 17 | package io.livekit.android.videoencodedecode |
| 2 | 18 | ||
| 3 | import android.os.Bundle | 19 | import android.os.Bundle |
| @@ -18,7 +34,7 @@ import androidx.constraintlayout.compose.Dimension | @@ -18,7 +34,7 @@ import androidx.constraintlayout.compose.Dimension | ||
| 18 | import androidx.lifecycle.ViewModel | 34 | import androidx.lifecycle.ViewModel |
| 19 | import androidx.lifecycle.ViewModelProvider | 35 | import androidx.lifecycle.ViewModelProvider |
| 20 | import androidx.lifecycle.viewmodel.CreationExtras | 36 | import androidx.lifecycle.viewmodel.CreationExtras |
| 21 | -import io.livekit.android.composesample.ui.theme.AppTheme | 37 | +import io.livekit.android.videoencodedecode.ui.theme.AppTheme |
| 22 | import kotlinx.parcelize.Parcelize | 38 | import kotlinx.parcelize.Parcelize |
| 23 | 39 | ||
| 24 | class CallActivity : AppCompatActivity() { | 40 | class CallActivity : AppCompatActivity() { |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -36,8 +36,8 @@ import androidx.compose.ui.tooling.preview.Preview | @@ -36,8 +36,8 @@ import androidx.compose.ui.tooling.preview.Preview | ||
| 36 | import androidx.compose.ui.unit.dp | 36 | import androidx.compose.ui.unit.dp |
| 37 | import androidx.core.content.ContextCompat | 37 | import androidx.core.content.ContextCompat |
| 38 | import com.google.accompanist.pager.ExperimentalPagerApi | 38 | import com.google.accompanist.pager.ExperimentalPagerApi |
| 39 | -import io.livekit.android.composesample.ui.theme.AppTheme | ||
| 40 | import io.livekit.android.sample.common.R | 39 | import io.livekit.android.sample.common.R |
| 40 | +import io.livekit.android.videoencodedecode.ui.theme.AppTheme | ||
| 41 | 41 | ||
| 42 | @ExperimentalPagerApi | 42 | @ExperimentalPagerApi |
| 43 | class MainActivity : ComponentActivity() { | 43 | class MainActivity : ComponentActivity() { |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -31,13 +31,13 @@ import androidx.compose.ui.res.painterResource | @@ -31,13 +31,13 @@ import androidx.compose.ui.res.painterResource | ||
| 31 | import androidx.compose.ui.unit.dp | 31 | import androidx.compose.ui.unit.dp |
| 32 | import androidx.constraintlayout.compose.ConstraintLayout | 32 | import androidx.constraintlayout.compose.ConstraintLayout |
| 33 | import androidx.constraintlayout.compose.Dimension | 33 | import androidx.constraintlayout.compose.Dimension |
| 34 | -import io.livekit.android.composesample.ui.theme.BlueMain | ||
| 35 | -import io.livekit.android.composesample.ui.theme.NoVideoBackground | ||
| 36 | import io.livekit.android.room.Room | 34 | import io.livekit.android.room.Room |
| 37 | import io.livekit.android.room.participant.ConnectionQuality | 35 | import io.livekit.android.room.participant.ConnectionQuality |
| 38 | import io.livekit.android.room.participant.Participant | 36 | import io.livekit.android.room.participant.Participant |
| 39 | import io.livekit.android.sample.common.R | 37 | import io.livekit.android.sample.common.R |
| 40 | import io.livekit.android.util.flow | 38 | import io.livekit.android.util.flow |
| 39 | +import io.livekit.android.videoencodedecode.ui.theme.BlueMain | ||
| 40 | +import io.livekit.android.videoencodedecode.ui.theme.NoVideoBackground | ||
| 41 | 41 | ||
| 42 | /** | 42 | /** |
| 43 | * Widget for displaying a participant. | 43 | * Widget for displaying a participant. |
| 1 | /* | 1 | /* |
| 2 | - * Copyright 2023-2024 LiveKit, Inc. | 2 | + * Copyright 2023-2025 LiveKit, Inc. |
| 3 | * | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. | 5 | * you may not use this file except in compliance with the License. |
| @@ -21,7 +21,14 @@ import androidx.compose.foundation.layout.fillMaxSize | @@ -21,7 +21,14 @@ import androidx.compose.foundation.layout.fillMaxSize | ||
| 21 | import androidx.compose.foundation.layout.size | 21 | import androidx.compose.foundation.layout.size |
| 22 | import androidx.compose.material.Icon | 22 | import androidx.compose.material.Icon |
| 23 | import androidx.compose.material.Surface | 23 | import androidx.compose.material.Surface |
| 24 | -import androidx.compose.runtime.* | 24 | +import androidx.compose.runtime.Composable |
| 25 | +import androidx.compose.runtime.DisposableEffect | ||
| 26 | +import androidx.compose.runtime.collectAsState | ||
| 27 | +import androidx.compose.runtime.currentCompositeKeyHash | ||
| 28 | +import androidx.compose.runtime.getValue | ||
| 29 | +import androidx.compose.runtime.mutableStateOf | ||
| 30 | +import androidx.compose.runtime.remember | ||
| 31 | +import androidx.compose.runtime.setValue | ||
| 25 | import androidx.compose.ui.Alignment | 32 | import androidx.compose.ui.Alignment |
| 26 | import androidx.compose.ui.Modifier | 33 | import androidx.compose.ui.Modifier |
| 27 | import androidx.compose.ui.graphics.Color | 34 | import androidx.compose.ui.graphics.Color |
| @@ -39,7 +46,6 @@ import io.livekit.android.room.track.Track | @@ -39,7 +46,6 @@ import io.livekit.android.room.track.Track | ||
| 39 | import io.livekit.android.room.track.VideoTrack | 46 | import io.livekit.android.room.track.VideoTrack |
| 40 | import io.livekit.android.util.flow | 47 | import io.livekit.android.util.flow |
| 41 | import io.livekit.android.videoencodedecode.ui.ComposeVisibility | 48 | import io.livekit.android.videoencodedecode.ui.ComposeVisibility |
| 42 | -import kotlinx.coroutines.flow.* | ||
| 43 | 49 | ||
| 44 | /** | 50 | /** |
| 45 | * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use | 51 | * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use |
| 1 | -package io.livekit.android.composesample.ui | 1 | +/* |
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.videoencodedecode.ui | ||
| 2 | 18 | ||
| 3 | import androidx.compose.foundation.background | 19 | import androidx.compose.foundation.background |
| 4 | import androidx.compose.foundation.layout.* | 20 | import androidx.compose.foundation.layout.* |
| 1 | -package io.livekit.android.composesample.ui.theme | 1 | +/* |
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.videoencodedecode.ui.theme | ||
| 2 | 18 | ||
| 3 | import androidx.compose.ui.graphics.Color | 19 | import androidx.compose.ui.graphics.Color |
| 4 | 20 |
| 1 | -package io.livekit.android.composesample.ui.theme | 1 | +/* |
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.videoencodedecode.ui.theme | ||
| 2 | 18 | ||
| 3 | import androidx.compose.foundation.shape.RoundedCornerShape | 19 | import androidx.compose.foundation.shape.RoundedCornerShape |
| 4 | import androidx.compose.material.Shapes | 20 | import androidx.compose.material.Shapes |
| 1 | -package io.livekit.android.composesample.ui.theme | 1 | +/* |
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.videoencodedecode.ui.theme | ||
| 2 | 18 | ||
| 3 | import androidx.compose.material.MaterialTheme | 19 | import androidx.compose.material.MaterialTheme |
| 4 | import androidx.compose.material.darkColors | 20 | import androidx.compose.material.darkColors |
| 1 | -package io.livekit.android.composesample.ui.theme | 1 | +/* |
| 2 | + * Copyright 2025 LiveKit, Inc. | ||
| 3 | + * | ||
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | + * you may not use this file except in compliance with the License. | ||
| 6 | + * You may obtain a copy of the License at | ||
| 7 | + * | ||
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | + * | ||
| 10 | + * Unless required by applicable law or agreed to in writing, software | ||
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | + * See the License for the specific language governing permissions and | ||
| 14 | + * limitations under the License. | ||
| 15 | + */ | ||
| 16 | + | ||
| 17 | +package io.livekit.android.videoencodedecode.ui.theme | ||
| 2 | 18 | ||
| 3 | import androidx.compose.material.Typography | 19 | import androidx.compose.material.Typography |
| 4 | import androidx.compose.ui.text.TextStyle | 20 | import androidx.compose.ui.text.TextStyle |
-
请 注册 或 登录 后发表评论