davidliu
Committed by GitHub

Changes for compose components livekit library (#282)

* save work

* update deps

* update deps

* Fix build

* Fix github actions

* Spotless fixes
正在显示 39 个修改的文件 包含 640 行增加181 行删除
@@ -32,7 +32,7 @@ jobs: @@ -32,7 +32,7 @@ jobs:
32 - name: set up JDK 12 32 - name: set up JDK 12
33 uses: actions/setup-java@v3.12.0 33 uses: actions/setup-java@v3.12.0
34 with: 34 with:
35 - java-version: '12' 35 + java-version: '17'
36 distribution: 'adopt' 36 distribution: 'adopt'
37 37
38 - uses: actions/cache@v3.3.2 38 - uses: actions/cache@v3.3.2
@@ -161,12 +161,14 @@ jobs: @@ -161,12 +161,14 @@ jobs:
161 sed -i -e "s,signing.keyId=,signing.keyId=$GPG_KEY_ID,g" gradle.properties 161 sed -i -e "s,signing.keyId=,signing.keyId=$GPG_KEY_ID,g" gradle.properties
162 sed -i -e "s,signing.password=,signing.password=$GPG_PASSWORD,g" gradle.properties 162 sed -i -e "s,signing.password=,signing.password=$GPG_PASSWORD,g" gradle.properties
163 sed -i -e "s,signing.secretKeyRingFile=,signing.secretKeyRingFile=$GITHUB_WORKSPACE/release.gpg,g" gradle.properties 163 sed -i -e "s,signing.secretKeyRingFile=,signing.secretKeyRingFile=$GITHUB_WORKSPACE/release.gpg,g" gradle.properties
  164 + sed -i -e "s,STAGING_PROFILE_ID=,STAGING_PROFILE_ID=$PROFILE_ID,g" gradle.properties
164 env: 165 env:
165 GPG_KEY_ARMOR: "${{ secrets.SIGNING_KEY_ARMOR }}" 166 GPG_KEY_ARMOR: "${{ secrets.SIGNING_KEY_ARMOR }}"
166 GPG_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 167 GPG_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
167 GPG_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} 168 GPG_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
168 NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} 169 NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
169 NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} 170 NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
  171 + PROFILE_ID: ${{ secrets.STAGING_PROFILE_ID }}
170 172
171 - name: Publish snapshot 173 - name: Publish snapshot
172 if: github.event_name == 'push' && contains(steps.version_name.outputs.version_name,'SNAPSHOT') 174 if: github.event_name == 'push' && contains(steps.version_name.outputs.version_name,'SNAPSHOT')
@@ -15,15 +15,15 @@ jobs: @@ -15,15 +15,15 @@ jobs:
15 working-directory: ./client-sdk-android 15 working-directory: ./client-sdk-android
16 steps: 16 steps:
17 - name: checkout client-sdk-android 17 - name: checkout client-sdk-android
18 - uses: actions/checkout@v2.3.4 18 + uses: actions/checkout@v4.0.0
19 with: 19 with:
20 path: ./client-sdk-android 20 path: ./client-sdk-android
21 submodules: recursive 21 submodules: recursive
22 22
23 - name: set up JDK 12 23 - name: set up JDK 12
24 - uses: actions/setup-java@v2 24 + uses: actions/setup-java@v3.12.0
25 with: 25 with:
26 - java-version: '12' 26 + java-version: '17'
27 distribution: 'adopt' 27 distribution: 'adopt'
28 28
29 - name: Grant execute permission for gradlew 29 - name: Grant execute permission for gradlew
@@ -51,15 +51,17 @@ jobs: @@ -51,15 +51,17 @@ jobs:
51 sed -i -e "s,signing.keyId=,signing.keyId=$GPG_KEY_ID,g" gradle.properties 51 sed -i -e "s,signing.keyId=,signing.keyId=$GPG_KEY_ID,g" gradle.properties
52 sed -i -e "s,signing.password=,signing.password=$GPG_PASSWORD,g" gradle.properties 52 sed -i -e "s,signing.password=,signing.password=$GPG_PASSWORD,g" gradle.properties
53 sed -i -e "s,signing.secretKeyRingFile=,signing.secretKeyRingFile=$GITHUB_WORKSPACE/release.gpg,g" gradle.properties 53 sed -i -e "s,signing.secretKeyRingFile=,signing.secretKeyRingFile=$GITHUB_WORKSPACE/release.gpg,g" gradle.properties
  54 + sed -i -e "s,STAGING_PROFILE_ID=,STAGING_PROFILE_ID=$PROFILE_ID,g" gradle.properties
54 env: 55 env:
55 GPG_KEY_ARMOR: "${{ secrets.SIGNING_KEY_ARMOR }}" 56 GPG_KEY_ARMOR: "${{ secrets.SIGNING_KEY_ARMOR }}"
56 GPG_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 57 GPG_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
57 GPG_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} 58 GPG_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
58 NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} 59 NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }}
59 NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} 60 NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
  61 + PROFILE_ID: ${{ secrets.STAGING_PROFILE_ID }}
60 62
61 - name: Publish to sonatype 63 - name: Publish to sonatype
62 run: ./gradlew publish 64 run: ./gradlew publish
63 - 65 +
64 - name: Close and release to maven 66 - name: Close and release to maven
65 run: ./gradlew closeAndReleaseRepository 67 run: ./gradlew closeAndReleaseRepository
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <project version="4"> 2 <project version="4">
3 <component name="CompilerConfiguration"> 3 <component name="CompilerConfiguration">
4 - <bytecodeTargetLevel target="1.8" /> 4 + <bytecodeTargetLevel target="17" />
5 </component> 5 </component>
6 </project> 6 </project>
1 <?xml version="1.0" encoding="UTF-8"?> 1 <?xml version="1.0" encoding="UTF-8"?>
2 <project version="4"> 2 <project version="4">
3 <component name="KotlinJpsPluginSettings"> 3 <component name="KotlinJpsPluginSettings">
4 - <option name="version" value="1.7.10" /> 4 + <option name="version" value="1.8.20" />
5 </component> 5 </component>
6 </project> 6 </project>
@@ -8,11 +8,11 @@ buildscript { @@ -8,11 +8,11 @@ buildscript {
8 mavenCentral() 8 mavenCentral()
9 } 9 }
10 dependencies { 10 dependencies {
11 - classpath 'com.android.tools.build:gradle:7.2.2'  
12 - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10" 11 + classpath "com.android.tools.build:gradle:$android_build_tools_version"
  12 + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" 13 classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
14 classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" 14 classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
15 - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.19' 15 + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.3'
16 classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0" 16 classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0"
17 classpath 'com.dicedmelon.gradle:jacoco-android:0.1.5' 17 classpath 'com.dicedmelon.gradle:jacoco-android:0.1.5'
18 classpath "com.diffplug.spotless:spotless-plugin-gradle:6.21.0" 18 classpath "com.diffplug.spotless:spotless-plugin-gradle:6.21.0"
@@ -72,7 +72,7 @@ task clean(type: Delete) { @@ -72,7 +72,7 @@ task clean(type: Delete) {
72 nexusStaging { 72 nexusStaging {
73 serverUrl = "https://s01.oss.sonatype.org/service/local/" 73 serverUrl = "https://s01.oss.sonatype.org/service/local/"
74 packageGroup = GROUP 74 packageGroup = GROUP
75 - stagingProfileId = "16b57cbf143daa" 75 + stagingProfileId = STAGING_PROFILE_ID
76 } 76 }
77 77
78 afterEvaluate { 78 afterEvaluate {
1 ext { 1 ext {
2 - compose_version = '1.2.1'  
3 - compose_compiler_version = '1.3.0'  
4 - kotlin_version = '1.7.10'  
5 - java_version = JavaVersion.VERSION_1_8 2 + android_build_tools_version = '8.0.2'
  3 + compose_version = '1.4.2'
  4 + compose_compiler_version = '1.4.6'
  5 + kotlin_version = '1.8.20'
  6 + java_version = JavaVersion.VERSION_17
6 dokka_version = '1.5.0' 7 dokka_version = '1.5.0'
7 androidSdk = [ 8 androidSdk = [
8 compileVersion: 33, 9 compileVersion: 33,
@@ -10,21 +11,27 @@ ext { @@ -10,21 +11,27 @@ ext {
10 minVersion : 21, 11 minVersion : 21,
11 ] 12 ]
12 versions = [ 13 versions = [
13 - androidx_core : "1.8.0", 14 + androidx_core : "1.10.1",
14 androidx_lifecycle: "2.5.1", 15 androidx_lifecycle: "2.5.1",
15 autoService : '1.0.1', 16 autoService : '1.0.1',
16 - dagger : "2.43", 17 + coroutines : "1.6.0",
  18 + dagger : "2.46",
17 groupie : "2.9.0", 19 groupie : "2.9.0",
18 junit : "4.13.2", 20 junit : "4.13.2",
19 junitJupiter : "5.5.0", 21 junitJupiter : "5.5.0",
20 - coroutines : "1.6.0",  
21 lint : "30.0.1", 22 lint : "30.0.1",
  23 + serialization : "1.5.0",
22 protobuf : "3.22.0", 24 protobuf : "3.22.0",
23 ] 25 ]
24 generated = [ 26 generated = [
25 protoSrc: "$projectDir/protocol", 27 protoSrc: "$projectDir/protocol",
26 ] 28 ]
27 deps = [ 29 deps = [
  30 + androidx : [
  31 + 'annotation' : 'androidx.annotation:annotation:1.6.0',
  32 + 'activity_compose' : 'androidx.activity:activity-compose:1.7.1',
  33 + 'constraintlayout_compose': "androidx.constraintlayout:constraintlayout-compose:1.0.1",
  34 + ],
28 auto : [ 35 auto : [
29 'service' : "com.google.auto.service:auto-service:${versions.autoService}", 36 'service' : "com.google.auto.service:auto-service:${versions.autoService}",
30 'serviceAnnotations': "com.google.auto.service:auto-service-annotations:${versions.autoService}", 37 'serviceAnnotations': "com.google.auto.service:auto-service-annotations:${versions.autoService}",
@@ -33,7 +40,11 @@ ext { @@ -33,7 +40,11 @@ ext {
33 "lib" : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}", 40 "lib" : "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}",
34 "test": "org.jetbrains.kotlinx:kotlinx-coroutines-test: ${versions.coroutines}", 41 "test": "org.jetbrains.kotlinx:kotlinx-coroutines-test: ${versions.coroutines}",
35 ], 42 ],
  43 + compose : [
  44 + "bom": "androidx.compose:compose-bom:2023.04.01",
  45 + ],
36 timber : "com.github.ajalt:timberkt:1.5.1", 46 timber : "com.github.ajalt:timberkt:1.5.1",
  47 +
37 // lint 48 // lint
38 lint : "com.android.tools.lint:lint:${versions.lint}", 49 lint : "com.android.tools.lint:lint:${versions.lint}",
39 lintApi : "com.android.tools.lint:lint-api:${versions.lint}", 50 lintApi : "com.android.tools.lint:lint-api:${versions.lint}",
@@ -41,9 +52,20 @@ ext { @@ -41,9 +52,20 @@ ext {
41 lintTests : "com.android.tools.lint:lint-tests:${versions.lint}", 52 lintTests : "com.android.tools.lint:lint-tests:${versions.lint}",
42 53
43 // tests 54 // tests
  55 + androidx_test : [
  56 + "core" : 'androidx.test:core:1.5.0',
  57 + "junit": "androidx.test.ext:junit:1.1.5",
  58 + ],
  59 + espresso : 'androidx.test.espresso:espresso-core:3.5.1',
44 junit : "junit:junit:${versions.junit}", 60 junit : "junit:junit:${versions.junit}",
45 junitJupiterApi : "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}", 61 junitJupiterApi : "org.junit.jupiter:junit-jupiter-api:${versions.junitJupiter}",
46 junitJupiterEngine: "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}", 62 junitJupiterEngine: "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}",
  63 + mockito : [
  64 + "core" : 'org.mockito:mockito-core:4.0.0',
  65 + "kotlin": "org.mockito.kotlin:mockito-kotlin:4.0.0",
  66 + ],
  67 + robolectric : 'org.robolectric:robolectric:4.10.2',
  68 +
47 ] 69 ]
48 annotations = [ 70 annotations = [
49 ] 71 ]
@@ -51,6 +51,8 @@ signing.keyId= @@ -51,6 +51,8 @@ signing.keyId=
51 signing.password= 51 signing.password=
52 signing.secretKeyRingFile= 52 signing.secretKeyRingFile=
53 53
  54 +STAGING_PROFILE_ID=
  55 +
54 RELEASE_SIGNING_ENABLED=true 56 RELEASE_SIGNING_ENABLED=true
55 57
56 # For instrumented tests. 58 # For instrumented tests.
@@ -58,4 +60,4 @@ RELEASE_SIGNING_ENABLED=true @@ -58,4 +60,4 @@ RELEASE_SIGNING_ENABLED=true
58 # Instead, override in ~/.gradle/gradle.properties 60 # Instead, override in ~/.gradle/gradle.properties
59 livekitUrl= 61 livekitUrl=
60 livekitApiKey= 62 livekitApiKey=
61 -livekitApiSecret=  
  63 +livekitApiSecret=
@@ -105,12 +105,12 @@ afterEvaluate { project -> @@ -105,12 +105,12 @@ afterEvaluate { project ->
105 } 105 }
106 106
107 task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 107 task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
108 - classifier = 'javadoc' 108 + archiveClassifier = 'javadoc'
109 from androidJavadocs.destinationDir 109 from androidJavadocs.destinationDir
110 } 110 }
111 111
112 task androidSourcesJar(type: Jar) { 112 task androidSourcesJar(type: Jar) {
113 - classifier = 'sources' 113 + archiveClassifier = 'sources'
114 from android.sourceSets.main.java.source 114 from android.sourceSets.main.java.source
115 } 115 }
116 } 116 }
@@ -131,13 +131,13 @@ afterEvaluate { project -> @@ -131,13 +131,13 @@ afterEvaluate { project ->
131 } 131 }
132 } 132 }
133 133
134 - artifacts {  
135 - if (project.getPlugins().hasPlugin('com.android.application') ||  
136 - project.getPlugins().hasPlugin('com.android.library')) {  
137 - archives androidSourcesJar  
138 - archives androidJavadocsJar  
139 - }  
140 - } 134 +// artifacts {
  135 +// if (project.getPlugins().hasPlugin('com.android.application') ||
  136 +// project.getPlugins().hasPlugin('com.android.library')) {
  137 +// archives androidSourcesJar
  138 +// archives androidJavadocsJar
  139 +// }
  140 +// }
141 141
142 android.libraryVariants.all { variant -> 142 android.libraryVariants.all { variant ->
143 tasks.androidJavadocs.doFirst { 143 tasks.androidJavadocs.doFirst {
@@ -149,8 +149,8 @@ afterEvaluate { project -> @@ -149,8 +149,8 @@ afterEvaluate { project ->
149 publication.groupId = GROUP 149 publication.groupId = GROUP
150 publication.version = VERSION_NAME 150 publication.version = VERSION_NAME
151 151
152 - publication.artifact androidSourcesJar  
153 - publication.artifact androidJavadocsJar 152 +// publication.artifact androidSourcesJar
  153 +// publication.artifact androidJavadocsJar
154 154
155 configurePom(publication.pom) 155 configurePom(publication.pom)
156 } 156 }
@@ -162,4 +162,4 @@ afterEvaluate { project -> @@ -162,4 +162,4 @@ afterEvaluate { project ->
162 } 162 }
163 } 163 }
164 } 164 }
165 -}  
  165 +}
1 -#Mon May 22 01:18:06 JST 2023 1 +#Mon May 01 22:58:53 JST 2023
2 distributionBase=GRADLE_USER_HOME 2 distributionBase=GRADLE_USER_HOME
3 distributionPath=wrapper/dists 3 distributionPath=wrapper/dists
4 -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 zipStoreBase=GRADLE_USER_HOME 5 zipStoreBase=GRADLE_USER_HOME
6 zipStorePath=wrapper/dists 6 zipStorePath=wrapper/dists
  1 +# Android Kotlin SDK for LiveKit
  2 +
  3 +----
@@ -6,13 +6,12 @@ plugins { @@ -6,13 +6,12 @@ plugins {
6 id 'kotlinx-serialization' 6 id 'kotlinx-serialization'
7 id 'com.google.protobuf' 7 id 'com.google.protobuf'
8 id 'jacoco' 8 id 'jacoco'
9 - id 'com.dicedmelon.gradle.jacoco-android' 9 + id("com.mxalbert.gradle.jacoco-android") version "0.2.1"
10 } 10 }
11 11
12 android { 12 android {
  13 + namespace 'io.livekit.android'
13 compileSdkVersion androidSdk.compileVersion 14 compileSdkVersion androidSdk.compileVersion
14 - buildToolsVersion "30.0.3"  
15 -  
16 15
17 defaultConfig { 16 defaultConfig {
18 minSdkVersion androidSdk.minVersion 17 minSdkVersion androidSdk.minVersion
@@ -58,15 +57,20 @@ android { @@ -58,15 +57,20 @@ android {
58 } 57 }
59 58
60 buildFeatures { 59 buildFeatures {
61 - compose true  
62 - }  
63 - composeOptions {  
64 - kotlinCompilerExtensionVersion compose_compiler_version 60 + buildConfig = true
65 } 61 }
66 kotlinOptions { 62 kotlinOptions {
67 freeCompilerArgs = ["-Xinline-classes", "-opt-in=kotlin.RequiresOptIn"] 63 freeCompilerArgs = ["-Xinline-classes", "-opt-in=kotlin.RequiresOptIn"]
68 jvmTarget = java_version 64 jvmTarget = java_version
69 } 65 }
  66 +
  67 + publishing {
  68 + singleVariant("release") {
  69 + withJavadocJar()
  70 + withSourcesJar()
  71 + }
  72 + }
  73 +
70 } 74 }
71 75
72 protobuf { 76 protobuf {
@@ -100,7 +104,7 @@ tasks.withType(Test) { @@ -100,7 +104,7 @@ tasks.withType(Test) {
100 } 104 }
101 105
102 jacocoAndroidUnitTestReport { 106 jacocoAndroidUnitTestReport {
103 - excludes += ['livekit/**',] 107 + excludes.add('livekit/**')
104 } 108 }
105 109
106 dokkaHtml { 110 dokkaHtml {
@@ -116,7 +120,7 @@ dokkaHtml { @@ -116,7 +120,7 @@ dokkaHtml {
116 120
117 // URL showing where the source code can be accessed through the web browser 121 // URL showing where the source code can be accessed through the web browser
118 remoteUrl.set(new URL( 122 remoteUrl.set(new URL(
119 - "https://github.com/livekit/client-sdk-android/tree/master/livekit-android-sdk/src/main/java")) 123 + "https://github.com/livekit/client-sdk-android/tree/master/livekit-android-sdk/src/main/java"))
120 // Suffix which is used to append the line number to the URL. Use #L for GitHub 124 // Suffix which is used to append the line number to the URL. Use #L for GitHub
121 remoteLineSuffix.set("#L") 125 remoteLineSuffix.set("#L")
122 } 126 }
@@ -138,14 +142,13 @@ dependencies { @@ -138,14 +142,13 @@ dependencies {
138 //api fileTree(dir: 'libs', include: ['*.jar']) 142 //api fileTree(dir: 'libs', include: ['*.jar'])
139 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 143 implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
140 implementation deps.coroutines.lib 144 implementation deps.coroutines.lib
141 - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0' 145 + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:${versions.serialization}"
142 api 'io.github.webrtc-sdk:android:114.5735.05' 146 api 'io.github.webrtc-sdk:android:114.5735.05'
143 api "com.squareup.okhttp3:okhttp:4.10.0" 147 api "com.squareup.okhttp3:okhttp:4.10.0"
144 api 'com.github.davidliu:audioswitch:d18e3e31d427c27f1593030e024b370bf24480fd' 148 api 'com.github.davidliu:audioswitch:d18e3e31d427c27f1593030e024b370bf24480fd'
145 - implementation "androidx.annotation:annotation:1.4.0" 149 + implementation deps.androidx.annotation
146 implementation "androidx.core:core:${versions.androidx_core}" 150 implementation "androidx.core:core:${versions.androidx_core}"
147 implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}" 151 implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}"
148 - implementation "androidx.compose.ui:ui:$compose_version"  
149 152
150 implementation "com.google.dagger:dagger:${versions.dagger}" 153 implementation "com.google.dagger:dagger:${versions.dagger}"
151 kapt "com.google.dagger:dagger-compiler:${versions.dagger}" 154 kapt "com.google.dagger:dagger-compiler:${versions.dagger}"
@@ -156,15 +159,15 @@ dependencies { @@ -156,15 +159,15 @@ dependencies {
156 lintChecks project(':livekit-lint') 159 lintChecks project(':livekit-lint')
157 lintPublish project(':livekit-lint') 160 lintPublish project(':livekit-lint')
158 161
159 - testImplementation 'junit:junit:4.13.2'  
160 - testImplementation 'org.robolectric:robolectric:4.10.2'  
161 - testImplementation 'org.mockito:mockito-core:4.0.0'  
162 - testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"  
163 - testImplementation 'androidx.test:core:1.4.0' 162 + testImplementation deps.junit
  163 + testImplementation deps.robolectric
  164 + testImplementation deps.mockito.core
  165 + testImplementation deps.mockito.kotlin
  166 + testImplementation deps.androidx_test.core
164 testImplementation deps.coroutines.test 167 testImplementation deps.coroutines.test
165 kaptTest "com.google.dagger:dagger-compiler:${versions.dagger}" 168 kaptTest "com.google.dagger:dagger-compiler:${versions.dagger}"
166 - androidTestImplementation 'androidx.test.ext:junit:1.1.3'  
167 - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 169 + androidTestImplementation deps.androidx_test.junit
  170 + androidTestImplementation deps.espresso
168 } 171 }
169 172
170 apply from: rootProject.file('gradle/gradle-mvn-push.gradle') 173 apply from: rootProject.file('gradle/gradle-mvn-push.gradle')
@@ -14,8 +14,7 @@ @@ -14,8 +14,7 @@
14 limitations under the License. 14 limitations under the License.
15 --> 15 -->
16 16
17 -<manifest package="io.livekit.android"  
18 - xmlns:android="http://schemas.android.com/apk/res/android"> 17 +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
19 18
20 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 19 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
21 <uses-permission android:name="android.permission.INTERNET" /> 20 <uses-permission android:name="android.permission.INTERNET" />
@@ -19,7 +19,6 @@ package io.livekit.android.room.track @@ -19,7 +19,6 @@ package io.livekit.android.room.track
19 import android.view.View 19 import android.view.View
20 import io.livekit.android.dagger.InjectionNames 20 import io.livekit.android.dagger.InjectionNames
21 import io.livekit.android.events.TrackEvent 21 import io.livekit.android.events.TrackEvent
22 -import io.livekit.android.room.track.video.ComposeVisibility  
23 import io.livekit.android.room.track.video.VideoSinkVisibility 22 import io.livekit.android.room.track.video.VideoSinkVisibility
24 import io.livekit.android.room.track.video.ViewVisibility 23 import io.livekit.android.room.track.video.ViewVisibility
25 import io.livekit.android.util.LKLog 24 import io.livekit.android.util.LKLog
@@ -55,7 +54,7 @@ class RemoteVideoTrack( @@ -55,7 +54,7 @@ class RemoteVideoTrack(
55 54
56 /** 55 /**
57 * If `autoManageVideo` is enabled, a VideoSinkVisibility should be passed, using 56 * If `autoManageVideo` is enabled, a VideoSinkVisibility should be passed, using
58 - * [ViewVisibility] if using a traditional View layout, or [ComposeVisibility] 57 + * [ViewVisibility] if using a traditional View layout, or ComposeVisibility
59 * if using Jetpack Compose. 58 * if using Jetpack Compose.
60 * 59 *
61 * By default, any Views passed to this method will be added with a [ViewVisibility]. 60 * By default, any Views passed to this method will be added with a [ViewVisibility].
@@ -22,10 +22,9 @@ import android.os.Looper @@ -22,10 +22,9 @@ import android.os.Looper
22 import android.view.View 22 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 androidx.compose.ui.layout.LayoutCoordinates  
26 import io.livekit.android.room.track.Track 25 import io.livekit.android.room.track.Track
27 import io.livekit.android.room.track.video.ViewVisibility.Notifier 26 import io.livekit.android.room.track.video.ViewVisibility.Notifier
28 -import java.util.* 27 +import java.util.Observable
29 28
30 abstract class VideoSinkVisibility : Observable() { 29 abstract class VideoSinkVisibility : Observable() {
31 abstract fun isVisible(): Boolean 30 abstract fun isVisible(): Boolean
@@ -48,46 +47,6 @@ abstract class VideoSinkVisibility : Observable() { @@ -48,46 +47,6 @@ abstract class VideoSinkVisibility : Observable() {
48 } 47 }
49 } 48 }
50 49
51 -class ComposeVisibility : VideoSinkVisibility() {  
52 - private var coordinates: LayoutCoordinates? = null  
53 -  
54 - private var lastVisible = isVisible()  
55 - private var lastSize = size()  
56 - override fun isVisible(): Boolean {  
57 - return (coordinates?.isAttached == true &&  
58 - coordinates?.size?.width != 0 &&  
59 - coordinates?.size?.height != 0)  
60 - }  
61 -  
62 - override fun size(): Track.Dimensions {  
63 - val width = coordinates?.size?.width ?: 0  
64 - val height = coordinates?.size?.height ?: 0  
65 - return Track.Dimensions(width, height)  
66 - }  
67 -  
68 - // Note, LayoutCoordinates are mutable and may be reused.  
69 - fun onGloballyPositioned(layoutCoordinates: LayoutCoordinates) {  
70 - coordinates = layoutCoordinates  
71 - val visible = isVisible()  
72 - val size = size()  
73 -  
74 - if (lastVisible != visible || lastSize != size) {  
75 - notifyChanged()  
76 - }  
77 -  
78 - lastVisible = visible  
79 - lastSize = size  
80 - }  
81 -  
82 - fun onDispose() {  
83 - if (coordinates == null) {  
84 - return  
85 - }  
86 - coordinates = null  
87 - notifyChanged()  
88 - }  
89 -}  
90 -  
91 /** 50 /**
92 * A [VideoSinkVisibility] for views. If using a custom view other than the sdk provided renderers, 51 * A [VideoSinkVisibility] for views. If using a custom view other than the sdk provided renderers,
93 * you must implement [Notifier], override [View.onVisibilityChanged] and call through to [recalculate], or 52 * you must implement [Notifier], override [View.onVisibilityChanged] and call through to [recalculate], or
@@ -102,7 +61,6 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() { @@ -102,7 +61,6 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() {
102 private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener { 61 private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
103 scheduleRecalculate() 62 scheduleRecalculate()
104 } 63 }
105 -  
106 private val scrollListener = ViewTreeObserver.OnScrollChangedListener { 64 private val scrollListener = ViewTreeObserver.OnScrollChangedListener {
107 scheduleRecalculate() 65 scheduleRecalculate()
108 } 66 }
@@ -36,7 +36,11 @@ limitations under the License. @@ -36,7 +36,11 @@ limitations under the License.
36 Original repo can be found at: https://github.com/ajalt/LKLogkt 36 Original repo can be found at: https://github.com/ajalt/LKLogkt
37 */ 37 */
38 38
39 -internal class LKLog { 39 +/**
  40 + * @suppress
  41 + */
  42 +@Suppress("NOTHING_TO_INLINE", "unused")
  43 +class LKLog {
40 44
41 companion object { 45 companion object {
42 var loggingLevel = OFF 46 var loggingLevel = OFF
@@ -90,7 +94,7 @@ internal class LKLog { @@ -90,7 +94,7 @@ internal class LKLog {
90 inline fun wtf(t: Throwable?) = log(WTF) { Timber.wtf(t) } 94 inline fun wtf(t: Throwable?) = log(WTF) { Timber.wtf(t) }
91 95
92 /** @suppress */ 96 /** @suppress */
93 - internal inline fun log(loggingLevel: LoggingLevel, block: () -> Unit) { 97 + inline fun log(loggingLevel: LoggingLevel, block: () -> Unit) {
94 if (loggingLevel >= LKLog.loggingLevel && Timber.treeCount() > 0) block() 98 if (loggingLevel >= LKLog.loggingLevel && Timber.treeCount() > 0) block()
95 } 99 }
96 } 100 }
@@ -159,6 +159,7 @@ class MockPeerConnection( @@ -159,6 +159,7 @@ class MockPeerConnection(
159 return super.addTransceiver(mediaType, init) 159 return super.addTransceiver(mediaType, init)
160 } 160 }
161 161
  162 + @Deprecated("Deprecated in Java")
162 override fun getStats(observer: StatsObserver?, track: MediaStreamTrack?): Boolean { 163 override fun getStats(observer: StatsObserver?, track: MediaStreamTrack?): Boolean {
163 observer?.onComplete(emptyArray()) 164 observer?.onComplete(emptyArray())
164 return true 165 return true
@@ -5,8 +5,8 @@ plugins { @@ -5,8 +5,8 @@ plugins {
5 } 5 }
6 6
7 java { 7 java {
8 - sourceCompatibility = JavaVersion.VERSION_1_8  
9 - targetCompatibility = JavaVersion.VERSION_1_8 8 + sourceCompatibility = java_version
  9 + targetCompatibility = java_version
10 } 10 }
11 11
12 dependencies { 12 dependencies {
@@ -4,12 +4,13 @@ plugins { @@ -4,12 +4,13 @@ plugins {
4 } 4 }
5 5
6 android { 6 android {
7 - compileSdk 32 7 + namespace 'io.livekit.android.sample.basic'
  8 + compileSdk 33
8 9
9 defaultConfig { 10 defaultConfig {
10 applicationId "io.livekit.android.sample.basic" 11 applicationId "io.livekit.android.sample.basic"
11 minSdk 21 12 minSdk 21
12 - targetSdk 32 13 + targetSdk 33
13 versionCode 1 14 versionCode 1
14 versionName "1.0" 15 versionName "1.0"
15 16
@@ -23,11 +24,11 @@ android { @@ -23,11 +24,11 @@ android {
23 } 24 }
24 } 25 }
25 compileOptions { 26 compileOptions {
26 - sourceCompatibility JavaVersion.VERSION_1_8  
27 - targetCompatibility JavaVersion.VERSION_1_8 27 + sourceCompatibility java_version
  28 + targetCompatibility java_version
28 } 29 }
29 kotlinOptions { 30 kotlinOptions {
30 - jvmTarget = '1.8' 31 + jvmTarget = java_version
31 } 32 }
32 } 33 }
33 34
@@ -15,6 +15,7 @@ final url = getDefaultUrl() @@ -15,6 +15,7 @@ final url = getDefaultUrl()
15 final token = getDefaultToken() 15 final token = getDefaultToken()
16 16
17 android { 17 android {
  18 + namespace "io.livekit.android.sample.common"
18 compileSdk androidSdk.compileVersion 19 compileSdk androidSdk.compileVersion
19 20
20 defaultConfig { 21 defaultConfig {
@@ -39,12 +40,15 @@ android { @@ -39,12 +40,15 @@ android {
39 buildConfigField "String", "DEFAULT_TOKEN", "\"$token\"" 40 buildConfigField "String", "DEFAULT_TOKEN", "\"$token\""
40 } 41 }
41 } 42 }
  43 + buildFeatures {
  44 + buildConfig = true
  45 + }
42 compileOptions { 46 compileOptions {
43 - sourceCompatibility JavaVersion.VERSION_1_8  
44 - targetCompatibility JavaVersion.VERSION_1_8 47 + sourceCompatibility java_version
  48 + targetCompatibility java_version
45 } 49 }
46 kotlinOptions { 50 kotlinOptions {
47 - jvmTarget = '1.8' 51 + jvmTarget = java_version
48 } 52 }
49 } 53 }
50 54
1 <?xml version="1.0" encoding="utf-8"?> 1 <?xml version="1.0" encoding="utf-8"?>
2 -<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
3 - package="io.livekit.android.sample.common"> 2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android">
4 3
5 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> 4 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6 <uses-permission android:name="android.permission.INTERNET" /> 5 <uses-permission android:name="android.permission.INTERNET" />
@@ -111,6 +111,8 @@ class CallViewModel( @@ -111,6 +111,8 @@ class CallViewModel(
111 private val mutablePermissionAllowed = MutableStateFlow(true) 111 private val mutablePermissionAllowed = MutableStateFlow(true)
112 val permissionAllowed = mutablePermissionAllowed.hide() 112 val permissionAllowed = mutablePermissionAllowed.hide()
113 113
  114 + var messagesReceived = 0
  115 +
114 init { 116 init {
115 viewModelScope.launch { 117 viewModelScope.launch {
116 // Collect any errors. 118 // Collect any errors.
@@ -137,14 +139,9 @@ class CallViewModel( @@ -137,14 +139,9 @@ class CallViewModel(
137 is RoomEvent.FailedToConnect -> mutableError.value = it.error 139 is RoomEvent.FailedToConnect -> mutableError.value = it.error
138 is RoomEvent.DataReceived -> { 140 is RoomEvent.DataReceived -> {
139 val identity = it.participant?.identity ?: "server" 141 val identity = it.participant?.identity ?: "server"
140 - val message = it.data.toString(Charsets.UTF_8)  
141 - mutableDataReceived.emit("$identity: $message")  
142 - }  
143 -  
144 - is RoomEvent.TrackSubscribed -> {  
145 - launch { collectTrackStats(it) } 142 + messagesReceived++
  143 + Timber.e { "message received from $identity, count $messagesReceived" }
146 } 144 }
147 -  
148 else -> { 145 else -> {
149 Timber.e { "Room event: $it" } 146 Timber.e { "Room event: $it" }
150 } 147 }
@@ -5,6 +5,7 @@ plugins { @@ -5,6 +5,7 @@ plugins {
5 } 5 }
6 6
7 android { 7 android {
  8 + namespace 'io.livekit.android.composesample'
8 compileSdkVersion androidSdk.compileVersion 9 compileSdkVersion androidSdk.compileVersion
9 10
10 defaultConfig { 11 defaultConfig {
  1 +/*
  2 + * Copyright 2023 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.composesample 17 package io.livekit.android.composesample
2 18
3 import android.app.Activity 19 import android.app.Activity
@@ -31,6 +47,7 @@ import io.livekit.android.composesample.ui.theme.AppTheme @@ -31,6 +47,7 @@ import io.livekit.android.composesample.ui.theme.AppTheme
31 import io.livekit.android.room.Room 47 import io.livekit.android.room.Room
32 import io.livekit.android.room.participant.Participant 48 import io.livekit.android.room.participant.Participant
33 import io.livekit.android.sample.CallViewModel 49 import io.livekit.android.sample.CallViewModel
  50 +import io.livekit.android.sample.common.R
34 import kotlinx.coroutines.Dispatchers 51 import kotlinx.coroutines.Dispatchers
35 import kotlinx.coroutines.launch 52 import kotlinx.coroutines.launch
36 import kotlinx.parcelize.Parcelize 53 import kotlinx.parcelize.Parcelize
@@ -45,13 +62,13 @@ class CallActivity : AppCompatActivity() { @@ -45,13 +62,13 @@ class CallActivity : AppCompatActivity() {
45 token = args.token, 62 token = args.token,
46 e2ee = args.e2eeOn, 63 e2ee = args.e2eeOn,
47 e2eeKey = args.e2eeKey, 64 e2eeKey = args.e2eeKey,
48 - application = application 65 + application = application,
49 ) 66 )
50 } 67 }
51 68
52 private val screenCaptureIntentLauncher = 69 private val screenCaptureIntentLauncher =
53 registerForActivityResult( 70 registerForActivityResult(
54 - ActivityResultContracts.StartActivityForResult() 71 + ActivityResultContracts.StartActivityForResult(),
55 ) { result -> 72 ) { result ->
56 val resultCode = result.resultCode 73 val resultCode = result.resultCode
57 val data = result.data 74 val data = result.data
@@ -146,24 +163,26 @@ class CallActivity : AppCompatActivity() { @@ -146,24 +163,26 @@ class CallActivity : AppCompatActivity() {
146 ConstraintLayout( 163 ConstraintLayout(
147 modifier = Modifier 164 modifier = Modifier
148 .fillMaxSize() 165 .fillMaxSize()
149 - .background(MaterialTheme.colors.background) 166 + .background(MaterialTheme.colors.background),
150 ) { 167 ) {
151 val (speakerView, audienceRow, buttonBar) = createRefs() 168 val (speakerView, audienceRow, buttonBar) = createRefs()
152 169
153 // Primary speaker view 170 // Primary speaker view
154 - Surface(modifier = Modifier.constrainAs(speakerView) {  
155 - top.linkTo(parent.top)  
156 - start.linkTo(parent.start)  
157 - end.linkTo(parent.end)  
158 - bottom.linkTo(audienceRow.top)  
159 - width = Dimension.fillToConstraints  
160 - height = Dimension.fillToConstraints  
161 - }) { 171 + Surface(
  172 + modifier = Modifier.constrainAs(speakerView) {
  173 + top.linkTo(parent.top)
  174 + start.linkTo(parent.start)
  175 + end.linkTo(parent.end)
  176 + bottom.linkTo(audienceRow.top)
  177 + width = Dimension.fillToConstraints
  178 + height = Dimension.fillToConstraints
  179 + },
  180 + ) {
162 if (room != null && primarySpeaker != null) { 181 if (room != null && primarySpeaker != null) {
163 ParticipantItem( 182 ParticipantItem(
164 room = room, 183 room = room,
165 participant = primarySpeaker, 184 participant = primarySpeaker,
166 - isSpeaking = activeSpeakers.contains(primarySpeaker) 185 + isSpeaking = activeSpeakers.contains(primarySpeaker),
167 ) 186 )
168 } 187 }
169 } 188 }
@@ -178,12 +197,12 @@ class CallActivity : AppCompatActivity() { @@ -178,12 +197,12 @@ class CallActivity : AppCompatActivity() {
178 end.linkTo(parent.end) 197 end.linkTo(parent.end)
179 width = Dimension.fillToConstraints 198 width = Dimension.fillToConstraints
180 height = Dimension.value(120.dp) 199 height = Dimension.value(120.dp)
181 - } 200 + },
182 ) { 201 ) {
183 if (room != null) { 202 if (room != null) {
184 items( 203 items(
185 count = participants.size, 204 count = participants.size,
186 - key = { index -> participants[index].sid } 205 + key = { index -> participants[index].sid },
187 ) { index -> 206 ) { index ->
188 ParticipantItem( 207 ParticipantItem(
189 room = room, 208 room = room,
@@ -191,7 +210,7 @@ class CallActivity : AppCompatActivity() { @@ -191,7 +210,7 @@ class CallActivity : AppCompatActivity() {
191 isSpeaking = activeSpeakers.contains(participants[index]), 210 isSpeaking = activeSpeakers.contains(participants[index]),
192 modifier = Modifier 211 modifier = Modifier
193 .fillMaxHeight() 212 .fillMaxHeight()
194 - .aspectRatio(1.0f, true) 213 + .aspectRatio(1.0f, true),
195 ) 214 )
196 } 215 }
197 } 216 }
@@ -208,7 +227,7 @@ class CallActivity : AppCompatActivity() { @@ -208,7 +227,7 @@ class CallActivity : AppCompatActivity() {
208 height = Dimension.wrapContent 227 height = Dimension.wrapContent
209 }, 228 },
210 verticalArrangement = Arrangement.SpaceEvenly, 229 verticalArrangement = Arrangement.SpaceEvenly,
211 - horizontalAlignment = Alignment.CenterHorizontally 230 + horizontalAlignment = Alignment.CenterHorizontally,
212 ) { 231 ) {
213 val controlSize = 40.dp 232 val controlSize = 40.dp
214 val controlPadding = 4.dp 233 val controlPadding = 4.dp
@@ -221,7 +240,7 @@ class CallActivity : AppCompatActivity() { @@ -221,7 +240,7 @@ class CallActivity : AppCompatActivity() {
221 onClick = { viewModel.setMicEnabled(!micEnabled) }, 240 onClick = { viewModel.setMicEnabled(!micEnabled) },
222 modifier = Modifier 241 modifier = Modifier
223 .size(controlSize) 242 .size(controlSize)
224 - .padding(controlPadding) 243 + .padding(controlPadding),
225 ) { 244 ) {
226 val resource = 245 val resource =
227 if (micEnabled) R.drawable.outline_mic_24 else R.drawable.outline_mic_off_24 246 if (micEnabled) R.drawable.outline_mic_24 else R.drawable.outline_mic_off_24
@@ -235,7 +254,7 @@ class CallActivity : AppCompatActivity() { @@ -235,7 +254,7 @@ class CallActivity : AppCompatActivity() {
235 onClick = { viewModel.setCameraEnabled(!videoEnabled) }, 254 onClick = { viewModel.setCameraEnabled(!videoEnabled) },
236 modifier = Modifier 255 modifier = Modifier
237 .size(controlSize) 256 .size(controlSize)
238 - .padding(controlPadding) 257 + .padding(controlPadding),
239 ) { 258 ) {
240 val resource = 259 val resource =
241 if (videoEnabled) R.drawable.outline_videocam_24 else R.drawable.outline_videocam_off_24 260 if (videoEnabled) R.drawable.outline_videocam_24 else R.drawable.outline_videocam_off_24
@@ -249,7 +268,7 @@ class CallActivity : AppCompatActivity() { @@ -249,7 +268,7 @@ class CallActivity : AppCompatActivity() {
249 onClick = { viewModel.flipCamera() }, 268 onClick = { viewModel.flipCamera() },
250 modifier = Modifier 269 modifier = Modifier
251 .size(controlSize) 270 .size(controlSize)
252 - .padding(controlPadding) 271 + .padding(controlPadding),
253 ) { 272 ) {
254 Icon( 273 Icon(
255 painterResource(id = R.drawable.outline_flip_camera_android_24), 274 painterResource(id = R.drawable.outline_flip_camera_android_24),
@@ -267,7 +286,7 @@ class CallActivity : AppCompatActivity() { @@ -267,7 +286,7 @@ class CallActivity : AppCompatActivity() {
267 }, 286 },
268 modifier = Modifier 287 modifier = Modifier
269 .size(controlSize) 288 .size(controlSize)
270 - .padding(controlPadding) 289 + .padding(controlPadding),
271 ) { 290 ) {
272 val resource = 291 val resource =
273 if (screencastEnabled) R.drawable.baseline_cast_connected_24 else R.drawable.baseline_cast_24 292 if (screencastEnabled) R.drawable.baseline_cast_connected_24 else R.drawable.baseline_cast_24
@@ -284,7 +303,7 @@ class CallActivity : AppCompatActivity() { @@ -284,7 +303,7 @@ class CallActivity : AppCompatActivity() {
284 onClick = { showMessageDialog = true }, 303 onClick = { showMessageDialog = true },
285 modifier = Modifier 304 modifier = Modifier
286 .size(controlSize) 305 .size(controlSize)
287 - .padding(controlPadding) 306 + .padding(controlPadding),
288 ) { 307 ) {
289 Icon( 308 Icon(
290 painterResource(id = R.drawable.baseline_chat_24), 309 painterResource(id = R.drawable.baseline_chat_24),
@@ -316,7 +335,7 @@ class CallActivity : AppCompatActivity() { @@ -316,7 +335,7 @@ class CallActivity : AppCompatActivity() {
316 onSendMessage(messageToSend) 335 onSendMessage(messageToSend)
317 showMessageDialog = false 336 showMessageDialog = false
318 messageToSend = "" 337 messageToSend = ""
319 - } 338 + },
320 ) { Text("Send") } 339 ) { Text("Send") }
321 }, 340 },
322 dismissButton = { 341 dismissButton = {
@@ -324,7 +343,7 @@ class CallActivity : AppCompatActivity() { @@ -324,7 +343,7 @@ class CallActivity : AppCompatActivity() {
324 onClick = { 343 onClick = {
325 showMessageDialog = false 344 showMessageDialog = false
326 messageToSend = "" 345 messageToSend = ""
327 - } 346 + },
328 ) { Text("Cancel") } 347 ) { Text("Cancel") }
329 }, 348 },
330 backgroundColor = Color.Black, 349 backgroundColor = Color.Black,
@@ -334,7 +353,7 @@ class CallActivity : AppCompatActivity() { @@ -334,7 +353,7 @@ class CallActivity : AppCompatActivity() {
334 onClick = { onExitClick() }, 353 onClick = { onExitClick() },
335 modifier = Modifier 354 modifier = Modifier
336 .size(controlSize) 355 .size(controlSize)
337 - .padding(controlPadding) 356 + .padding(controlPadding),
338 ) { 357 ) {
339 Icon( 358 Icon(
340 painterResource(id = R.drawable.ic_baseline_cancel_24), 359 painterResource(id = R.drawable.ic_baseline_cancel_24),
@@ -356,7 +375,7 @@ class CallActivity : AppCompatActivity() { @@ -356,7 +375,7 @@ class CallActivity : AppCompatActivity() {
356 onClick = { showAudioDeviceDialog = true }, 375 onClick = { showAudioDeviceDialog = true },
357 modifier = Modifier 376 modifier = Modifier
358 .size(controlSize) 377 .size(controlSize)
359 - .padding(controlPadding) 378 + .padding(controlPadding),
360 ) { 379 ) {
361 val resource = R.drawable.volume_up_48px 380 val resource = R.drawable.volume_up_48px
362 Icon( 381 Icon(
@@ -370,14 +389,14 @@ class CallActivity : AppCompatActivity() { @@ -370,14 +389,14 @@ class CallActivity : AppCompatActivity() {
370 onDismissRequest = { showAudioDeviceDialog = false }, 389 onDismissRequest = { showAudioDeviceDialog = false },
371 selectDevice = { audioSwitchHandler?.selectDevice(it) }, 390 selectDevice = { audioSwitchHandler?.selectDevice(it) },
372 currentDevice = audioSwitchHandler?.selectedAudioDevice, 391 currentDevice = audioSwitchHandler?.selectedAudioDevice,
373 - availableDevices = audioSwitchHandler?.availableAudioDevices ?: emptyList() 392 + availableDevices = audioSwitchHandler?.availableAudioDevices ?: emptyList(),
374 ) 393 )
375 } 394 }
376 Surface( 395 Surface(
377 onClick = { viewModel.toggleSubscriptionPermissions() }, 396 onClick = { viewModel.toggleSubscriptionPermissions() },
378 modifier = Modifier 397 modifier = Modifier
379 .size(controlSize) 398 .size(controlSize)
380 - .padding(controlPadding) 399 + .padding(controlPadding),
381 ) { 400 ) {
382 val resource = 401 val resource =
383 if (permissionAllowed) R.drawable.account_cancel_outline else R.drawable.account_cancel 402 if (permissionAllowed) R.drawable.account_cancel_outline else R.drawable.account_cancel
@@ -393,7 +412,7 @@ class CallActivity : AppCompatActivity() { @@ -393,7 +412,7 @@ class CallActivity : AppCompatActivity() {
393 onClick = { showDebugDialog = true }, 412 onClick = { showDebugDialog = true },
394 modifier = Modifier 413 modifier = Modifier
395 .size(controlSize) 414 .size(controlSize)
396 - .padding(controlPadding) 415 + .padding(controlPadding),
397 ) { 416 ) {
398 val resource = R.drawable.dots_horizontal_circle_outline 417 val resource = R.drawable.dots_horizontal_circle_outline
399 Icon( 418 Icon(
@@ -427,7 +446,7 @@ class CallActivity : AppCompatActivity() { @@ -427,7 +446,7 @@ class CallActivity : AppCompatActivity() {
427 scope.launch { 446 scope.launch {
428 scaffoldState.snackbarHostState.showSnackbar(error?.toString() ?: "") 447 scaffoldState.snackbarHostState.showSnackbar(error?.toString() ?: "")
429 } 448 }
430 - } 449 + },
431 ) 450 )
432 }, 451 },
433 content = { innerPadding -> 452 content = { innerPadding ->
@@ -436,9 +455,9 @@ class CallActivity : AppCompatActivity() { @@ -436,9 +455,9 @@ class CallActivity : AppCompatActivity() {
436 modifier = Modifier 455 modifier = Modifier
437 .padding(innerPadding) 456 .padding(innerPadding)
438 .fillMaxSize() 457 .fillMaxSize()
439 - .wrapContentSize() 458 + .wrapContentSize(),
440 ) 459 )
441 - } 460 + },
442 ) 461 )
443 } 462 }
444 } 463 }
@@ -454,6 +473,6 @@ class CallActivity : AppCompatActivity() { @@ -454,6 +473,6 @@ class CallActivity : AppCompatActivity() {
454 val url: String, 473 val url: String,
455 val token: String, 474 val token: String,
456 val e2eeKey: String, 475 val e2eeKey: String,
457 - val e2eeOn: Boolean 476 + val e2eeOn: Boolean,
458 ) : Parcelable 477 ) : Parcelable
459 } 478 }
  1 +/*
  2 + * Copyright 2023 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.composesample 17 package io.livekit.android.composesample
2 18
3 import android.content.Intent 19 import android.content.Intent
@@ -38,6 +54,7 @@ import androidx.compose.ui.unit.dp @@ -38,6 +54,7 @@ import androidx.compose.ui.unit.dp
38 import com.google.accompanist.pager.ExperimentalPagerApi 54 import com.google.accompanist.pager.ExperimentalPagerApi
39 import io.livekit.android.composesample.ui.theme.AppTheme 55 import io.livekit.android.composesample.ui.theme.AppTheme
40 import io.livekit.android.sample.MainViewModel 56 import io.livekit.android.sample.MainViewModel
  57 +import io.livekit.android.sample.common.R
41 import io.livekit.android.sample.util.requestNeededPermissions 58 import io.livekit.android.sample.util.requestNeededPermissions
42 59
43 @ExperimentalPagerApi 60 @ExperimentalPagerApi
  1 +/*
  2 + * Copyright 2023 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.composesample 17 package io.livekit.android.composesample
2 18
3 import androidx.compose.foundation.background 19 import androidx.compose.foundation.background
@@ -20,6 +36,7 @@ import io.livekit.android.composesample.ui.theme.NoVideoBackground @@ -20,6 +36,7 @@ import io.livekit.android.composesample.ui.theme.NoVideoBackground
20 import io.livekit.android.room.Room 36 import io.livekit.android.room.Room
21 import io.livekit.android.room.participant.ConnectionQuality 37 import io.livekit.android.room.participant.ConnectionQuality
22 import io.livekit.android.room.participant.Participant 38 import io.livekit.android.room.participant.Participant
  39 +import io.livekit.android.sample.common.R
23 import io.livekit.android.util.flow 40 import io.livekit.android.util.flow
24 41
25 /** 42 /**
  1 +/*
  2 + * Copyright 2023 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.composesample 17 package io.livekit.android.composesample
2 18
3 import android.app.Application 19 import android.app.Application
@@ -9,5 +25,6 @@ class SampleApplication : Application() { @@ -9,5 +25,6 @@ class SampleApplication : Application() {
9 override fun onCreate() { 25 override fun onCreate() {
10 super.onCreate() 26 super.onCreate()
11 LiveKit.loggingLevel = LoggingLevel.VERBOSE 27 LiveKit.loggingLevel = LoggingLevel.VERBOSE
  28 + LiveKit.enableWebRTCLogging = true
12 } 29 }
13 } 30 }
  1 +/*
  2 + * Copyright 2023 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.composesample 17 package io.livekit.android.composesample
2 18
3 import androidx.compose.foundation.layout.Box 19 import androidx.compose.foundation.layout.Box
4 import androidx.compose.material.Icon 20 import androidx.compose.material.Icon
5 -import androidx.compose.runtime.* 21 +import androidx.compose.runtime.Composable
  22 +import androidx.compose.runtime.LaunchedEffect
  23 +import androidx.compose.runtime.collectAsState
  24 +import androidx.compose.runtime.getValue
  25 +import androidx.compose.runtime.mutableStateOf
  26 +import androidx.compose.runtime.remember
  27 +import androidx.compose.runtime.setValue
6 import androidx.compose.ui.Alignment 28 import androidx.compose.ui.Alignment
7 import androidx.compose.ui.Modifier 29 import androidx.compose.ui.Modifier
8 import androidx.compose.ui.graphics.Color 30 import androidx.compose.ui.graphics.Color
9 import androidx.compose.ui.res.painterResource 31 import androidx.compose.ui.res.painterResource
10 -import io.livekit.android.compose.VideoRenderer 32 +import io.livekit.android.composesample.ui.VideoRenderer
11 import io.livekit.android.room.Room 33 import io.livekit.android.room.Room
12 import io.livekit.android.room.participant.Participant 34 import io.livekit.android.room.participant.Participant
13 import io.livekit.android.room.track.CameraPosition 35 import io.livekit.android.room.track.CameraPosition
14 import io.livekit.android.room.track.LocalVideoTrack 36 import io.livekit.android.room.track.LocalVideoTrack
15 import io.livekit.android.room.track.Track 37 import io.livekit.android.room.track.Track
16 import io.livekit.android.room.track.VideoTrack 38 import io.livekit.android.room.track.VideoTrack
  39 +import io.livekit.android.sample.common.R
17 import io.livekit.android.util.flow 40 import io.livekit.android.util.flow
18 41
19 /** 42 /**
@@ -60,7 +83,7 @@ fun VideoItemTrackSelector( @@ -60,7 +83,7 @@ fun VideoItemTrackSelector(
60 room = room, 83 room = room,
61 videoTrack = videoTrack, 84 videoTrack = videoTrack,
62 mirror = room.localParticipant == participant && cameraFacingFront, 85 mirror = room.localParticipant == participant && cameraFacingFront,
63 - modifier = modifier 86 + modifier = modifier,
64 ) 87 )
65 } else { 88 } else {
66 Box(modifier = modifier) { 89 Box(modifier = modifier) {
@@ -68,7 +91,7 @@ fun VideoItemTrackSelector( @@ -68,7 +91,7 @@ fun VideoItemTrackSelector(
68 painter = painterResource(id = R.drawable.outline_videocam_off_24), 91 painter = painterResource(id = R.drawable.outline_videocam_off_24),
69 contentDescription = null, 92 contentDescription = null,
70 tint = Color.White, 93 tint = Color.White,
71 - modifier = Modifier.align(Alignment.Center) 94 + modifier = Modifier.align(Alignment.Center),
72 ) 95 )
73 } 96 }
74 } 97 }
  1 +/*
  2 + * Copyright 2023 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.composesample.ui
  18 +
  19 +import androidx.compose.ui.layout.LayoutCoordinates
  20 +import io.livekit.android.room.track.Track
  21 +import io.livekit.android.room.track.video.VideoSinkVisibility
  22 +
  23 +/**
  24 + *
  25 + */
  26 +class ComposeVisibility : VideoSinkVisibility() {
  27 + private var coordinates: LayoutCoordinates? = null
  28 +
  29 + private var lastVisible = isVisible()
  30 + private var lastSize = size()
  31 + override fun isVisible(): Boolean {
  32 + return (coordinates?.isAttached == true &&
  33 + coordinates?.size?.width != 0 &&
  34 + coordinates?.size?.height != 0)
  35 + }
  36 +
  37 + override fun size(): Track.Dimensions {
  38 + val width = coordinates?.size?.width ?: 0
  39 + val height = coordinates?.size?.height ?: 0
  40 + return Track.Dimensions(width, height)
  41 + }
  42 +
  43 + // Note, LayoutCoordinates are mutable and may be reused.
  44 + fun onGloballyPositioned(layoutCoordinates: LayoutCoordinates) {
  45 + coordinates = layoutCoordinates
  46 + val visible = isVisible()
  47 + val size = size()
  48 +
  49 + if (lastVisible != visible || lastSize != size) {
  50 + notifyChanged()
  51 + }
  52 +
  53 + lastVisible = visible
  54 + lastSize = size
  55 + }
  56 +
  57 + fun onDispose() {
  58 + if (coordinates == null) {
  59 + return
  60 + }
  61 + coordinates = null
  62 + notifyChanged()
  63 + }
  64 +}
@@ -14,17 +14,32 @@ @@ -14,17 +14,32 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -package io.livekit.android.compose 17 +package io.livekit.android.composesample.ui
18 18
19 -import androidx.compose.runtime.* 19 +import androidx.compose.foundation.background
  20 +import androidx.compose.foundation.layout.Box
  21 +import androidx.compose.runtime.Composable
  22 +import androidx.compose.runtime.DisposableEffect
  23 +import androidx.compose.runtime.currentCompositeKeyHash
  24 +import androidx.compose.runtime.getValue
  25 +import androidx.compose.runtime.mutableStateOf
  26 +import androidx.compose.runtime.remember
  27 +import androidx.compose.runtime.setValue
20 import androidx.compose.ui.Modifier 28 import androidx.compose.ui.Modifier
  29 +import androidx.compose.ui.graphics.Color
21 import androidx.compose.ui.layout.onGloballyPositioned 30 import androidx.compose.ui.layout.onGloballyPositioned
  31 +import androidx.compose.ui.platform.LocalView
22 import androidx.compose.ui.viewinterop.AndroidView 32 import androidx.compose.ui.viewinterop.AndroidView
23 import io.livekit.android.renderer.TextureViewRenderer 33 import io.livekit.android.renderer.TextureViewRenderer
24 import io.livekit.android.room.Room 34 import io.livekit.android.room.Room
25 import io.livekit.android.room.track.RemoteVideoTrack 35 import io.livekit.android.room.track.RemoteVideoTrack
26 import io.livekit.android.room.track.VideoTrack 36 import io.livekit.android.room.track.VideoTrack
27 -import io.livekit.android.room.track.video.ComposeVisibility 37 +import org.webrtc.RendererCommon
  38 +
  39 +enum class ScaleType {
  40 + FitInside,
  41 + Fill,
  42 +}
28 43
29 /** 44 /**
30 * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use 45 * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use
@@ -33,10 +48,21 @@ import io.livekit.android.room.track.video.ComposeVisibility @@ -33,10 +48,21 @@ import io.livekit.android.room.track.video.ComposeVisibility
33 @Composable 48 @Composable
34 fun VideoRenderer( 49 fun VideoRenderer(
35 room: Room, 50 room: Room,
36 - videoTrack: VideoTrack, 51 + videoTrack: VideoTrack?,
37 modifier: Modifier = Modifier, 52 modifier: Modifier = Modifier,
38 mirror: Boolean = false, 53 mirror: Boolean = false,
  54 + scaleType: ScaleType = ScaleType.Fill,
39 ) { 55 ) {
  56 + // Show a black box for preview.
  57 + if (LocalView.current.isInEditMode) {
  58 + Box(
  59 + modifier = Modifier
  60 + .background(Color.Black)
  61 + .then(modifier)
  62 + )
  63 + return
  64 + }
  65 +
40 val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() } 66 val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
41 var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) } 67 var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
42 var view: TextureViewRenderer? by remember { mutableStateOf(null) } 68 var view: TextureViewRenderer? by remember { mutableStateOf(null) }
@@ -46,7 +72,7 @@ fun VideoRenderer( @@ -46,7 +72,7 @@ fun VideoRenderer(
46 boundVideoTrack = null 72 boundVideoTrack = null
47 } 73 }
48 74
49 - fun setupVideoIfNeeded(videoTrack: VideoTrack, view: TextureViewRenderer) { 75 + fun setupVideoIfNeeded(videoTrack: VideoTrack?, view: TextureViewRenderer) {
50 if (boundVideoTrack == videoTrack) { 76 if (boundVideoTrack == videoTrack) {
51 return 77 return
52 } 78 }
@@ -54,10 +80,12 @@ fun VideoRenderer( @@ -54,10 +80,12 @@ fun VideoRenderer(
54 cleanupVideoTrack() 80 cleanupVideoTrack()
55 81
56 boundVideoTrack = videoTrack 82 boundVideoTrack = videoTrack
57 - if (videoTrack is RemoteVideoTrack) {  
58 - videoTrack.addRenderer(view, videoSinkVisibility)  
59 - } else {  
60 - videoTrack.addRenderer(view) 83 + if (videoTrack != null) {
  84 + if (videoTrack is RemoteVideoTrack) {
  85 + videoTrack.addRenderer(view, videoSinkVisibility)
  86 + } else {
  87 + videoTrack.addRenderer(view)
  88 + }
61 } 89 }
62 } 90 }
63 91
@@ -85,6 +113,15 @@ fun VideoRenderer( @@ -85,6 +113,15 @@ fun VideoRenderer(
85 room.initVideoRenderer(this) 113 room.initVideoRenderer(this)
86 setupVideoIfNeeded(videoTrack, this) 114 setupVideoIfNeeded(videoTrack, this)
87 115
  116 + when (scaleType) {
  117 + ScaleType.FitInside -> {
  118 + this.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
  119 + }
  120 +
  121 + ScaleType.Fill -> {
  122 + this.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
  123 + }
  124 + }
88 view = this 125 view = this
89 } 126 }
90 }, 127 },
@@ -4,12 +4,14 @@ plugins { @@ -4,12 +4,14 @@ plugins {
4 } 4 }
5 5
6 android { 6 android {
7 - compileSdk 32 7 + namespace 'io.livekit.android.sample.record'
  8 + compileSdk androidSdk.compileVersion
  9 +
8 10
9 defaultConfig { 11 defaultConfig {
10 applicationId "io.livekit.android.sample.record" 12 applicationId "io.livekit.android.sample.record"
11 - minSdk 21  
12 - targetSdk 32 13 + minSdk androidSdk.minVersion
  14 + targetSdk androidSdk.targetVersion
13 versionCode 1 15 versionCode 1
14 versionName "1.0" 16 versionName "1.0"
15 17
@@ -26,11 +28,11 @@ android { @@ -26,11 +28,11 @@ android {
26 } 28 }
27 } 29 }
28 compileOptions { 30 compileOptions {
29 - sourceCompatibility JavaVersion.VERSION_1_8  
30 - targetCompatibility JavaVersion.VERSION_1_8 31 + sourceCompatibility java_version
  32 + targetCompatibility java_version
31 } 33 }
32 kotlinOptions { 34 kotlinOptions {
33 - jvmTarget = '1.8' 35 + jvmTarget = java_version
34 } 36 }
35 buildFeatures { 37 buildFeatures {
36 compose true 38 compose true
@@ -5,8 +5,9 @@ plugins { @@ -5,8 +5,9 @@ plugins {
5 } 5 }
6 6
7 android { 7 android {
  8 + namespace "io.livekit.android.sample"
8 compileSdkVersion androidSdk.compileVersion 9 compileSdkVersion androidSdk.compileVersion
9 - buildToolsVersion "30.0.3" 10 +
10 defaultConfig { 11 defaultConfig {
11 applicationId "io.livekit.android" 12 applicationId "io.livekit.android"
12 minSdkVersion androidSdk.minVersion 13 minSdkVersion androidSdk.minVersion
  1 +/*
  2 + * Copyright 2023 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.sample 17 package io.livekit.android.sample
2 18
3 import android.app.Activity 19 import android.app.Activity
@@ -13,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity @@ -13,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity
13 import androidx.lifecycle.lifecycleScope 29 import androidx.lifecycle.lifecycleScope
14 import androidx.recyclerview.widget.LinearLayoutManager 30 import androidx.recyclerview.widget.LinearLayoutManager
15 import com.xwray.groupie.GroupieAdapter 31 import com.xwray.groupie.GroupieAdapter
  32 +import io.livekit.android.sample.common.R
16 import io.livekit.android.sample.databinding.CallActivityBinding 33 import io.livekit.android.sample.databinding.CallActivityBinding
17 import io.livekit.android.sample.dialog.showDebugMenuDialog 34 import io.livekit.android.sample.dialog.showDebugMenuDialog
18 import io.livekit.android.sample.dialog.showSelectAudioDeviceDialog 35 import io.livekit.android.sample.dialog.showSelectAudioDeviceDialog
@@ -29,7 +46,7 @@ class CallActivity : AppCompatActivity() { @@ -29,7 +46,7 @@ class CallActivity : AppCompatActivity() {
29 lateinit var binding: CallActivityBinding 46 lateinit var binding: CallActivityBinding
30 private val screenCaptureIntentLauncher = 47 private val screenCaptureIntentLauncher =
31 registerForActivityResult( 48 registerForActivityResult(
32 - ActivityResultContracts.StartActivityForResult() 49 + ActivityResultContracts.StartActivityForResult(),
33 ) { result -> 50 ) { result ->
34 val resultCode = result.resultCode 51 val resultCode = result.resultCode
35 val data = result.data 52 val data = result.data
@@ -83,7 +100,7 @@ class CallActivity : AppCompatActivity() { @@ -83,7 +100,7 @@ class CallActivity : AppCompatActivity() {
83 R.drawable.outline_videocam_24 100 R.drawable.outline_videocam_24
84 } else { 101 } else {
85 R.drawable.outline_videocam_off_24 102 R.drawable.outline_videocam_off_24
86 - } 103 + },
87 ) 104 )
88 binding.flipCamera.isEnabled = enabled 105 binding.flipCamera.isEnabled = enabled
89 } 106 }
@@ -94,7 +111,7 @@ class CallActivity : AppCompatActivity() { @@ -94,7 +111,7 @@ class CallActivity : AppCompatActivity() {
94 R.drawable.outline_mic_24 111 R.drawable.outline_mic_24
95 } else { 112 } else {
96 R.drawable.outline_mic_off_24 113 R.drawable.outline_mic_off_24
97 - } 114 + },
98 ) 115 )
99 } 116 }
100 117
@@ -112,7 +129,7 @@ class CallActivity : AppCompatActivity() { @@ -112,7 +129,7 @@ class CallActivity : AppCompatActivity() {
112 R.drawable.baseline_cast_connected_24 129 R.drawable.baseline_cast_connected_24
113 } else { 130 } else {
114 R.drawable.baseline_cast_24 131 R.drawable.baseline_cast_24
115 - } 132 + },
116 ) 133 )
117 } 134 }
118 135
@@ -4,7 +4,7 @@ pluginManagement { @@ -4,7 +4,7 @@ pluginManagement {
4 } 4 }
5 } 5 }
6 include ':sample-app', ':sample-app-compose', ':livekit-android-sdk' 6 include ':sample-app', ':sample-app-compose', ':livekit-android-sdk'
7 -rootProject.name='livekit-android' 7 +rootProject.name = 'livekit-android'
8 include ':sample-app-common' 8 include ':sample-app-common'
9 include ':livekit-lint' 9 include ':livekit-lint'
10 include ':video-encode-decode-test' 10 include ':video-encode-decode-test'
@@ -21,10 +21,11 @@ final apiKey = getApiKey() @@ -21,10 +21,11 @@ final apiKey = getApiKey()
21 final apiSecret = getApiSecret() 21 final apiSecret = getApiSecret()
22 22
23 android { 23 android {
  24 + namespace "io.livekit.android.videoencodedecode"
24 compileSdkVersion androidSdk.compileVersion 25 compileSdkVersion androidSdk.compileVersion
25 26
26 defaultConfig { 27 defaultConfig {
27 - applicationId "io.livekit.android.videoencodedecodetest" 28 + applicationId "io.livekit.android.videoencodedecode"
28 minSdkVersion androidSdk.minVersion 29 minSdkVersion androidSdk.minVersion
29 targetSdkVersion androidSdk.targetVersion 30 targetSdkVersion androidSdk.targetVersion
30 versionCode 1 31 versionCode 1
@@ -60,7 +61,8 @@ android { @@ -60,7 +61,8 @@ android {
60 freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn' 61 freeCompilerArgs += '-opt-in=kotlin.RequiresOptIn'
61 } 62 }
62 buildFeatures { 63 buildFeatures {
63 - compose true 64 + buildConfig = true
  65 + compose = true
64 } 66 }
65 composeOptions { 67 composeOptions {
66 kotlinCompilerExtensionVersion compose_compiler_version 68 kotlinCompilerExtensionVersion compose_compiler_version
  1 +/*
  2 + * Copyright 2023 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.Manifest 19 import android.Manifest
@@ -21,6 +37,7 @@ import androidx.compose.ui.unit.dp @@ -21,6 +37,7 @@ import androidx.compose.ui.unit.dp
21 import androidx.core.content.ContextCompat 37 import androidx.core.content.ContextCompat
22 import com.google.accompanist.pager.ExperimentalPagerApi 38 import com.google.accompanist.pager.ExperimentalPagerApi
23 import io.livekit.android.composesample.ui.theme.AppTheme 39 import io.livekit.android.composesample.ui.theme.AppTheme
  40 +import io.livekit.android.sample.common.R
24 41
25 @ExperimentalPagerApi 42 @ExperimentalPagerApi
26 class MainActivity : ComponentActivity() { 43 class MainActivity : ComponentActivity() {
  1 +/*
  2 + * Copyright 2023 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 androidx.compose.foundation.background 19 import androidx.compose.foundation.background
@@ -20,6 +36,7 @@ import io.livekit.android.composesample.ui.theme.NoVideoBackground @@ -20,6 +36,7 @@ import io.livekit.android.composesample.ui.theme.NoVideoBackground
20 import io.livekit.android.room.Room 36 import io.livekit.android.room.Room
21 import io.livekit.android.room.participant.ConnectionQuality 37 import io.livekit.android.room.participant.ConnectionQuality
22 import io.livekit.android.room.participant.Participant 38 import io.livekit.android.room.participant.Participant
  39 +import io.livekit.android.sample.common.R
23 import io.livekit.android.util.flow 40 import io.livekit.android.util.flow
24 41
25 /** 42 /**
  1 +/*
  2 + * Copyright 2023 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 androidx.compose.foundation.layout.Box 19 import androidx.compose.foundation.layout.Box
@@ -21,8 +37,8 @@ import io.livekit.android.room.participant.Participant @@ -21,8 +37,8 @@ import io.livekit.android.room.participant.Participant
21 import io.livekit.android.room.track.RemoteVideoTrack 37 import io.livekit.android.room.track.RemoteVideoTrack
22 import io.livekit.android.room.track.Track 38 import io.livekit.android.room.track.Track
23 import io.livekit.android.room.track.VideoTrack 39 import io.livekit.android.room.track.VideoTrack
24 -import io.livekit.android.room.track.video.ComposeVisibility  
25 import io.livekit.android.util.flow 40 import io.livekit.android.util.flow
  41 +import io.livekit.android.videoencodedecode.ui.ComposeVisibility
26 import kotlinx.coroutines.flow.* 42 import kotlinx.coroutines.flow.*
27 43
28 /** 44 /**
@@ -137,7 +153,7 @@ fun VideoItemTrackSelector( @@ -137,7 +153,7 @@ fun VideoItemTrackSelector(
137 } else { 153 } else {
138 Box(modifier = modifier) { 154 Box(modifier = modifier) {
139 Icon( 155 Icon(
140 - painter = painterResource(id = R.drawable.outline_videocam_off_24), 156 + painter = painterResource(id = io.livekit.android.sample.common.R.drawable.outline_videocam_off_24),
141 contentDescription = null, 157 contentDescription = null,
142 tint = Color.White, 158 tint = Color.White,
143 modifier = Modifier.align(Alignment.Center) 159 modifier = Modifier.align(Alignment.Center)
  1 +/*
  2 + * Copyright 2023 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
  18 +
  19 +import androidx.compose.ui.layout.LayoutCoordinates
  20 +import io.livekit.android.room.track.Track
  21 +import io.livekit.android.room.track.video.VideoSinkVisibility
  22 +
  23 +/**
  24 + *
  25 + */
  26 +class ComposeVisibility : VideoSinkVisibility() {
  27 + private var coordinates: LayoutCoordinates? = null
  28 +
  29 + private var lastVisible = isVisible()
  30 + private var lastSize = size()
  31 + override fun isVisible(): Boolean {
  32 + return (coordinates?.isAttached == true &&
  33 + coordinates?.size?.width != 0 &&
  34 + coordinates?.size?.height != 0)
  35 + }
  36 +
  37 + override fun size(): Track.Dimensions {
  38 + val width = coordinates?.size?.width ?: 0
  39 + val height = coordinates?.size?.height ?: 0
  40 + return Track.Dimensions(width, height)
  41 + }
  42 +
  43 + // Note, LayoutCoordinates are mutable and may be reused.
  44 + fun onGloballyPositioned(layoutCoordinates: LayoutCoordinates) {
  45 + coordinates = layoutCoordinates
  46 + val visible = isVisible()
  47 + val size = size()
  48 +
  49 + if (lastVisible != visible || lastSize != size) {
  50 + notifyChanged()
  51 + }
  52 +
  53 + lastVisible = visible
  54 + lastSize = size
  55 + }
  56 +
  57 + fun onDispose() {
  58 + if (coordinates == null) {
  59 + return
  60 + }
  61 + coordinates = null
  62 + notifyChanged()
  63 + }
  64 +}
  1 +/*
  2 + * Copyright 2023 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
  18 +
  19 +import androidx.compose.foundation.background
  20 +import androidx.compose.foundation.layout.Box
  21 +import androidx.compose.runtime.Composable
  22 +import androidx.compose.runtime.DisposableEffect
  23 +import androidx.compose.runtime.currentCompositeKeyHash
  24 +import androidx.compose.runtime.getValue
  25 +import androidx.compose.runtime.mutableStateOf
  26 +import androidx.compose.runtime.remember
  27 +import androidx.compose.runtime.setValue
  28 +import androidx.compose.ui.Modifier
  29 +import androidx.compose.ui.graphics.Color
  30 +import androidx.compose.ui.layout.onGloballyPositioned
  31 +import androidx.compose.ui.platform.LocalView
  32 +import androidx.compose.ui.viewinterop.AndroidView
  33 +import io.livekit.android.renderer.TextureViewRenderer
  34 +import io.livekit.android.room.Room
  35 +import io.livekit.android.room.track.RemoteVideoTrack
  36 +import io.livekit.android.room.track.VideoTrack
  37 +import org.webrtc.RendererCommon
  38 +
  39 +enum class ScaleType {
  40 + FitInside,
  41 + Fill,
  42 +}
  43 +
  44 +/**
  45 + * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use
  46 + * [TextureViewRenderer].
  47 + */
  48 +@Composable
  49 +fun VideoRenderer(
  50 + room: Room,
  51 + videoTrack: VideoTrack?,
  52 + modifier: Modifier = Modifier,
  53 + mirror: Boolean = false,
  54 + scaleType: ScaleType = ScaleType.Fill,
  55 +) {
  56 + // Show a black box for preview.
  57 + if (LocalView.current.isInEditMode) {
  58 + Box(
  59 + modifier = Modifier
  60 + .background(Color.Black)
  61 + .then(modifier)
  62 + )
  63 + return
  64 + }
  65 +
  66 + val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
  67 + var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
  68 + var view: TextureViewRenderer? by remember { mutableStateOf(null) }
  69 +
  70 + fun cleanupVideoTrack() {
  71 + view?.let { boundVideoTrack?.removeRenderer(it) }
  72 + boundVideoTrack = null
  73 + }
  74 +
  75 + fun setupVideoIfNeeded(videoTrack: VideoTrack?, view: TextureViewRenderer) {
  76 + if (boundVideoTrack == videoTrack) {
  77 + return
  78 + }
  79 +
  80 + cleanupVideoTrack()
  81 +
  82 + boundVideoTrack = videoTrack
  83 + if (videoTrack != null) {
  84 + if (videoTrack is RemoteVideoTrack) {
  85 + videoTrack.addRenderer(view, videoSinkVisibility)
  86 + } else {
  87 + videoTrack.addRenderer(view)
  88 + }
  89 + }
  90 + }
  91 +
  92 + DisposableEffect(view, mirror) {
  93 + view?.setMirror(mirror)
  94 + onDispose { }
  95 + }
  96 +
  97 + DisposableEffect(room, videoTrack) {
  98 + onDispose {
  99 + videoSinkVisibility.onDispose()
  100 + cleanupVideoTrack()
  101 + }
  102 + }
  103 +
  104 + DisposableEffect(currentCompositeKeyHash.toString()) {
  105 + onDispose {
  106 + view?.release()
  107 + }
  108 + }
  109 +
  110 + AndroidView(
  111 + factory = { context ->
  112 + TextureViewRenderer(context).apply {
  113 + room.initVideoRenderer(this)
  114 + setupVideoIfNeeded(videoTrack, this)
  115 +
  116 + when (scaleType) {
  117 + ScaleType.FitInside -> {
  118 + this.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT)
  119 + }
  120 +
  121 + ScaleType.Fill -> {
  122 + this.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL)
  123 + }
  124 + }
  125 + view = this
  126 + }
  127 + },
  128 + update = { v -> setupVideoIfNeeded(videoTrack, v) },
  129 + modifier = modifier
  130 + .onGloballyPositioned { videoSinkVisibility.onGloballyPositioned(it) },
  131 + )
  132 +}