diff --git a/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt b/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt index 19185375d..2ade9a615 100644 --- a/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt +++ b/app/src/main/java/live/hms/app2/ui/home/HomeFragment.kt @@ -11,6 +11,7 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.widget.EditText import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts @@ -26,7 +27,6 @@ import live.hms.roomkit.ui.settings.SettingsMode import live.hms.roomkit.ui.settings.SettingsStore import live.hms.roomkit.util.EmailUtils import live.hms.app2.util.* -import live.hms.roomkit.util.NameUtils.isValidUserName import live.hms.roomkit.ui.HMSPrebuiltOptions import live.hms.roomkit.ui.HMSRoomKit import live.hms.roomkit.ui.meeting.* @@ -249,6 +249,15 @@ class HomeFragment : Fragment() { } } + fun isValidUserName(editText: EditText): Boolean { + val username = editText.text.toString() + if (username.isEmpty()) { + return false + } + return true + } + + private fun enableButton() { binding.btnJoinNow.isEnabled = true binding.btnJoinNow.background = diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 000000000..9cfde5b76 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,121 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-kapt' + id 'maven-publish' + id 'kotlin-parcelize' + id 'signing' + id "org.jetbrains.dokka" version "1.5.0" +} + +def getVersionName = { -> + return "$HMS_ROOM_KIT_VERSION" +} + +android { + namespace 'live.hms.common' + compileSdk 33 + + defaultConfig { + minSdk 21 + targetSdk 33 + versionCode 1 + versionName getVersionName() + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + buildFeatures { + viewBinding true + dataBinding { + enabled true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + publishing { + singleVariant('release') { + withSourcesJar() + } + } + +} + +dependencies { + implementation 'androidx.core:core:1.10.1' + def hmsVersion = "2.7.7-de" + // To add dependencies of specific module + implementation "live.100ms:android-sdk:$hmsVersion" + implementation "live.100ms:video-view:$hmsVersion" + implementation 'com.google.code.gson:gson:2.8.6' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} + + +task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { + archiveFileName = "javadoc.jar" + from "build/dokka/javadoc" + archiveClassifier.set("javadoc") +} + +afterEvaluate { + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + from components.release + pom { + // Only sign the artefacts if this is a maven central build. + // This would only halt jitpack builds and/or make our signing keys public. + if(rootProject.properties["ossrhUsername"]) { + artifact javadocJar + signing { + sign publishing.publications + } + } + name = "100ms.live Android Room Kit" + description = "Room Kit that simplifies setting up videoconferencing in your own app. See more at https://www.100ms.live/docs/android/v2/guides/quickstart" + url = "100ms.live" + licenses { + license { + name = 'MIT License' + url = 'http://www.opensource.org/licenses/mit-license.php' + } + } + developers { + developer { + id = '1' + name = '100ms' + email = 'support@100ms.live' + } + } + scm { + connection = 'SCM is private' + developerConnection = 'SCM is private' + url = 'SCM is private' + } + } + groupId = "live.100ms" + artifactId = 'common' + version = getVersionName() + } + } + } +} \ No newline at end of file diff --git a/common/consumer-rules.pro b/common/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/common/proguard-rules.pro b/common/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/src/androidTest/java/live/hms/common/ExampleInstrumentedTest.kt b/common/src/androidTest/java/live/hms/common/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..722069bd9 --- /dev/null +++ b/common/src/androidTest/java/live/hms/common/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package live.hms.common + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("live.hms.common.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/common/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/util/NameUtils.kt b/common/src/main/java/live/hms/common/util/NameUtils.kt similarity index 97% rename from room-kit/src/main/java/live/hms/roomkit/util/NameUtils.kt rename to common/src/main/java/live/hms/common/util/NameUtils.kt index 7e7dab8d0..4510f14b3 100644 --- a/room-kit/src/main/java/live/hms/roomkit/util/NameUtils.kt +++ b/common/src/main/java/live/hms/common/util/NameUtils.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.util +package live.hms.common.util import android.widget.EditText import java.util.* diff --git a/room-kit/src/main/java/live/hms/roomkit/util/ViewExt.kt b/common/src/main/java/live/hms/common/util/ViewExt.kt similarity index 93% rename from room-kit/src/main/java/live/hms/roomkit/util/ViewExt.kt rename to common/src/main/java/live/hms/common/util/ViewExt.kt index d68430458..f8e928294 100644 --- a/room-kit/src/main/java/live/hms/roomkit/util/ViewExt.kt +++ b/common/src/main/java/live/hms/common/util/ViewExt.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.util +package live.hms.common.util import android.app.Activity import android.app.AlertDialog @@ -12,8 +12,7 @@ import android.view.* import android.view.accessibility.AccessibilityManager import androidx.core.content.FileProvider import androidx.core.view.GestureDetectorCompat -import live.hms.roomkit.R -import live.hms.roomkit.helpers.OnSingleClickListener +import live.hms.common.util.helpers.OnSingleClickListener import live.hms.video.media.capturers.camera.CameraControl import live.hms.video.media.settings.HMSSimulcastLayerDefinition import live.hms.video.media.tracks.HMSLocalVideoTrack @@ -80,7 +79,7 @@ fun Bitmap.saveCaptureToLocalCache(context: Context) : Uri? { val imagePath = File(context.cacheDir, "images") val newFile = File(imagePath, "image.png") - return FileProvider.getUriForFile(context, "live.hms.roomkit.provider", newFile) + return null } @@ -98,9 +97,6 @@ fun Activity.openShareIntent(uri: Uri) { startActivity(Intent.createChooser(shareIntent, "Choose an app")) } -fun HMSVideoTrack?.isValid(): Boolean { - return !(this == null || this.isMute || this.isDegraded) -} fun Context.showTileListDialog( @@ -139,6 +135,8 @@ fun SurfaceViewRenderer.onBitMap(onBitmap: (Bitmap?) -> Unit, scale : Float = 1. }, scale) } + + fun Context.showSimulcastDialog(hmsVideoTrack: HMSRemoteVideoTrack?) { if (hmsVideoTrack == null) return @@ -196,17 +194,7 @@ fun Context.showMirrorOptions(surfaceViewRenderer: SurfaceViewRenderer?) { } -fun SurfaceViewRenderer.setInit() { - setTag(R.id.IS_INT,true) -} -fun SurfaceViewRenderer.setRelease() { - setTag(R.id.IS_INT,false) -} - -fun SurfaceViewRenderer.isInit() : Boolean { - return (getTag(R.id.IS_INT) as? Boolean) == true -} fun HMSVideoTrack?.switchCamera() { (this as? HMSLocalVideoTrack)?.switchCamera() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/CustomPeerMetadata.kt b/common/src/main/java/live/hms/common/util/helpers/CustomPeerMetadata.kt similarity index 97% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/CustomPeerMetadata.kt rename to common/src/main/java/live/hms/common/util/helpers/CustomPeerMetadata.kt index 2e77c88f1..709e5ab2c 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/CustomPeerMetadata.kt +++ b/common/src/main/java/live/hms/common/util/helpers/CustomPeerMetadata.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.ui.meeting +package live.hms.common.util.helpers import com.google.gson.Gson import com.google.gson.JsonSyntaxException diff --git a/room-kit/src/main/java/live/hms/roomkit/helpers/NetworkQualityHelper.kt b/common/src/main/java/live/hms/common/util/helpers/NetworkQualityHelper.kt similarity index 93% rename from room-kit/src/main/java/live/hms/roomkit/helpers/NetworkQualityHelper.kt rename to common/src/main/java/live/hms/common/util/helpers/NetworkQualityHelper.kt index b6753fdb0..460b2cced 100644 --- a/room-kit/src/main/java/live/hms/roomkit/helpers/NetworkQualityHelper.kt +++ b/common/src/main/java/live/hms/common/util/helpers/NetworkQualityHelper.kt @@ -1,9 +1,9 @@ -package live.hms.roomkit.helpers +package live.hms.common.util.helpers import android.content.Context import android.graphics.drawable.Drawable import androidx.core.content.ContextCompat -import live.hms.roomkit.R +import live.hms.common.R class NetworkQualityHelper { diff --git a/room-kit/src/main/java/live/hms/roomkit/helpers/OnSingleClickListener.kt b/common/src/main/java/live/hms/common/util/helpers/OnSingleClickListener.kt similarity index 96% rename from room-kit/src/main/java/live/hms/roomkit/helpers/OnSingleClickListener.kt rename to common/src/main/java/live/hms/common/util/helpers/OnSingleClickListener.kt index f99c862ca..5c49cb91c 100644 --- a/room-kit/src/main/java/live/hms/roomkit/helpers/OnSingleClickListener.kt +++ b/common/src/main/java/live/hms/common/util/helpers/OnSingleClickListener.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.helpers +package live.hms.common.util.helpers import android.view.View diff --git a/room-kit/src/main/res/drawable/avd_mic_off_to_on.xml b/common/src/main/res/drawable/avd_mic_off_to_on.xml similarity index 100% rename from room-kit/src/main/res/drawable/avd_mic_off_to_on.xml rename to common/src/main/res/drawable/avd_mic_off_to_on.xml diff --git a/room-kit/src/main/res/drawable/avd_mic_on_to_off.xml b/common/src/main/res/drawable/avd_mic_on_to_off.xml similarity index 100% rename from room-kit/src/main/res/drawable/avd_mic_on_to_off.xml rename to common/src/main/res/drawable/avd_mic_on_to_off.xml diff --git a/room-kit/src/main/res/drawable/avd_video_off_to_on.xml b/common/src/main/res/drawable/avd_video_off_to_on.xml similarity index 100% rename from room-kit/src/main/res/drawable/avd_video_off_to_on.xml rename to common/src/main/res/drawable/avd_video_off_to_on.xml diff --git a/room-kit/src/main/res/drawable/avd_video_on_to_off.xml b/common/src/main/res/drawable/avd_video_on_to_off.xml similarity index 100% rename from room-kit/src/main/res/drawable/avd_video_on_to_off.xml rename to common/src/main/res/drawable/avd_video_on_to_off.xml diff --git a/room-kit/src/main/res/drawable/circle_secondary_24.xml b/common/src/main/res/drawable/circle_secondary_24.xml similarity index 100% rename from room-kit/src/main/res/drawable/circle_secondary_24.xml rename to common/src/main/res/drawable/circle_secondary_24.xml diff --git a/room-kit/src/main/res/drawable/circle_secondary_80.xml b/common/src/main/res/drawable/circle_secondary_80.xml similarity index 100% rename from room-kit/src/main/res/drawable/circle_secondary_80.xml rename to common/src/main/res/drawable/circle_secondary_80.xml diff --git a/room-kit/src/main/res/drawable/dot_default_4.xml b/common/src/main/res/drawable/dot_default_4.xml similarity index 100% rename from room-kit/src/main/res/drawable/dot_default_4.xml rename to common/src/main/res/drawable/dot_default_4.xml diff --git a/app/src/main/res/drawable/dot_selected_4.xml b/common/src/main/res/drawable/dot_selected_4.xml similarity index 84% rename from app/src/main/res/drawable/dot_selected_4.xml rename to common/src/main/res/drawable/dot_selected_4.xml index aedc1cbaa..1008c36f1 100644 --- a/app/src/main/res/drawable/dot_selected_4.xml +++ b/common/src/main/res/drawable/dot_selected_4.xml @@ -6,7 +6,7 @@ android:shape="ring" android:thickness="4dp" android:useLevel="false"> - + diff --git a/room-kit/src/main/res/drawable/dot_selector_4.xml b/common/src/main/res/drawable/dot_selector_4.xml similarity index 100% rename from room-kit/src/main/res/drawable/dot_selector_4.xml rename to common/src/main/res/drawable/dot_selector_4.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_0.xml b/common/src/main/res/drawable/ic_baseline_wifi_0.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_0.xml rename to common/src/main/res/drawable/ic_baseline_wifi_0.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_1.xml b/common/src/main/res/drawable/ic_baseline_wifi_1.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_1.xml rename to common/src/main/res/drawable/ic_baseline_wifi_1.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_2.xml b/common/src/main/res/drawable/ic_baseline_wifi_2.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_2.xml rename to common/src/main/res/drawable/ic_baseline_wifi_2.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_3.xml b/common/src/main/res/drawable/ic_baseline_wifi_3.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_3.xml rename to common/src/main/res/drawable/ic_baseline_wifi_3.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_4.xml b/common/src/main/res/drawable/ic_baseline_wifi_4.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_4.xml rename to common/src/main/res/drawable/ic_baseline_wifi_4.xml diff --git a/room-kit/src/main/res/drawable/ic_baseline_wifi_5.xml b/common/src/main/res/drawable/ic_baseline_wifi_5.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_baseline_wifi_5.xml rename to common/src/main/res/drawable/ic_baseline_wifi_5.xml diff --git a/room-kit/src/main/res/drawable/ic_brb.xml b/common/src/main/res/drawable/ic_brb.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_brb.xml rename to common/src/main/res/drawable/ic_brb.xml diff --git a/room-kit/src/main/res/drawable/ic_degraded.xml b/common/src/main/res/drawable/ic_degraded.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_degraded.xml rename to common/src/main/res/drawable/ic_degraded.xml diff --git a/room-kit/src/main/res/drawable/ic_hand_raise.xml b/common/src/main/res/drawable/ic_hand_raise.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_hand_raise.xml rename to common/src/main/res/drawable/ic_hand_raise.xml diff --git a/room-kit/src/main/res/drawable/ic_mic_mute.xml b/common/src/main/res/drawable/ic_mic_mute.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_mic_mute.xml rename to common/src/main/res/drawable/ic_mic_mute.xml diff --git a/room-kit/src/main/res/drawable/ic_mobile_screen_share_24.xml b/common/src/main/res/drawable/ic_mobile_screen_share_24.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_mobile_screen_share_24.xml rename to common/src/main/res/drawable/ic_mobile_screen_share_24.xml diff --git a/room-kit/src/main/res/drawable/ic_share_screen.xml b/common/src/main/res/drawable/ic_share_screen.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_share_screen.xml rename to common/src/main/res/drawable/ic_share_screen.xml diff --git a/room-kit/src/main/res/drawable/ic_signal_medium.xml b/common/src/main/res/drawable/ic_signal_medium.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_signal_medium.xml rename to common/src/main/res/drawable/ic_signal_medium.xml diff --git a/room-kit/src/main/res/drawable/ic_signal_strong.xml b/common/src/main/res/drawable/ic_signal_strong.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_signal_strong.xml rename to common/src/main/res/drawable/ic_signal_strong.xml diff --git a/room-kit/src/main/res/drawable/ic_signal_terrible.xml b/common/src/main/res/drawable/ic_signal_terrible.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_signal_terrible.xml rename to common/src/main/res/drawable/ic_signal_terrible.xml diff --git a/room-kit/src/main/res/drawable/ic_signal_weak.xml b/common/src/main/res/drawable/ic_signal_weak.xml similarity index 100% rename from room-kit/src/main/res/drawable/ic_signal_weak.xml rename to common/src/main/res/drawable/ic_signal_weak.xml diff --git a/room-kit/src/main/res/drawable/icon_maximised.xml b/common/src/main/res/drawable/icon_maximised.xml similarity index 100% rename from room-kit/src/main/res/drawable/icon_maximised.xml rename to common/src/main/res/drawable/icon_maximised.xml diff --git a/room-kit/src/main/res/font/clan_pro_medium.ttf b/common/src/main/res/font/clan_pro_medium.ttf similarity index 100% rename from room-kit/src/main/res/font/clan_pro_medium.ttf rename to common/src/main/res/font/clan_pro_medium.ttf diff --git a/room-kit/src/main/res/font/clan_pro_news.TTF b/common/src/main/res/font/clan_pro_news.TTF similarity index 100% rename from room-kit/src/main/res/font/clan_pro_news.TTF rename to common/src/main/res/font/clan_pro_news.TTF diff --git a/room-kit/src/main/res/font/inter_bold.ttf b/common/src/main/res/font/inter_bold.ttf similarity index 100% rename from room-kit/src/main/res/font/inter_bold.ttf rename to common/src/main/res/font/inter_bold.ttf diff --git a/room-kit/src/main/res/font/inter_extrabold.ttf b/common/src/main/res/font/inter_extrabold.ttf similarity index 100% rename from room-kit/src/main/res/font/inter_extrabold.ttf rename to common/src/main/res/font/inter_extrabold.ttf diff --git a/room-kit/src/main/res/font/inter_medium.ttf b/common/src/main/res/font/inter_medium.ttf similarity index 100% rename from room-kit/src/main/res/font/inter_medium.ttf rename to common/src/main/res/font/inter_medium.ttf diff --git a/room-kit/src/main/res/font/inter_regular.ttf b/common/src/main/res/font/inter_regular.ttf similarity index 100% rename from room-kit/src/main/res/font/inter_regular.ttf rename to common/src/main/res/font/inter_regular.ttf diff --git a/room-kit/src/main/res/font/inter_semibold.ttf b/common/src/main/res/font/inter_semibold.ttf similarity index 100% rename from room-kit/src/main/res/font/inter_semibold.ttf rename to common/src/main/res/font/inter_semibold.ttf diff --git a/room-kit/src/main/res/layout/fragment_video_grid_page.xml b/common/src/main/res/layout/fragment_video_grid_page.xml similarity index 82% rename from room-kit/src/main/res/layout/fragment_video_grid_page.xml rename to common/src/main/res/layout/fragment_video_grid_page.xml index fd0a34dd3..983388387 100644 --- a/room-kit/src/main/res/layout/fragment_video_grid_page.xml +++ b/common/src/main/res/layout/fragment_video_grid_page.xml @@ -5,4 +5,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:animateLayoutChanges="false" - tools:context=".ui.meeting.videogrid.VideoGridPageFragment" /> + /> diff --git a/room-kit/src/main/res/values/dimens.xml b/common/src/main/res/values/dimens.xml similarity index 86% rename from room-kit/src/main/res/values/dimens.xml rename to common/src/main/res/values/dimens.xml index e4cd7e0e1..542fe2b29 100644 --- a/room-kit/src/main/res/values/dimens.xml +++ b/common/src/main/res/values/dimens.xml @@ -8,6 +8,8 @@ 186dp 16dp 8dp + 1dp + 4dp 4dp 32dp \ No newline at end of file diff --git a/common/src/test/java/live/hms/common/ExampleUnitTest.kt b/common/src/test/java/live/hms/common/ExampleUnitTest.kt new file mode 100644 index 000000000..74f6a247f --- /dev/null +++ b/common/src/test/java/live/hms/common/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package live.hms.common + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/room-kit/build.gradle b/room-kit/build.gradle index 2baa2dc73..8ea98dd79 100644 --- a/room-kit/build.gradle +++ b/room-kit/build.gradle @@ -74,7 +74,10 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.percentlayout:percentlayout:1.0.0' - def hmsVersion = "2.7.2" + implementation "live.100ms:common:0.0.6" + implementation "live.100ms:videogrid:0.0.6" + + def hmsVersion = "2.7.7-de" // To add dependencies of specific module implementation "live.100ms:android-sdk:$hmsVersion" implementation "live.100ms:video-view:$hmsVersion" diff --git a/room-kit/src/main/java/live/hms/roomkit/ViewExt.kt b/room-kit/src/main/java/live/hms/roomkit/ViewExt.kt index 5fd1b417c..24faf7856 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ViewExt.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ViewExt.kt @@ -19,7 +19,7 @@ import androidx.core.content.FileProvider import androidx.core.view.GestureDetectorCompat import androidx.fragment.app.Fragment import live.hms.roomkit.R -import live.hms.roomkit.helpers.OnSingleClickListener +import live.hms.common.util.helpers.OnSingleClickListener import live.hms.video.media.capturers.camera.CameraControl import live.hms.video.media.settings.HMSSimulcastLayerDefinition import live.hms.video.media.tracks.HMSLocalVideoTrack @@ -270,72 +270,3 @@ fun SurfaceViewRenderer.isInit() : Boolean { } -fun HMSVideoView.setCameraGestureListener(track : HMSVideoTrack?,onImageCapture : (Uri)-> Unit, onLongPress: () -> Unit) { - - val cameraControl: CameraControl = (track as? HMSLocalVideoTrack)?.getCameraControl() ?: return - var lastZoom = cameraControl.getMinZoom() - - val gestureDetector = GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() { - - override fun onDown(e: MotionEvent) = true - override fun onSingleTapUp(event: MotionEvent): Boolean { - if (cameraControl.isTapToFocusSupported()) - cameraControl.setTapToFocusAt( - event.x, - event.y, - viewWidth = width, - viewHeight = height - ) - return true - } - - override fun onDoubleTap(e: MotionEvent): Boolean { - - val cachePath = File(context.cacheDir, "images") - cachePath.mkdirs() - val imageSavePath = File(cachePath, "image.jpeg") - - cameraControl.captureImageAtMaxSupportedResolution(imageSavePath) { it -> - - val fileSaveUri = FileProvider.getUriForFile( - context, - "live.hms.roomkit.provider", - imageSavePath - ) - - onImageCapture.invoke(fileSaveUri) - } - return true - } - - override fun onLongPress(e: MotionEvent) { - onLongPress.invoke() - } - - - - }) - - val scaleGestureDetector = ScaleGestureDetector( - context, - object : ScaleGestureDetector.SimpleOnScaleGestureListener() { - override fun onScale(detector: ScaleGestureDetector): Boolean { - if (cameraControl.isZoomSupported()) { - lastZoom *= detector.scaleFactor - cameraControl.setZoom(lastZoom) - return true - } - return false - } - }) - - this.setOnTouchListener { _, event -> - var didConsume = scaleGestureDetector.onTouchEvent(event) - if (!scaleGestureDetector.isInProgress) { - didConsume = gestureDetector.onTouchEvent(event) - } - didConsume - } - - -} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ChangeNameDialogFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ChangeNameDialogFragment.kt index 26ec79fb7..9d7f5a176 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ChangeNameDialogFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ChangeNameDialogFragment.kt @@ -9,7 +9,7 @@ import android.widget.EditText import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels import live.hms.roomkit.R -import live.hms.roomkit.util.NameUtils.isValidUserName +import live.hms.common.util.NameUtils.isValidUserName class ChangeNameDialogFragment : DialogFragment() { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/HlsStreamingToggleBottomSheet.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/HlsStreamingToggleBottomSheet.kt index 8bf909f97..45658da5f 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/HlsStreamingToggleBottomSheet.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/HlsStreamingToggleBottomSheet.kt @@ -9,7 +9,7 @@ import androidx.fragment.app.activityViewModels import com.google.android.material.bottomsheet.BottomSheetDialogFragment import live.hms.roomkit.databinding.HlsBottomSheetDialogBinding import live.hms.roomkit.ui.meeting.participants.meetingToHlsUrl -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener import live.hms.roomkit.util.viewLifecycle import live.hms.video.sdk.models.HMSHlsRecordingConfig diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt index f690adf72..558ef9637 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingActivity.kt @@ -1,7 +1,6 @@ package live.hms.roomkit.ui.meeting import android.Manifest.permission.BLUETOOTH_CONNECT -import android.content.pm.PackageManager import android.os.Bundle import android.view.View import android.view.WindowManager @@ -10,10 +9,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat -import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat -import androidx.core.content.ContextCompat -import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment import kotlinx.coroutines.launch @@ -21,6 +17,8 @@ import live.hms.roomkit.R import live.hms.roomkit.animation.RootViewDeferringInsetsCallback import live.hms.roomkit.databinding.ActivityMeetingBinding import live.hms.roomkit.ui.HMSPrebuiltOptions +import live.hms.videogrid.GridViewModel +import live.hms.videogrid.GridViewModelFactory import live.hms.roomkit.ui.settings.SettingsStore import live.hms.roomkit.util.ROOM_CODE import live.hms.roomkit.util.ROOM_PREBUILT @@ -43,6 +41,12 @@ class MeetingActivity : AppCompatActivity() { ) } + private val gridViewModel : live.hms.videogrid.GridViewModel by viewModels { + live.hms.videogrid.GridViewModelFactory( + application + ) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = ActivityMeetingBinding.inflate(layoutInflater) diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt index 9c8da0460..607e3d454 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingFragment.kt @@ -59,7 +59,6 @@ import live.hms.roomkit.ui.meeting.broadcastreceiver.PipUtils.muteTogglePipEvent import live.hms.roomkit.ui.meeting.chat.ChatAdapter import live.hms.roomkit.ui.meeting.chat.ChatUseCase import live.hms.roomkit.ui.meeting.chat.ChatViewModel -import live.hms.roomkit.ui.meeting.commons.VideoGridBaseFragment import live.hms.roomkit.ui.meeting.participants.RtmpRecordBottomSheet import live.hms.roomkit.ui.meeting.pinnedvideo.PinnedVideoFragment import live.hms.roomkit.ui.meeting.videogrid.VideoGridFragment @@ -706,8 +705,8 @@ class MeetingFragment : Fragment() { if (settings.showReconnectingProgressBars) { updateProgressBarUI(state.heading, state.message) showProgressBar() - if (currentFragment is VideoGridBaseFragment) - (currentFragment as VideoGridBaseFragment).unbindViews() + if (currentFragment is live.hms.videogrid.VideoGridBaseFragment) + (currentFragment as live.hms.videogrid.VideoGridBaseFragment).unbindViews() } } @@ -733,8 +732,8 @@ class MeetingFragment : Fragment() { } is MeetingState.Reconnected -> { hideProgressBar() - if (currentFragment is VideoGridBaseFragment) - (currentFragment as VideoGridBaseFragment).bindViews() + if (currentFragment is live.hms.videogrid.VideoGridBaseFragment) + (currentFragment as live.hms.videogrid.VideoGridBaseFragment).bindViews() isMeetingOngoing = true } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt index 141f4ed1e..ee6809317 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/MeetingViewModel.kt @@ -13,8 +13,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.HMSPrebuiltOptions -import live.hms.roomkit.ui.meeting.activespeaker.ActiveSpeakerHandler import live.hms.roomkit.ui.meeting.chat.ChatMessage import live.hms.roomkit.ui.meeting.chat.Recipient import live.hms.roomkit.ui.polls.PollCreationInfo @@ -335,62 +335,9 @@ class MeetingViewModel( // Live data containing the current Speaker in the meeting val speakers = MutableLiveData>() - private val activeSpeakerHandler = ActiveSpeakerHandler(false) { _tracks } val updateRowAndColumnSpanForVideoPeerGrid = MutableLiveData>() - val speakerUpdateLiveData = object : ActiveSpeakerLiveData() { - private val speakerH = ActiveSpeakerHandler(true,settings.videoGridRows* settings.videoGridColumns - ) { _tracks } - - override fun addSpeakerSource() { - addSource(speakers) { speakers : Array -> - - val excludeLocalTrackIfRemotePeerIsPreset : Array = - speakers.filter { it.peer?.isLocal == false }.toTypedArray() - - val result = speakerH.speakerUpdate(excludeLocalTrackIfRemotePeerIsPreset) - setValue(result.first) - } - } - - override fun removeSpeakerSource() { - removeSource(speakers) - } - - //TODO can't be null - fun refreshSpeaker() { - // speakers.postValue(speakers.value) - } - - override fun updateMaxActiveSpeaker(rowCount: Int, columnCount: Int) { - speakerH.updateMaxActiveSpeaker(rowCount*columnCount) - refreshSpeaker() - } - init { - addSpeakerSource() - - // Add all tracks as they come in. - addSource(tracks) { meetTracks: List -> - //if remote peer and local peer is present inset mode - val excludeLocalTrackIfRemotePeerIsPreset = if (meetTracks.size > 1) { - meetTracks.filter { !it.isLocal }.toList() - } else - meetTracks - - val result = speakerH.trackUpdateTrigger(excludeLocalTrackIfRemotePeerIsPreset) - setValue(result) - } - - } - - - - } - - val activeSpeakers: LiveData, Array>> = - speakers.map(activeSpeakerHandler::speakerUpdate) - val activeSpeakersUpdatedTracks = _liveDataTracks.map(activeSpeakerHandler::trackUpdateTrigger) // We need all the active speakers, but the very first time it should be filled. // with all the tracks. diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PreviewFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PreviewFragment.kt index 800b939c7..fdcbda618 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PreviewFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/PreviewFragment.kt @@ -4,7 +4,6 @@ import android.content.Context import android.os.Bundle import android.util.Log import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ImageView @@ -22,12 +21,17 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import com.bumptech.glide.Glide +import live.hms.common.util.NameUtils +import live.hms.common.util.openShareIntent +import live.hms.common.util.setCameraGestureListener +import live.hms.common.util.setOnSingleClickListener +import live.hms.common.util.switchCamera import live.hms.roomkit.R import live.hms.roomkit.animation.ControlFocusInsetsAnimationCallback import live.hms.roomkit.animation.TranslateDeferringInsetsAnimationCallback import live.hms.roomkit.databinding.FragmentPreviewBinding import live.hms.roomkit.drawableStart -import live.hms.roomkit.helpers.NetworkQualityHelper +import live.hms.common.util.helpers.NetworkQualityHelper import live.hms.roomkit.hideKeyboard import live.hms.roomkit.setDrawables import live.hms.roomkit.ui.meeting.participants.ParticipantsAdapter @@ -37,16 +41,13 @@ import live.hms.roomkit.ui.theme.* import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.util.* import live.hms.video.audio.HMSAudioManager -import live.hms.video.error.HMSException import live.hms.video.media.tracks.HMSLocalAudioTrack import live.hms.video.media.tracks.HMSLocalVideoTrack import live.hms.video.sdk.models.HMSLocalPeer import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.HMSRoom import live.hms.video.sdk.models.enums.HMSPeerUpdate -import live.hms.video.sdk.models.enums.HMSRoomUpdate import live.hms.video.sdk.models.role.PublishParams -import live.hms.video.utils.HMSLogger class PreviewFragment : Fragment() { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/SettingsBottomSheet.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/SettingsBottomSheet.kt index 4b7f7edfd..352156696 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/SettingsBottomSheet.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/SettingsBottomSheet.kt @@ -10,8 +10,8 @@ import androidx.appcompat.widget.AppCompatButton import com.google.android.material.bottomsheet.BottomSheetDialogFragment import live.hms.roomkit.R import live.hms.roomkit.databinding.SettingsBottomSheetDialogBinding +import live.hms.roomkit.setOnSingleClickListener import live.hms.roomkit.ui.meeting.participants.MusicSelectionSheet -import live.hms.roomkit.util.setOnSingleClickListener import live.hms.roomkit.util.viewLifecycle diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerFragment.kt index 40591f110..29c6de991 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerFragment.kt @@ -4,24 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import live.hms.roomkit.R import live.hms.roomkit.databinding.FragmentActiveSpeakerBinding -import live.hms.roomkit.ui.meeting.CustomPeerMetadata -import live.hms.roomkit.ui.meeting.MeetingTrack -import live.hms.roomkit.ui.meeting.commons.VideoGridBaseFragment -import live.hms.roomkit.ui.meeting.pinnedvideo.StatsInterpreter -import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.util.* -import live.hms.video.media.tracks.HMSLocalVideoTrack -import live.hms.video.media.tracks.HMSRemoteVideoTrack -import live.hms.video.media.tracks.HMSVideoTrack -import live.hms.video.sdk.models.enums.HMSPeerUpdate -import live.hms.video.utils.HMSLogger -import org.webrtc.RendererCommon -import org.webrtc.SurfaceViewRenderer -class ActiveSpeakerFragment : VideoGridBaseFragment() { +class ActiveSpeakerFragment : Fragment() { companion object { private const val TAG = "ActiveSpeakerFragment" @@ -29,11 +18,6 @@ class ActiveSpeakerFragment : VideoGridBaseFragment() { private var binding by viewLifecycle() - private var screenShareTrack: MeetingTrack? = null - private var wasLastModePip = false - - private val mediaPlayerManager by lazy { MediaPlayerManager(lifecycle) } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -44,207 +28,5 @@ class ActiveSpeakerFragment : VideoGridBaseFragment() { return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.applyTheme() - screenShareStats = StatsInterpreter(settings.showStats) - initViewModels() - } - - private fun unBindScreenShareTrack() { - screenShareTrack?.let { - unbindSurfaceView(binding.screenShare, it) - } - } - - override fun onResume() { - if (wasLastModePip) { - //if it's coming back from pip --> full screen i.e [pause --> resume] we wont' bind the screenshare track again since we never removed it in the first place - super.onResume() - wasLastModePip = false - screenShareOverLocalVideoInGrid() - return - } - - screenShareTrack?.let { meetingTrack -> - binding.screenShare.raisedHand.alpha = - visibilityOpacity(CustomPeerMetadata.fromJson(meetingTrack.peer.metadata)?.isHandRaised == true) - bindSurfaceView( - binding.screenShare, - meetingTrack, - RendererCommon.ScalingType.SCALE_ASPECT_FIT - ) - binding.screenShare.hmsVideoView.setOnLongClickListener { view -> - openDialog( - view as? SurfaceViewRenderer, - meetingTrack.video, - meetingTrack.peer.name.orEmpty() - ) - return@setOnLongClickListener true - } - } - super.onResume() - } - - private fun openDialog( - surfaceView: SurfaceViewRenderer?, - videoTrack: HMSVideoTrack?, - peerName: String - ) { - - if (videoTrack.isValid().not()) - return - contextSafe { context, activity -> - context.showTileListDialog ( - isLocalTrack = videoTrack is HMSLocalVideoTrack, - onScreenCapture = { captureVideoFrame(surfaceView, videoTrack) }, - onSimulcast = { context.showSimulcastDialog(videoTrack as? HMSRemoteVideoTrack) }, - onMirror = { context.showMirrorOptions(surfaceView)} - ) - } - - } - - private fun captureVideoFrame(surfaceView: SurfaceViewRenderer?, videoTrack: HMSVideoTrack?) { - - //safe check incase video - if (videoTrack.isValid().not()){ - return - } - contextSafe { context, activity -> mediaPlayerManager.startPlay(R.raw.camera_shut, context )} - surfaceView?.vibrateStrong() - - surfaceView?.onBitMap(onBitmap = { bitmap -> - contextSafe { context, activity -> - //stores the bitmap in local cache thus avoiding any permission - val uri = bitmap?.saveCaptureToLocalCache(context) - //the uri is used to open share intent - uri?.let { activity.openShareIntent(it) } - } - }) - } - - - override fun onPause() { - if (activity?.isInPictureInPictureMode == true) { - //when it's pip mode don't unbind views - wasLastModePip = true - screenShareOverLocalVideoInGrid() - } else { - unBindScreenShareTrack() - } -// screenShareStats.close() - super.onPause() - } - - private fun screenShareOverLocalVideoInGrid() { - //hide video grid when screen share is shown! - binding.container.visibility = if (screenShareTrack != null && activity?.isInPictureInPictureMode == true) { - View.GONE - } else { - View.VISIBLE - } - } - - override fun initViewModels() { - super.initViewModels() - meetingViewModel.tracks.observe(viewLifecycleOwner) { tracks -> - HMSLogger.v(TAG, "tracks update received 🎼 [size=${tracks.size}]") - updateScreenshareTracks(tracks) - } - - meetingViewModel.activeSpeakersUpdatedTracks.observe(viewLifecycleOwner) { tracks -> - HMSLogger.v(TAG, "tracks update received 🎼 [size=${tracks.size}]") - updateVideos(binding.container, tracks, false) - } - - meetingViewModel.activeSpeakers.observe(viewLifecycleOwner) { (videos, speakers) -> - updateVideos(binding.container, videos, false) - // Active speaker should be updated via, tracks AND actual active speakers. - applySpeakerUpdates(speakers) - } - - meetingViewModel.peerMetadataNameUpdate.observe(viewLifecycleOwner) { - if( screenShareTrack?.peer?.peerID == it.first.peerID) { - when(it.second) { - HMSPeerUpdate.METADATA_CHANGED -> { - HMSLogger.v(TAG,"metadata changed : ${it.second} ") - } - - HMSPeerUpdate.NAME_CHANGED -> { - binding.screenShare.name.text = it.first.name - } - else -> {} - } - } - } - - meetingViewModel.trackStatus.observe(viewLifecycleOwner) { statsPair -> - if (statsPair.second){ - binding.screenShare.statsView.visibility = View.GONE - }else{ - binding.screenShare.statsView.visibility = View.VISIBLE - binding.screenShare.statsView.text = statsPair.first - } - } - } - - override fun isScreenshare(): Boolean { - return false - } - - private var screenShareStats : StatsInterpreter? = null - private fun updateScreenshareTracks(tracks: List) { - - // Check if the currently shared screen-share track is removed - screenShareTrack?.let { screen -> - if (!tracks.contains(screen)) { - screenShareTrack?.let { unbindSurfaceView(binding.screenShare, it) } -// screenShareStats.close() - screenShareTrack = null - screenShareOverLocalVideoInGrid() - } - } - - // Check for screen share - if (screenShareTrack == null) tracks.find { it.isScreen }?.let { screen -> - screenShareStats?.initiateStats( - viewLifecycleOwner, - meetingViewModel.getStats(), - screen.video, - screen.audio, screen.peer.isLocal - ) { statsString -> - binding.screenShare.statsView.text = statsString - } - meetingViewModel.statsToggleData.observe(viewLifecycleOwner, Observer { - if (it){ - binding.screenShare.statsView.visibility = View.VISIBLE - }else{ - binding.screenShare.statsView.visibility = View.GONE - } - }) - screenShareTrack = screen - screenShareOverLocalVideoInGrid() - if (isFragmentVisible) { - bindSurfaceView( - binding.screenShare, - screen, - RendererCommon.ScalingType.SCALE_ASPECT_FIT - ) - } - bindVideo(binding.screenShare, screen) - binding.screenShare.apply { - iconAudioOff.visibility = View.GONE - iconScreenShare.visibility = View.GONE - audioLevel.visibility = View.GONE - raisedHand.alpha = visibilityOpacity(CustomPeerMetadata.fromJson(screen.peer.metadata)?.isHandRaised == true) - } - binding.screenShareContainer.visibility = View.VISIBLE - } - - if (screenShareTrack == null && binding.screenShareContainer.visibility != View.GONE) { - binding.screenShareContainer.visibility = View.GONE - } - } } \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/audiomode/AudioItemsAdapter.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/audiomode/AudioItemsAdapter.kt index c44124103..54f73cecf 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/audiomode/AudioItemsAdapter.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/audiomode/AudioItemsAdapter.kt @@ -6,7 +6,7 @@ import androidx.annotation.MainThread import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import live.hms.roomkit.databinding.ListItemAudioBinding -import live.hms.roomkit.util.NameUtils +import live.hms.common.util.NameUtils import live.hms.roomkit.util.visibility class AudioItemsAdapter : RecyclerView.Adapter() { diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantItem.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantItem.kt index 917ec261f..043234f9c 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantItem.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantItem.kt @@ -4,16 +4,14 @@ import androidx.appcompat.widget.PopupMenu import com.xwray.groupie.viewbinding.BindableItem import live.hms.roomkit.R import live.hms.roomkit.databinding.ListItemPeerListBinding -import live.hms.roomkit.helpers.NetworkQualityHelper -import live.hms.roomkit.ui.meeting.CustomPeerMetadata +import live.hms.common.util.helpers.NetworkQualityHelper +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.getColorOrDefault import live.hms.video.connection.stats.quality.HMSNetworkQuality -import live.hms.video.media.tracks.HMSTrack import live.hms.video.media.tracks.HMSTrackType import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.HMSRemotePeer -import live.hms.video.sdk.models.role.HMSRole class ParticipantItem(private val hmsPeer: HMSPeer, private val toggleTrack: (hmsPeer: HMSRemotePeer, type: HMSTrackType) -> Unit, diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsFragment.kt index fc277b876..427e61236 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/participants/ParticipantsFragment.kt @@ -21,7 +21,7 @@ import com.xwray.groupie.GroupieAdapter import kotlinx.coroutines.launch import live.hms.roomkit.R import live.hms.roomkit.databinding.FragmentParticipantsBinding -import live.hms.roomkit.ui.meeting.CustomPeerMetadata +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.meeting.MeetingState import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.meeting.MeetingViewModelFactory diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/PinnedVideoFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/PinnedVideoFragment.kt index 163e7583f..b53608da1 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/PinnedVideoFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/PinnedVideoFragment.kt @@ -11,8 +11,9 @@ import androidx.core.view.forEach import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.LinearLayoutManager +import live.hms.common.util.NameUtils import live.hms.roomkit.databinding.FragmentPinnedVideoBinding -import live.hms.roomkit.ui.meeting.CustomPeerMetadata +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.meeting.MeetingTrack import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.meeting.MeetingViewModelFactory @@ -231,7 +232,7 @@ class PinnedVideoFragment : Fragment() { if (newName != null) { with(binding.pinVideo) { name.text = newName - nameInitials.text = NameUtils.getInitials(newName) + nameInitials.text = live.hms.common.util.NameUtils.getInitials(newName) } } } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/VideoListAdapter.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/VideoListAdapter.kt index cdbe131ee..b95b5f064 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/VideoListAdapter.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/pinnedvideo/VideoListAdapter.kt @@ -11,12 +11,12 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.flow.Flow import live.hms.roomkit.databinding.ListItemVideoBinding -import live.hms.roomkit.helpers.NetworkQualityHelper -import live.hms.roomkit.ui.meeting.CustomPeerMetadata +import live.hms.common.util.helpers.NetworkQualityHelper +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.meeting.MeetingTrack import live.hms.roomkit.ui.theme.HMSPrebuiltTheme import live.hms.roomkit.ui.theme.getColorOrDefault -import live.hms.roomkit.util.NameUtils +import live.hms.common.util.NameUtils import live.hms.roomkit.util.visibility import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.enums.HMSPeerUpdate diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridAdapter.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridAdapter.kt deleted file mode 100644 index 4c24c19db..000000000 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridAdapter.kt +++ /dev/null @@ -1,25 +0,0 @@ -package live.hms.roomkit.ui.meeting.videogrid - -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.DiffUtil -import androidx.viewpager2.adapter.FragmentStateAdapter - -class VideoGridAdapter(parentFragment: Fragment,val isScreenShare: Boolean = false) : FragmentStateAdapter(parentFragment) { - - companion object { - const val TAG = "VideoGridAdapter" - } - - var totalPages = 0 - set(value) { - val callback = VideoGridPagerDiffUtil(field, value) - val diff = DiffUtil.calculateDiff(callback) - field = value - diff.dispatchUpdatesTo(this) - } - - override fun getItemCount() = totalPages - override fun createFragment(position: Int) = VideoGridPageFragment.newInstance(position, isScreenShare) - override fun getItemId(position: Int) = position.toLong() - override fun containsItem(itemId: Long) = itemId < totalPages -} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridFragment.kt index 30f4a9774..711e828b0 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridFragment.kt @@ -13,16 +13,18 @@ import com.google.android.material.tabs.TabLayoutMediator import live.hms.roomkit.R import live.hms.roomkit.databinding.FragmentGridVideoBinding import live.hms.roomkit.ui.inset.makeInset -import live.hms.roomkit.ui.meeting.CustomPeerMetadata +import live.hms.common.util.helpers.CustomPeerMetadata import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.settings.SettingsStore import live.hms.roomkit.ui.theme.applyTheme import live.hms.roomkit.ui.theme.setIconDisabled -import live.hms.roomkit.util.NameUtils +import live.hms.common.util.NameUtils import live.hms.roomkit.util.viewLifecycle import live.hms.video.error.HMSException import live.hms.video.sdk.HMSActionResultListener import live.hms.video.sdk.models.enums.HMSPeerUpdate +import live.hms.videogrid.GridViewModel +import live.hms.videogrid.VideoGridAdapter import org.webrtc.RendererCommon class VideoGridFragment : Fragment() { @@ -36,6 +38,7 @@ class VideoGridFragment : Fragment() { private lateinit var clipboard: ClipboardManager private val meetingViewModel: MeetingViewModel by activityViewModels() + private val gridViewModel: GridViewModel by activityViewModels() private lateinit var peerGridVideoAdapter: VideoGridAdapter private lateinit var screenShareAdapter: VideoGridAdapter @@ -54,7 +57,7 @@ class VideoGridFragment : Fragment() { ): View { binding = FragmentGridVideoBinding.inflate(inflater, container, false) settings = SettingsStore(requireContext()) - + gridViewModel.initHMSSDK(meetingViewModel.hmsSDK) initVideoGrid() initViewModels() return binding.root @@ -129,7 +132,7 @@ class VideoGridFragment : Fragment() { } - meetingViewModel.tracks.observe(viewLifecycleOwner) { + gridViewModel.getTrackLiveData().observe(viewLifecycleOwner) { val localMeeting = it.filter { it.isLocal }.firstOrNull() //show or hide inset @@ -178,7 +181,7 @@ class VideoGridFragment : Fragment() { @SuppressLint("SetTextI18n") private fun initViewModels() { - meetingViewModel.tracks.observe(viewLifecycleOwner) { tracks -> + gridViewModel.getTrackLiveData().observe(viewLifecycleOwner) { tracks -> val screenShareTrackList = tracks.filter { it.isScreen } var newRowCount = 0 @@ -203,7 +206,7 @@ class VideoGridFragment : Fragment() { binding.localScreenShareContainer.visibility = View.GONE } - meetingViewModel.updateRowAndColumnSpanForVideoPeerGrid.value = Pair(newRowCount, newColumnCount) + gridViewModel.updateRowAndColumnSpanForVideoPeerGrid.value = Pair(newRowCount, newColumnCount) val itemsPerPage = newRowCount * newColumnCount // Without this, the extra inset adds one more tile than they should diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageViewModel.kt b/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageViewModel.kt deleted file mode 100644 index 264432ac6..000000000 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageViewModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package live.hms.roomkit.ui.meeting.videogrid - -import androidx.lifecycle.ViewModel -import live.hms.roomkit.ui.meeting.MeetingTrack - -class VideoGridPageViewModel : ViewModel() { - var initialVideos = arrayOf() - var onVideoItemClick: ((MeetingTrack) -> Unit)? = null -} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionCreation.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionCreation.kt index 19e5a9898..b89dd1f84 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionCreation.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionCreation.kt @@ -13,7 +13,7 @@ import androidx.recyclerview.widget.RecyclerView.VERTICAL import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutPollQuestionCreationBinding import live.hms.roomkit.ui.meeting.MeetingViewModel -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener import live.hms.roomkit.util.viewLifecycle /** diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt index b5ad97afb..abdcb35fd 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollQuestionViewHolder.kt @@ -12,7 +12,7 @@ import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutPollQuestionCreationItemBinding import live.hms.roomkit.databinding.LayoutPollQuizItemShortAnswerBinding import live.hms.roomkit.databinding.LayoutPollQuizOptionsItemMultiChoiceBinding -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener private var count : Long = 0 sealed class QuestionUi(open val index : Long, open val viewType : Int, open val requiredToAnswer : Boolean){ diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt index d81f4329a..9e5a4023a 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/PollsCreationFragment.kt @@ -16,7 +16,7 @@ import live.hms.roomkit.ui.meeting.MeetingFragmentDirections import live.hms.roomkit.ui.meeting.MeetingViewModel import live.hms.roomkit.ui.polls.previous.PreviousPollsAdaptor import live.hms.roomkit.ui.polls.previous.PreviousPollsInfo -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener import live.hms.roomkit.util.viewLifecycle /** diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt index 5cf24354a..a9160cbd3 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayFragment.kt @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import live.hms.roomkit.databinding.LayoutPollsDisplayBinding import live.hms.roomkit.ui.meeting.MeetingViewModel -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener import live.hms.roomkit.util.viewLifecycle import live.hms.video.polls.models.HmsPoll diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt index 42f27312e..d7391feab 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/polls/display/PollDisplayQuestionHolder.kt @@ -10,7 +10,7 @@ import live.hms.roomkit.R import live.hms.roomkit.databinding.LayoutPollsDisplayChoicesQuesionBinding import live.hms.roomkit.databinding.LayoutQuizDisplayShortAnswerBinding import live.hms.roomkit.ui.polls.display.voting.VotingProgressAdapter -import live.hms.roomkit.util.setOnSingleClickListener +import live.hms.common.util.setOnSingleClickListener import live.hms.video.polls.models.HmsPoll import live.hms.video.polls.models.question.HMSPollQuestion import live.hms.video.polls.models.question.HMSPollQuestionType diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt b/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt index 97a7f3c9d..7ccae0667 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt +++ b/room-kit/src/main/java/live/hms/roomkit/ui/theme/ThemeExt.kt @@ -1,13 +1,18 @@ package live.hms.roomkit.ui.theme import android.content.res.ColorStateList -import android.graphics.PorterDuff +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Shader import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RectShape +import android.graphics.drawable.shapes.RoundRectShape import android.view.View -import android.widget.ImageButton import android.widget.ImageView import android.widget.TextView +import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.appcompat.widget.AppCompatImageButton import androidx.cardview.widget.CardView @@ -103,26 +108,43 @@ fun getColorOrDefault(colorStr: String?, defaultColor: String): Int { } } +fun View.backgroundGradientDrawable(@ColorInt startColor: Int, @ColorInt endColor: Int): Unit { + val h = this.height.toFloat() + val shapeDrawable = ShapeDrawable(RectShape()) + shapeDrawable.paint.shader = + LinearGradient(0f, 0f, 0f, h, startColor, endColor, Shader.TileMode.REPEAT) + this.background = shapeDrawable +} + internal fun ImageView.setIconEnabled( @DrawableRes enabledIconDrawableRes: Int ) { this.setBackgroundResource(R.drawable.gray_round_stroked_drawable) + this.setBackgroundColor(resources.getColor(android.R.color.transparent)) this.setImageResource(enabledIconDrawableRes) - - background.setColorFilter( - getColorOrDefault( - HMSPrebuiltTheme.getColours()?.secondaryBright, - HMSPrebuiltTheme.getDefaults().border_bright - ), PorterDuff.Mode.DST_OVER - ) - drawable.setTint( getColorOrDefault( HMSPrebuiltTheme.getColours()?.onSurfaceHigh, HMSPrebuiltTheme.getDefaults().onsurface_high_emp ) ) + val shapedrawable = ShapeDrawable() + val mDensity = getResources().getDisplayMetrics().density; + val r: Float = 10 * mDensity + val radii = floatArrayOf(r, r, r, r, r, r, r, r) + + shapedrawable.shape = RoundRectShape(radii, null, null) + shapedrawable.paint.color = getColorOrDefault( + HMSPrebuiltTheme.getColours()?.secondaryBright, + HMSPrebuiltTheme.getDefaults().border_bright + ) + shapedrawable.paint.isAntiAlias = true + shapedrawable.paint.strokeWidth = r/3 + shapedrawable.paint.style = Paint.Style.STROKE + + + background = shapedrawable (drawable as? Animatable)?.start() } diff --git a/settings.gradle b/settings.gradle index 970dba65a..4dbdde74e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,3 +2,5 @@ include ':lib' include ':app' rootProject.name = "Android 100ms" include ':room-kit' +include ':videogrid' +include ':common' diff --git a/videogrid/.gitignore b/videogrid/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/videogrid/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/videogrid/build.gradle b/videogrid/build.gradle new file mode 100644 index 000000000..83d1d7787 --- /dev/null +++ b/videogrid/build.gradle @@ -0,0 +1,130 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'kotlin-kapt' + id 'maven-publish' + id 'kotlin-parcelize' + id 'signing' + id "org.jetbrains.dokka" version "1.5.0" +} + +def getVersionName = { -> + return "$HMS_ROOM_KIT_VERSION" +} + +android { + namespace 'live.hms.videogrid' + compileSdk 33 + + defaultConfig { + minSdk 21 + targetSdk 33 + + versionCode 1 + versionName getVersionName() + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + buildFeatures { + viewBinding true + dataBinding { + enabled true + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + publishing { + singleVariant('release') { + withSourcesJar() + } + } +} + +dependencies { + def hmsVersion = "2.7.7-de" + // To add dependencies of specific module + implementation "live.100ms:android-sdk:$hmsVersion" + implementation "live.100ms:video-view:$hmsVersion" + + + implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.viewpager2:viewpager2:1.0.0' + implementation 'androidx.fragment:fragment-ktx:1.3.2' + + // Life Cycle + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0" + implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0" + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' + implementation 'com.google.android.material:material:1.5.0' + + implementation "live.100ms:common:0.0.6" +} + + +task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { + archiveFileName = "javadoc.jar" + from "build/dokka/javadoc" + archiveClassifier.set("javadoc") +} + +afterEvaluate { + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + from components.release + pom { + // Only sign the artefacts if this is a maven central build. + // This would only halt jitpack builds and/or make our signing keys public. + if(rootProject.properties["ossrhUsername"]) { + artifact javadocJar + signing { + sign publishing.publications + } + } + name = "100ms.live Android Room Kit" + description = "Room Kit that simplifies setting up videoconferencing in your own app. See more at https://www.100ms.live/docs/android/v2/guides/quickstart" + url = "100ms.live" + licenses { + license { + name = 'MIT License' + url = 'http://www.opensource.org/licenses/mit-license.php' + } + } + developers { + developer { + id = '1' + name = '100ms' + email = 'support@100ms.live' + } + } + scm { + connection = 'SCM is private' + developerConnection = 'SCM is private' + url = 'SCM is private' + } + } + groupId = "live.100ms" + artifactId = 'videogrid' + version = getVersionName() + } + } + } +} \ No newline at end of file diff --git a/videogrid/consumer-rules.pro b/videogrid/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/videogrid/proguard-rules.pro b/videogrid/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/videogrid/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/videogrid/src/androidTest/java/live/hms/videogrid/ExampleInstrumentedTest.kt b/videogrid/src/androidTest/java/live/hms/videogrid/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..666956c9c --- /dev/null +++ b/videogrid/src/androidTest/java/live/hms/videogrid/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package live.hms.videogrid + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("live.hms.videogrid.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/videogrid/src/main/AndroidManifest.xml b/videogrid/src/main/AndroidManifest.xml new file mode 100644 index 000000000..a5918e68a --- /dev/null +++ b/videogrid/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerCache.kt b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerCache.kt similarity index 96% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerCache.kt rename to videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerCache.kt index 7cdf68c86..cd0ea2f13 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerCache.kt +++ b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerCache.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.ui.meeting.activespeaker +package live.hms.videogrid import java.util.concurrent.ConcurrentLinkedQueue diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerHandler.kt b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerHandler.kt similarity index 83% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerHandler.kt rename to videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerHandler.kt index c8547ca57..c23c510a9 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/activespeaker/ActiveSpeakerHandler.kt +++ b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerHandler.kt @@ -1,13 +1,12 @@ -package live.hms.roomkit.ui.meeting.activespeaker +package live.hms.videogrid -import live.hms.roomkit.ui.meeting.MeetingTrack import live.hms.video.sdk.models.HMSSpeaker import live.hms.video.utils.HMSLogger -class ActiveSpeakerHandler(private val appendUnsorted : Boolean = false, private val numActiveSpeakerVideos : Int = 4, private val getTracks: () -> List) { +class ActiveSpeakerHandler(private val appendUnsorted : Boolean = false, private val numActiveSpeakerVideos : Int = 4, private val getTracks: () -> ArrayList) { private val TAG = ActiveSpeakerHandler::class.java.simpleName private val speakerCache = ActiveSpeakerCache(numActiveSpeakerVideos, appendUnsorted) - fun trackUpdateTrigger(tracks: List): List { + fun trackUpdateTrigger(tracks: List): List { synchronized(tracks) { // Update lru just to keep it as much filled as possible @@ -28,7 +27,7 @@ class ActiveSpeakerHandler(private val appendUnsorted : Boolean = false, private } } - fun speakerUpdate(speakers: Array): Pair, Array> { + fun speakerUpdate(speakers: Array): Pair, Array> { HMSLogger.v( TAG, "speakers update received 🎙 [size=${speakers.size}, names=${speakers.map { it.peer?.name }}] " @@ -42,7 +41,7 @@ class ActiveSpeakerHandler(private val appendUnsorted : Boolean = false, private return Pair(update(), speakers) } - private fun update(): List { + private fun update(): List { // Update all the videos which aren't screenshares val order = speakerCache.getAllItems() diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ActiveSpeakerLiveData.kt b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerLiveData.kt similarity index 91% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/ActiveSpeakerLiveData.kt rename to videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerLiveData.kt index 7628ba36c..7f59275a1 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/ActiveSpeakerLiveData.kt +++ b/videogrid/src/main/java/live/hms/videogrid/ActiveSpeakerLiveData.kt @@ -1,6 +1,7 @@ -package live.hms.roomkit.ui.meeting +package live.hms.videogrid import androidx.lifecycle.MediatorLiveData +import live.hms.video.sdk.reactive.MeetingTrack abstract class ActiveSpeakerLiveData : MediatorLiveData>() { diff --git a/videogrid/src/main/java/live/hms/videogrid/GridViewModel.kt b/videogrid/src/main/java/live/hms/videogrid/GridViewModel.kt new file mode 100644 index 000000000..07b880360 --- /dev/null +++ b/videogrid/src/main/java/live/hms/videogrid/GridViewModel.kt @@ -0,0 +1,105 @@ +package live.hms.videogrid + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import live.hms.video.sdk.HMSAudioListener +import live.hms.video.sdk.HMSSDK +import live.hms.video.sdk.models.HMSSpeaker +import live.hms.video.sdk.reactive.MeetingTrack + +class GridViewModel( + application: Application +) : AndroidViewModel(application) { + + private var hmssdk: HMSSDK? = null + val speakersLiveData = MutableLiveData>() + val updateRowAndColumnSpanForVideoPeerGrid = MutableLiveData>() + + private val liveData by lazy { hmssdk!!.getMeetingTrackFlow().asLiveData() } + + companion object { + const val TAG = "GridViewModel" + } + + fun initHMSSDK(hmssdk: HMSSDK) { + this.hmssdk = hmssdk + } + + + fun getTrackLiveData(): LiveData> { + if (hmssdk == null) throw Exception("HMSSDK not initialized") + return liveData + } + + + + + + val speakerUpdateLiveData by lazy { + object : ActiveSpeakerLiveData() { + private val speakerH = ActiveSpeakerHandler( + true, 3 * 2 + ) { getTrackLiveData().value?: ArrayList() } + + + init { + initSpeakerCallBack() + addSpeakerSource() + + // Add all tracks as they come in. + addSource(getTrackLiveData()) { meetTracks: List -> + //if remote peer and local peer is present inset mode + val excludeLocalTrackIfRemotePeerIsPreset = if (meetTracks.size > 1) { + meetTracks.filter { !it.isLocal }.toList() + } else meetTracks + + val result = speakerH.trackUpdateTrigger(excludeLocalTrackIfRemotePeerIsPreset) + setValue(result) + } + + } + + private fun initSpeakerCallBack() { + hmssdk!!.addAudioObserver(object : HMSAudioListener { + override fun onAudioLevelUpdate(speakers: Array) { + speakersLiveData.postValue(speakers) + } + }) + + } + + + override fun addSpeakerSource() { + addSource(speakersLiveData) { speakers: Array -> + + val excludeLocalTrackIfRemotePeerIsPreset: Array = + speakers.filter { it.peer?.isLocal == false }.toTypedArray() + + val result = speakerH.speakerUpdate(excludeLocalTrackIfRemotePeerIsPreset) + setValue(result.first) + } + } + + override fun removeSpeakerSource() { + removeSource(speakersLiveData) + } + + //TODO can't be null + fun refreshSpeaker() { + // speakers.postValue(speakers.value) + } + + override fun updateMaxActiveSpeaker(rowCount: Int, columnCount: Int) { + speakerH.updateMaxActiveSpeaker(rowCount * columnCount) + refreshSpeaker() + } + + + } + } + + +} \ No newline at end of file diff --git a/videogrid/src/main/java/live/hms/videogrid/GridViewModelFactory.kt b/videogrid/src/main/java/live/hms/videogrid/GridViewModelFactory.kt new file mode 100644 index 000000000..53804fc1a --- /dev/null +++ b/videogrid/src/main/java/live/hms/videogrid/GridViewModelFactory.kt @@ -0,0 +1,18 @@ +package live.hms.videogrid + +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class GridViewModelFactory( + private val application: Application +) : ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(GridViewModel::class.java)) { + return GridViewModel(application) as T + } + throw IllegalArgumentException("Unknown ViewModel class $modelClass") + } +} \ No newline at end of file diff --git a/videogrid/src/main/java/live/hms/videogrid/VideoGridAdapter.kt b/videogrid/src/main/java/live/hms/videogrid/VideoGridAdapter.kt new file mode 100644 index 000000000..f92ce06aa --- /dev/null +++ b/videogrid/src/main/java/live/hms/videogrid/VideoGridAdapter.kt @@ -0,0 +1,42 @@ +package live.hms.videogrid + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.DiffUtil +import androidx.viewpager2.adapter.FragmentStateAdapter + +class VideoGridAdapter( + private val fragmentManager: FragmentManager, + private val lifeCycle: Lifecycle, + private val isScreenShare: Boolean = false +) : FragmentStateAdapter(fragmentManager, lifeCycle) { + + companion object { + const val TAG = "VideoGridAdapter" + } + + constructor( + activity: FragmentActivity, isScreenShare: Boolean = false + ) : this(activity.supportFragmentManager, activity.lifecycle, isScreenShare) + + constructor( + parentFragment: Fragment, isScreenShare: Boolean = false + ) : this(parentFragment.childFragmentManager, parentFragment.lifecycle, isScreenShare) + + var totalPages = 0 + set(value) { + val callback = VideoGridPagerDiffUtil(field, value) + val diff = DiffUtil.calculateDiff(callback) + field = value + diff.dispatchUpdatesTo(this) + } + + override fun getItemCount() = totalPages + override fun createFragment(position: Int) = + VideoGridPageFragment.newInstance(position, isScreenShare) + + override fun getItemId(position: Int) = position.toLong() + override fun containsItem(itemId: Long) = itemId < totalPages +} \ No newline at end of file diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/commons/VideoGridBaseFragment.kt b/videogrid/src/main/java/live/hms/videogrid/VideoGridBaseFragment.kt similarity index 78% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/commons/VideoGridBaseFragment.kt rename to videogrid/src/main/java/live/hms/videogrid/VideoGridBaseFragment.kt index 3832c423c..c26496a6b 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/commons/VideoGridBaseFragment.kt +++ b/videogrid/src/main/java/live/hms/videogrid/VideoGridBaseFragment.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.ui.meeting.commons +package live.hms.videogrid import android.content.Context import android.os.Bundle @@ -13,25 +13,18 @@ import androidx.core.view.children import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import live.hms.roomkit.R -import live.hms.roomkit.databinding.GridItemVideoBinding -import live.hms.roomkit.databinding.VideoCardBinding -import live.hms.roomkit.helpers.NetworkQualityHelper -import live.hms.roomkit.ui.meeting.CustomPeerMetadata -import live.hms.roomkit.ui.meeting.MeetingTrack -import live.hms.roomkit.ui.meeting.MeetingViewModel -import live.hms.roomkit.ui.meeting.pinnedvideo.StatsInterpreter -import live.hms.roomkit.ui.settings.SettingsStore -import live.hms.roomkit.ui.theme.HMSPrebuiltTheme -import live.hms.roomkit.ui.theme.applyTheme -import live.hms.roomkit.ui.theme.getColorOrDefault -import live.hms.roomkit.util.* -import live.hms.video.media.tracks.HMSLocalVideoTrack -import live.hms.video.media.tracks.HMSRemoteVideoTrack +import live.hms.common.util.NameUtils +import live.hms.common.util.helpers.CustomPeerMetadata +import live.hms.common.util.helpers.NetworkQualityHelper +import live.hms.common.util.setCameraGestureListener import live.hms.video.media.tracks.HMSVideoTrack import live.hms.video.sdk.models.HMSPeer import live.hms.video.sdk.models.HMSSpeaker import live.hms.video.sdk.models.enums.HMSPeerUpdate +import live.hms.videogrid.databinding.HmsGridItemBinding +import live.hms.videogrid.databinding.HmsVideoCardBinding +import live.hms.videogrid.utils.contextSafe +import live.hms.videogrid.utils.visibilityOpacity import live.hms.videoview.HMSVideoView import org.webrtc.RendererCommon import org.webrtc.SurfaceViewRenderer @@ -52,8 +45,7 @@ abstract class VideoGridBaseFragment : Fragment() { private const val TAG = "VideoGridBase" } - protected val settings: SettingsStore by lazy { SettingsStore(requireContext()) } - protected val meetingViewModel by activityViewModels() + // Determined using the onResume() and onPause() var isFragmentVisible = false @@ -69,13 +61,12 @@ abstract class VideoGridBaseFragment : Fragment() { private var gridColumnCount = 0 data class RenderedViewPair( - val binding: GridItemVideoBinding, - val meetingTrack: MeetingTrack, - val statsInterpreter: StatsInterpreter?, + val binding: HmsGridItemBinding, + val meetingTrack: live.hms.video.sdk.reactive.MeetingTrack, ) protected val renderedViews = ArrayList() - private val mediaPlayerManager by lazy { MediaPlayerManager(lifecycle) } + protected val gridViewModel by activityViewModels() internal fun shouldUpdateRowOrGrid(rowCount: Int, columnCount: Int) : Boolean{ return !(rowCount == gridRowCount && columnCount == gridColumnCount) @@ -177,20 +168,21 @@ abstract class VideoGridBaseFragment : Fragment() { } - private fun createVideoView(parent: ViewGroup): GridItemVideoBinding { - val binding = GridItemVideoBinding.inflate( + private fun createVideoView(parent: ViewGroup): HmsGridItemBinding { + val binding = HmsGridItemBinding.inflate( LayoutInflater.from(requireContext()), parent, false ) - binding.videoCard.applyTheme() + //TODO add + //binding.videoCard.applyTheme() return binding } protected fun bindSurfaceView( - binding: VideoCardBinding, - item: MeetingTrack, + binding: HmsVideoCardBinding, + item: live.hms.video.sdk.reactive.MeetingTrack, scalingType: RendererCommon.ScalingType = RendererCommon.ScalingType.SCALE_ASPECT_BALANCED ) { Log.d(TAG,"bindSurfaceView for :: ${item.peer.name}") @@ -201,14 +193,14 @@ abstract class VideoGridBaseFragment : Fragment() { item.video?.let { track -> view.setScalingType(scalingType) view.addTrack(track) - view.disableAutoSimulcastLayerSelect(meetingViewModel.isAutoSimulcastEnabled()) +// view.disableAutoSimulcastLayerSelect(meetingViewModel.isAutoSimulcastEnabled()) binding.hmsVideoView.visibility = if (item.video?.isDegraded == true ) View.INVISIBLE else View.VISIBLE binding.hmsVideoView.setOnLongClickListener { (it as? HMSVideoView)?.let { videoView -> openDialog(videoView, item.video, item.peer.name.orEmpty()) } true } binding.hmsVideoView.setCameraGestureListener(item.video, { - activity?.openShareIntent(it) + }, onLongPress = {(binding.hmsVideoView as? SurfaceViewRenderer)?.let { surfaceView -> openDialog(surfaceView, item.video, item.peer.name.orEmpty()) }}) } @@ -222,36 +214,18 @@ abstract class VideoGridBaseFragment : Fragment() { ) { contextSafe { context, activity -> - context.showTileListDialog ( - isLocalTrack = videoTrack is HMSLocalVideoTrack, - onScreenCapture = { captureVideoFrame(surfaceView, videoTrack) }, - onSimulcast = { context.showSimulcastDialog(videoTrack as? HMSRemoteVideoTrack) }, - onMirror = { context.showMirrorOptions(surfaceView)} - ) +// context.showTileListDialog ( +// isLocalTrack = videoTrack is HMSLocalVideoTrack, +// onScreenCapture = { captureVideoFrame(surfaceView, videoTrack) }, +// onSimulcast = { context.showSimulcastDialog(videoTrack as? HMSRemoteVideoTrack) }, +// onMirror = { context.showMirrorOptions(surfaceView)} +// ) } } - private fun captureVideoFrame(surfaceView: SurfaceViewRenderer?, videoTrack: HMSVideoTrack?) { - - //safe check incase video - if (videoTrack.isValid().not()){ - return - } + private fun captureVideoFrame(surfaceView: SurfaceViewRenderer?, videoTrack: HMSVideoTrack?) {} - contextSafe { context, activity -> mediaPlayerManager.startPlay(R.raw.camera_shut, context )} - surfaceView?.vibrateStrong() - - surfaceView?.onBitMap(onBitmap = { bitmap -> - contextSafe { context, activity -> - //stores the bitmap in local cache thus avoiding any permission - val uri = bitmap?.saveCaptureToLocalCache(context) - //the uri is used to open share intent - uri?.let { activity.openShareIntent(it) } - } - }) - } - - protected fun bindVideo(binding: VideoCardBinding, item: MeetingTrack) { + protected fun bindVideo(binding: HmsVideoCardBinding, item: live.hms.video.sdk.reactive.MeetingTrack) { // FIXME: Add a shared VM with activity scope to subscribe to events // binding.container.setOnClickListener { viewModel.onVideoItemClick?.invoke(item) } //binding.applyTheme() @@ -291,11 +265,11 @@ abstract class VideoGridBaseFragment : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) - setVideoGridRowsAndColumns(settings.videoGridRows, settings.videoGridColumns) + setVideoGridRowsAndColumns(3, 2) } protected fun unbindSurfaceView( - binding: VideoCardBinding, - item: MeetingTrack, + binding: HmsVideoCardBinding, + item: live.hms.video.sdk.reactive.MeetingTrack, metadata: String = "" ) { Log.d(TAG,"unbindSurfaceView for :: ${item.peer.name}") @@ -309,7 +283,7 @@ abstract class VideoGridBaseFragment : Fragment() { protected fun updateVideos( layout: GridLayout, - newVideos: List, + newVideos: List, isVideoGrid: Boolean, isScreenShare: Boolean = false ) { @@ -348,7 +322,7 @@ abstract class VideoGridBaseFragment : Fragment() { // VideoTrack was not present, hence had to create an empty tile) bindSurfaceView(renderedViewPair.binding.videoCard, newVideo, if (isScreenshare()) RendererCommon.ScalingType.SCALE_ASPECT_FIT else RendererCommon.ScalingType.SCALE_ASPECT_BALANCED) //handling simulcast case since we are updating local reference it thinks it's an update instead of rebinding it - renderedViewPair.statsInterpreter?.updateVideoTrack(newVideo.video) + //renderedViewPair.statsInterpreter?.updateVideoTrack(newVideo.video) } val downlinkScore = newVideo.peer.networkQuality?.downlinkQuality updateNetworkQualityView(downlinkScore ?: -1,requireContext(),renderedViewPair.binding.videoCard.networkQuality) @@ -363,23 +337,23 @@ abstract class VideoGridBaseFragment : Fragment() { // Create a new view val videoBinding = createVideoView(layout) - var statsInterpreter: StatsInterpreter? = null +// var statsInterpreter: StatsInterpreter? = null if (!isVideoGrid) { - statsInterpreter = StatsInterpreter(settings.showStats) - meetingViewModel.statsToggleLiveData.observe(this) { - if (it) { - videoBinding.videoCard.statsView.visibility = View.VISIBLE - statsInterpreter.initiateStats( - viewLifecycleOwner, - meetingViewModel.getStats(), - newVideo.video, - newVideo.audio, - newVideo.peer.isLocal - ) { videoBinding.videoCard.statsView.text = it } - } else { - videoBinding.videoCard.statsView.visibility = View.GONE - } - } +// statsInterpreter = StatsInterpreter(settings.showStats) +// meetingViewModel.statsToggleLiveData.observe(this) { +// if (it) { +// videoBinding.videoCard.statsView.visibility = View.VISIBLE +// statsInterpreter.initiateStats( +// viewLifecycleOwner, +// meetingViewModel.getStats(), +// newVideo.video, +// newVideo.audio, +// newVideo.peer.isLocal +// ) { videoBinding.videoCard.statsView.text = it } +// } else { +// videoBinding.videoCard.statsView.visibility = View.GONE +// } +// } } // Bind surfaceView when view is visible to user @@ -394,7 +368,7 @@ abstract class VideoGridBaseFragment : Fragment() { visibilityOpacity(CustomPeerMetadata.fromJson(newVideo.peer.metadata)?.isBRBOn == true) layout.addView(videoBinding.root) - newRenderedViews.add(RenderedViewPair(videoBinding, newVideo, statsInterpreter)) + newRenderedViews.add(RenderedViewPair(videoBinding, newVideo)) } } } @@ -451,7 +425,8 @@ abstract class VideoGridBaseFragment : Fragment() { fun updateNetworkQualityView(downlinkScore : Int,context: Context,imageView: ImageView){ NetworkQualityHelper.getNetworkResource(downlinkScore, context).let { drawable -> if (downlinkScore == 0) { - imageView.setColorFilter(getColorOrDefault(HMSPrebuiltTheme.getColours()?.alertErrorDefault, HMSPrebuiltTheme.getDefaults().error_default), android.graphics.PorterDuff.Mode.SRC_IN); + //TODO + //imageView.setColorFilter(getColorOrDefault(HMSPrebuiltTheme.getColours()?.alertErrorDefault, HMSPrebuiltTheme.getDefaults().error_default), android.graphics.PorterDuff.Mode.SRC_IN); } else { imageView.colorFilter = null } @@ -480,7 +455,7 @@ abstract class VideoGridBaseFragment : Fragment() { videoCard.audioLevel.apply { text = "$level" } - if (level >= settings.silenceAudioLevelThreshold) { + if (level >= 10) { hideOrShowGridsForPip(index) wasLastSpeakingViewIndex = index } @@ -488,7 +463,7 @@ abstract class VideoGridBaseFragment : Fragment() { level >= 70 -> { container.strokeWidth = 6 } - 70 > level && level >= settings.silenceAudioLevelThreshold -> { + 70 > level && level >= 10 -> { container.strokeWidth = 4 } else -> { @@ -497,7 +472,7 @@ abstract class VideoGridBaseFragment : Fragment() { } } - videoCard.audioLevel.visibility = if (meetingViewModel.isPrebuiltDebugMode()) View.VISIBLE else View.INVISIBLE + videoCard.audioLevel.visibility = View.INVISIBLE } } } @@ -545,20 +520,20 @@ abstract class VideoGridBaseFragment : Fragment() { renderedViews.forEach { renderedView -> bindSurfaceView(renderedView.binding.videoCard, renderedView.meetingTrack, if (isScreenshare()) RendererCommon.ScalingType.SCALE_ASPECT_FIT else RendererCommon.ScalingType.SCALE_ASPECT_BALANCED) - meetingViewModel.statsToggleLiveData.observe(this) { - if (it) { - renderedView.binding.videoCard.statsView.visibility = View.VISIBLE - renderedView.statsInterpreter?.initiateStats( - viewLifecycleOwner, - meetingViewModel.getStats(), - renderedView.meetingTrack.video, - renderedView.meetingTrack.audio, - renderedView.meetingTrack.peer.isLocal - ) { string -> renderedView.binding.videoCard.statsView.text = string } - } else { - renderedView.binding.videoCard.statsView.visibility = View.GONE - } - } +// meetingViewModel.statsToggleLiveData.observe(this) { +// if (it) { +// renderedView.binding.videoCard.statsView.visibility = View.VISIBLE +// renderedView.statsInterpreter?.initiateStats( +// viewLifecycleOwner, +// meetingViewModel.getStats(), +// renderedView.meetingTrack.video, +// renderedView.meetingTrack.audio, +// renderedView.meetingTrack.peer.isLocal +// ) { string -> renderedView.binding.videoCard.statsView.text = string } +// } else { +// renderedView.binding.videoCard.statsView.visibility = View.GONE +// } +// } } } @@ -601,9 +576,10 @@ abstract class VideoGridBaseFragment : Fragment() { @CallSuper open fun initViewModels() { - meetingViewModel.peerMetadataNameUpdate.observe(viewLifecycleOwner) { - applyMetadataUpdates(it) - } + //TODO add flow from sdk side +// meetingViewModel.peerMetadataNameUpdate.observe(viewLifecycleOwner) { +// applyMetadataUpdates(it) +// } } abstract fun isScreenshare(): Boolean diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageFragment.kt b/videogrid/src/main/java/live/hms/videogrid/VideoGridPageFragment.kt similarity index 77% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageFragment.kt rename to videogrid/src/main/java/live/hms/videogrid/VideoGridPageFragment.kt index ee6c016a6..a1b0fcc3c 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageFragment.kt +++ b/videogrid/src/main/java/live/hms/videogrid/VideoGridPageFragment.kt @@ -1,14 +1,12 @@ -package live.hms.roomkit.ui.meeting.videogrid +package live.hms.videogrid import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf -import live.hms.roomkit.databinding.FragmentVideoGridPageBinding -import live.hms.roomkit.ui.meeting.MeetingTrack -import live.hms.roomkit.ui.meeting.commons.VideoGridBaseFragment -import live.hms.roomkit.util.viewLifecycle +import live.hms.common.databinding.FragmentVideoGridPageBinding +import live.hms.videogrid.utils.viewLifecycle import kotlin.math.min class VideoGridPageFragment : VideoGridBaseFragment() { @@ -28,7 +26,6 @@ class VideoGridPageFragment : VideoGridBaseFragment() { } } - private var binding by viewLifecycle() private val pageIndex by lazy { requireArguments()[BUNDLE_PAGE_INDEX] as Int } private val isScreenShare by lazy { requireArguments()[BUNDLE_IS_SCREEN_SHARE] as Boolean } @@ -47,10 +44,10 @@ class VideoGridPageFragment : VideoGridBaseFragment() { override fun onResume() { super.onResume() // Turn of sorting when we leave the first page - meetingViewModel.speakerUpdateLiveData.enableSorting(pageIndex == 0) + gridViewModel.speakerUpdateLiveData.enableSorting(pageIndex == 0) } - private fun getCurrentPageVideos(tracks: List): List { - val pageVideos = ArrayList() + private fun getCurrentPageVideos(tracks: List): List { + val pageVideos = ArrayList() // Range is [fromIndex, toIndex] -- Notice the bounds val itemsCount = maxItems @@ -72,7 +69,7 @@ class VideoGridPageFragment : VideoGridBaseFragment() { //don't update if row and column are same if (shouldUpdate.not()) return setVideoGridRowsAndColumns(rowCount, columnCount) - meetingViewModel.speakerUpdateLiveData.refresh(rowCount, columnCount) + gridViewModel.speakerUpdateLiveData.refresh(rowCount, columnCount) } override fun onActivityCreated(savedInstanceState: Bundle?) { @@ -84,11 +81,11 @@ class VideoGridPageFragment : VideoGridBaseFragment() { override fun initViewModels() { super.initViewModels() if (isScreenShare.not()) { - meetingViewModel.speakerUpdateLiveData.observe(viewLifecycleOwner) { videoGridTrack -> + gridViewModel.speakerUpdateLiveData.observe(viewLifecycleOwner) { videoGridTrack -> renderCurrentPage(videoGridTrack) } } else { - meetingViewModel.tracks.observe(viewLifecycleOwner) { track -> + gridViewModel.getTrackLiveData().observe(viewLifecycleOwner) { track -> val screenShareTrack = track.filter { it.isScreen }.toList() renderCurrentPage(screenShareTrack) } @@ -97,7 +94,7 @@ class VideoGridPageFragment : VideoGridBaseFragment() { //Don't register listener if it's not screen share if (isScreenShare.not()){ - meetingViewModel.updateRowAndColumnSpanForVideoPeerGrid.observe(viewLifecycleOwner) { (rowCount, columnCount) -> + gridViewModel.updateRowAndColumnSpanForVideoPeerGrid.observe(viewLifecycleOwner) { (rowCount, columnCount) -> refreshGridRowsAndColumns(rowCount, columnCount) } } @@ -109,7 +106,7 @@ class VideoGridPageFragment : VideoGridBaseFragment() { return isScreenShare } - private fun renderCurrentPage(tracks: List) { + private fun renderCurrentPage(tracks: List) { val videos = getCurrentPageVideos(tracks) updateVideos(binding.container, videos, false) } diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageItem.kt b/videogrid/src/main/java/live/hms/videogrid/VideoGridPageItem.kt similarity index 84% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageItem.kt rename to videogrid/src/main/java/live/hms/videogrid/VideoGridPageItem.kt index e4f24d3ef..a94267d23 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPageItem.kt +++ b/videogrid/src/main/java/live/hms/videogrid/VideoGridPageItem.kt @@ -1,6 +1,6 @@ -package live.hms.roomkit.ui.meeting.videogrid +package live.hms.videogrid -import live.hms.roomkit.ui.meeting.MeetingTrack +import live.hms.video.sdk.reactive.MeetingTrack data class VideoGridPageItem( val id: Long, diff --git a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPagerDiffUtil.kt b/videogrid/src/main/java/live/hms/videogrid/VideoGridPagerDiffUtil.kt similarity index 94% rename from room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPagerDiffUtil.kt rename to videogrid/src/main/java/live/hms/videogrid/VideoGridPagerDiffUtil.kt index 925b96aa8..916ef302a 100644 --- a/room-kit/src/main/java/live/hms/roomkit/ui/meeting/videogrid/VideoGridPagerDiffUtil.kt +++ b/videogrid/src/main/java/live/hms/videogrid/VideoGridPagerDiffUtil.kt @@ -1,4 +1,4 @@ -package live.hms.roomkit.ui.meeting.videogrid +package live.hms.videogrid import androidx.recyclerview.widget.DiffUtil diff --git a/videogrid/src/main/java/live/hms/videogrid/utils/Ext.kt b/videogrid/src/main/java/live/hms/videogrid/utils/Ext.kt new file mode 100644 index 000000000..45c8c7e5d --- /dev/null +++ b/videogrid/src/main/java/live/hms/videogrid/utils/Ext.kt @@ -0,0 +1,64 @@ +package live.hms.videogrid.utils + +import android.content.Context +import android.util.Log +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.viewbinding.ViewBinding +import live.hms.video.media.tracks.HMSVideoTrack +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +fun HMSVideoTrack?.isValid(): Boolean { + return !(this == null || this.isMute || this.isDegraded) +} + +fun visibilityOpacity(show: Boolean) = if (show) { + 1.0f +} else { + 0.0f +} + + +fun Fragment.viewLifecycle(): ReadWriteProperty = + object : ReadWriteProperty, DefaultLifecycleObserver { + + private var binding: T? = null + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + + + binding = null + owner.lifecycle.removeObserver(this) + } + + override fun getValue(thisRef: Fragment, property: KProperty<*>): T { + return this.binding ?: error("Called before onCreateView or after onDestroyView.") + } + + override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) { + if (this.binding != null) { + error("ViewBindingLifecycleExtension: binding already initialized to $binding") + } + + this.binding = value + + // Observe the view lifecycle of the Fragment. + // The view lifecycle owner is null before onCreateView and after onDestroyView. + // The observer is automatically removed after the onDestroy event. + thisRef.viewLifecycleOwnerLiveData.observe(thisRef.viewLifecycleOwner) { + it.lifecycle.addObserver(this) + } + + } + } + +fun Fragment.contextSafe(funCall: (context: Context, activity: FragmentActivity) -> Unit) { + if (context != null && activity != null && activity?.isFinishing == false && isAdded) { + funCall.invoke(context!!, activity!!) + } +} + diff --git a/videogrid/src/main/res/layout/hms_fragment_video_grid.xml b/videogrid/src/main/res/layout/hms_fragment_video_grid.xml new file mode 100644 index 000000000..fa24034ff --- /dev/null +++ b/videogrid/src/main/res/layout/hms_fragment_video_grid.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/videogrid/src/main/res/layout/hms_grid_item.xml b/videogrid/src/main/res/layout/hms_grid_item.xml new file mode 100644 index 000000000..3e2509b53 --- /dev/null +++ b/videogrid/src/main/res/layout/hms_grid_item.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/videogrid/src/main/res/layout/hms_video_card.xml b/videogrid/src/main/res/layout/hms_video_card.xml new file mode 100644 index 000000000..dafefb44c --- /dev/null +++ b/videogrid/src/main/res/layout/hms_video_card.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/videogrid/src/test/java/live/hms/videogrid/ExampleUnitTest.kt b/videogrid/src/test/java/live/hms/videogrid/ExampleUnitTest.kt new file mode 100644 index 000000000..733785aab --- /dev/null +++ b/videogrid/src/test/java/live/hms/videogrid/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package live.hms.videogrid + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file