diff --git a/README.md b/README.md index 1872754..dd3399b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This Android library exposes the `DailyVoiceClient` class, to connect to a Daily Add the following dependency to your `build.gradle` file: ``` -implementation "ai.rtvi:rtvi-client-android-daily:0.1.2" +implementation "ai.rtvi:rtvi-client-android-daily:0.1.3" ``` Instantiate from your code: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db99b4d..07a5d33 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,22 +1,20 @@ [versions] agp = "8.5.1" -daily-android-client = "0.22.1" +daily-android-client = "0.23.0" kotlin = "2.0.0" coreKtx = "1.13.1" -appcompat = "1.7.0" kotlinxCoroutinesTest = "1.8.1" kotlinxSerializationJson = "1.7.1" kotlinxSerializationPlugin = "2.0.0" dokka = "1.9.20" androidxTest = "1.6.1" -rtviClient = "0.1.1" +rtviClient = "0.1.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-rules = { module = "androidx.test:rules", version.ref = "androidxTest" } androidx-runner = { module = "androidx.test:runner", version.ref = "androidxTest" } daily-android-client = { module = "co.daily:client", version.ref = "daily-android-client" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } rtvi-client = { module = "ai.rtvi:client", version.ref = "rtviClient" } diff --git a/rtvi-client-android-daily/build.gradle.kts b/rtvi-client-android-daily/build.gradle.kts index 7a636f7..41d9423 100644 --- a/rtvi-client-android-daily/build.gradle.kts +++ b/rtvi-client-android-daily/build.gradle.kts @@ -42,11 +42,9 @@ android { dependencies { implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) implementation(libs.kotlinx.serialization.json) - implementation(libs.daily.android.client) - //api(project(":rtvi-client-android")) + api(libs.daily.android.client) api(libs.rtvi.client) androidTestImplementation(libs.androidx.runner) @@ -66,7 +64,7 @@ publishing { register("release") { groupId = "ai.rtvi" artifactId = "client-daily" - version = "0.1.2" + version = "0.1.3" pom { name.set("RTVI Client Daily Transport") @@ -105,6 +103,8 @@ signing { val signingKey = System.getenv("RTVI_GPG_SIGNING_KEY") val signingPassphrase = System.getenv("RTVI_GPG_SIGNING_PASSPHRASE") - useInMemoryPgpKeys(signingKey, signingPassphrase) - sign(publishing.publications) + if (!signingKey.isNullOrEmpty() || !signingPassphrase.isNullOrEmpty()) { + useInMemoryPgpKeys(signingKey, signingPassphrase) + sign(publishing.publications) + } } \ No newline at end of file diff --git a/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/DailyTransport.kt b/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/DailyTransport.kt index 220fbcb..9f6fb7f 100644 --- a/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/DailyTransport.kt +++ b/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/DailyTransport.kt @@ -61,11 +61,14 @@ class DailyTransport( private var call: CallClient? = null + private var expiry: Long? = null + private val callListener = object : CallClientListener { val participants = mutableMapOf() var botUser: Participant? = null var clientReady: Boolean = false + var tracks: Tracks? = null override fun onLocalAudioLevel(audioLevel: Float) { transportContext.callbacks.onUserAudioLevel(audioLevel) @@ -88,7 +91,7 @@ class DailyTransport( override fun onParticipantJoined(participant: co.daily.model.Participant) { updateParticipant(participant) - updateBotUser() + updateBotUserAndTracks() } override fun onParticipantLeft( @@ -96,12 +99,12 @@ class DailyTransport( reason: ParticipantLeftReason ) { participants.remove(participant.id.toRtvi()) - updateBotUser() + updateBotUserAndTracks() } override fun onParticipantUpdated(participant: co.daily.model.Participant) { updateParticipant(participant) - updateBotUser() + updateBotUserAndTracks() if (!clientReady && participant.id.toRtvi() == botUser?.id) { if (participant.media?.microphone?.state == MediaState.playable) { @@ -109,16 +112,24 @@ class DailyTransport( Log.i(TAG, "Sending client-ready") clientReady = true - sendMessage(MsgClientToServer( - type = "client-ready", - data = null - )) + sendMessage( + MsgClientToServer( + type = "client-ready", + data = null + ) + ) } } } - private fun updateBotUser() { + private fun updateBotUserAndTracks() { botUser = participants.values.firstOrNull { !it.local } + val newTracks = tracks() + + if (newTracks != tracks) { + tracks = newTracks + transportContext.callbacks.onTracksUpdated(newTracks) + } } private fun updateParticipant(participant: co.daily.model.Participant) { @@ -131,7 +142,7 @@ class DailyTransport( CallState.joining -> {} CallState.joined -> { call?.participants()?.all?.values?.forEach(::updateParticipant) - updateBotUser() + updateBotUserAndTracks() } CallState.leaving -> {} @@ -261,6 +272,13 @@ class DailyTransport( return@join } + expiry = it.success?.callConfig?.let { config -> + listOfNotNull( + config.roomExpiration, + config.tokenExpiration + ).minOrNull() + } + setState(TransportState.Connected) transportContext.callbacks.onConnected() promise.resolveOk(Unit) @@ -366,6 +384,8 @@ class DailyTransport( } } + override fun expiry() = thread.assertCurrent { expiry } + override fun enableCam(enable: Boolean): Future = thread.runOnThreadReturningFuture { withCall { callClient -> diff --git a/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/VoiceClientVideoView.kt b/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/VoiceClientVideoView.kt new file mode 100644 index 0000000..ad337ed --- /dev/null +++ b/rtvi-client-android-daily/src/main/java/ai/rtvi/client/daily/VoiceClientVideoView.kt @@ -0,0 +1,28 @@ +package ai.rtvi.client.daily + +import ai.rtvi.client.types.MediaTrackId +import android.content.Context +import android.util.AttributeSet +import co.daily.model.MediaStreamTrack +import co.daily.view.VideoView + +/** + * Overrides the Daily [VideoView] to allow [MediaTrackId] tracks from the VoiceClient to be + * rendered. + */ +class VoiceClientVideoView @JvmOverloads constructor( + viewContext: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : VideoView(viewContext, attrs, defStyleAttr, defStyleRes) { + + /** + * Displays the specified [MediaTrackId] in this view. + */ + var voiceClientTrack: MediaTrackId? + get() = track?.id?.let(::MediaTrackId) + set(value) { + track = value?.id?.let(::MediaStreamTrack) + } +} \ No newline at end of file