davidliu
Committed by GitHub

Put back composables for now (#308)

* Put back composables for now

* Spotless
1 ext { 1 ext {
2 android_build_tools_version = '8.0.2' 2 android_build_tools_version = '8.0.2'
3 - compose_version = '1.4.2'  
4 - compose_compiler_version = '1.4.6' 3 + compose_version = '1.2.1'
  4 + compose_compiler_version = '1.4.5'
5 kotlin_version = '1.8.20' 5 kotlin_version = '1.8.20'
6 java_version = JavaVersion.VERSION_17 6 java_version = JavaVersion.VERSION_17
7 dokka_version = '1.5.0' 7 dokka_version = '1.5.0'
@@ -57,8 +57,12 @@ android { @@ -57,8 +57,12 @@ android {
57 } 57 }
58 58
59 buildFeatures { 59 buildFeatures {
  60 + compose true
60 buildConfig = true 61 buildConfig = true
61 } 62 }
  63 + composeOptions {
  64 + kotlinCompilerExtensionVersion compose_compiler_version
  65 + }
62 kotlinOptions { 66 kotlinOptions {
63 freeCompilerArgs = ["-Xinline-classes", "-opt-in=kotlin.RequiresOptIn"] 67 freeCompilerArgs = ["-Xinline-classes", "-opt-in=kotlin.RequiresOptIn"]
64 jvmTarget = java_version 68 jvmTarget = java_version
@@ -149,6 +153,7 @@ dependencies { @@ -149,6 +153,7 @@ dependencies {
149 implementation deps.androidx.annotation 153 implementation deps.androidx.annotation
150 implementation "androidx.core:core:${versions.androidx_core}" 154 implementation "androidx.core:core:${versions.androidx_core}"
151 implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}" 155 implementation "com.google.protobuf:protobuf-javalite:${versions.protobuf}"
  156 + implementation "androidx.compose.ui:ui:$compose_version"
152 157
153 implementation 'javax.sip:android-jain-sip-ri:1.3.0-91' 158 implementation 'javax.sip:android-jain-sip-ri:1.3.0-91'
154 159
  1 +/*
  2 + * Copyright 2023 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package io.livekit.android.compose
  18 +
  19 +import androidx.compose.runtime.Composable
  20 +import androidx.compose.runtime.DisposableEffect
  21 +import androidx.compose.runtime.currentCompositeKeyHash
  22 +import androidx.compose.runtime.getValue
  23 +import androidx.compose.runtime.mutableStateOf
  24 +import androidx.compose.runtime.remember
  25 +import androidx.compose.runtime.setValue
  26 +import androidx.compose.ui.Modifier
  27 +import androidx.compose.ui.layout.onGloballyPositioned
  28 +import androidx.compose.ui.viewinterop.AndroidView
  29 +import io.livekit.android.renderer.TextureViewRenderer
  30 +import io.livekit.android.room.Room
  31 +import io.livekit.android.room.track.RemoteVideoTrack
  32 +import io.livekit.android.room.track.VideoTrack
  33 +import io.livekit.android.room.track.video.ComposeVisibility
  34 +
  35 +/**
  36 + * Widget for displaying a VideoTrack. Handles the Compose <-> AndroidView interop needed to use
  37 + * [TextureViewRenderer].
  38 + */
  39 +@Composable
  40 +fun VideoRenderer(
  41 + room: Room,
  42 + videoTrack: VideoTrack?,
  43 + modifier: Modifier = Modifier,
  44 + mirror: Boolean = false,
  45 +) {
  46 + val videoSinkVisibility = remember(room, videoTrack) { ComposeVisibility() }
  47 + var boundVideoTrack by remember { mutableStateOf<VideoTrack?>(null) }
  48 + var view: TextureViewRenderer? by remember { mutableStateOf(null) }
  49 +
  50 + fun cleanupVideoTrack() {
  51 + view?.let { boundVideoTrack?.removeRenderer(it) }
  52 + boundVideoTrack = null
  53 + }
  54 +
  55 + fun setupVideoIfNeeded(videoTrack: VideoTrack?, view: TextureViewRenderer) {
  56 + if (boundVideoTrack == videoTrack) {
  57 + return
  58 + }
  59 +
  60 + cleanupVideoTrack()
  61 +
  62 + boundVideoTrack = videoTrack
  63 + if (videoTrack != null) {
  64 + if (videoTrack is RemoteVideoTrack) {
  65 + videoTrack.addRenderer(view, videoSinkVisibility)
  66 + } else {
  67 + videoTrack.addRenderer(view)
  68 + }
  69 + }
  70 + }
  71 +
  72 + DisposableEffect(view, mirror) {
  73 + view?.setMirror(mirror)
  74 + onDispose { }
  75 + }
  76 +
  77 + DisposableEffect(room, videoTrack) {
  78 + onDispose {
  79 + videoSinkVisibility.onDispose()
  80 + cleanupVideoTrack()
  81 + }
  82 + }
  83 +
  84 + DisposableEffect(currentCompositeKeyHash.toString()) {
  85 + onDispose {
  86 + view?.release()
  87 + }
  88 + }
  89 +
  90 + AndroidView(
  91 + factory = { context ->
  92 + TextureViewRenderer(context).apply {
  93 + room.initVideoRenderer(this)
  94 + setupVideoIfNeeded(videoTrack, this)
  95 + view = this
  96 + }
  97 + },
  98 + update = { v -> setupVideoIfNeeded(videoTrack, v) },
  99 + modifier = modifier
  100 + .onGloballyPositioned { videoSinkVisibility.onGloballyPositioned(it) },
  101 + )
  102 +}
  1 +/*
  2 + * Copyright 2023 LiveKit, Inc.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package io.livekit.android.room.track.video
  18 +
  19 +import androidx.compose.ui.layout.LayoutCoordinates
  20 +import io.livekit.android.room.track.Track
  21 +
  22 +/**
  23 + * A [VideoSinkVisibility] for compose views.
  24 + *
  25 + * To use, pass an `onGloballyPositioned` modifier your composable like so:
  26 + * ```
  27 + * modifier = Modifier.onGloballyPositioned { videoSinkVisibility.onGloballyPositioned(it) }
  28 + * ```
  29 + */
  30 +class ComposeVisibility : VideoSinkVisibility() {
  31 + private var coordinates: LayoutCoordinates? = null
  32 +
  33 + private var lastVisible = isVisible()
  34 + private var lastSize = size()
  35 + override fun isVisible(): Boolean {
  36 + return (coordinates?.isAttached == true &&
  37 + coordinates?.size?.width != 0 &&
  38 + coordinates?.size?.height != 0)
  39 + }
  40 +
  41 + override fun size(): Track.Dimensions {
  42 + val width = coordinates?.size?.width ?: 0
  43 + val height = coordinates?.size?.height ?: 0
  44 + return Track.Dimensions(width, height)
  45 + }
  46 +
  47 + // Note, LayoutCoordinates are mutable and may be reused.
  48 + fun onGloballyPositioned(layoutCoordinates: LayoutCoordinates) {
  49 + coordinates = layoutCoordinates
  50 + val visible = isVisible()
  51 + val size = size()
  52 +
  53 + if (lastVisible != visible || lastSize != size) {
  54 + notifyChanged()
  55 + }
  56 +
  57 + lastVisible = visible
  58 + lastSize = size
  59 + }
  60 +
  61 + fun onDispose() {
  62 + if (coordinates == null) {
  63 + return
  64 + }
  65 + coordinates = null
  66 + notifyChanged()
  67 + }
  68 +}
@@ -26,6 +26,11 @@ import io.livekit.android.room.track.Track @@ -26,6 +26,11 @@ import io.livekit.android.room.track.Track
26 import io.livekit.android.room.track.video.ViewVisibility.Notifier 26 import io.livekit.android.room.track.video.ViewVisibility.Notifier
27 import java.util.Observable 27 import java.util.Observable
28 28
  29 +/**
  30 + * Provides the visibility and dimensions of the video sink, allowing LiveKit
  31 + * to automatically manage the quality of the stream when adaptive streaming
  32 + * is used.
  33 + */
29 abstract class VideoSinkVisibility : Observable() { 34 abstract class VideoSinkVisibility : Observable() {
30 abstract fun isVisible(): Boolean 35 abstract fun isVisible(): Boolean
31 abstract fun size(): Track.Dimensions 36 abstract fun size(): Track.Dimensions
@@ -83,9 +88,12 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() { @@ -83,9 +88,12 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() {
83 88
84 private fun scheduleRecalculate() { 89 private fun scheduleRecalculate() {
85 handler.removeCallbacksAndMessages(null) 90 handler.removeCallbacksAndMessages(null)
86 - handler.postDelayed({ 91 + handler.postDelayed(
  92 + {
87 recalculate() 93 recalculate()
88 - }, 2000) 94 + },
  95 + 2000,
  96 + )
89 } 97 }
90 98
91 fun recalculate() { 99 fun recalculate() {
@@ -29,7 +29,7 @@ import androidx.compose.ui.Alignment @@ -29,7 +29,7 @@ import androidx.compose.ui.Alignment
29 import androidx.compose.ui.Modifier 29 import androidx.compose.ui.Modifier
30 import androidx.compose.ui.graphics.Color 30 import androidx.compose.ui.graphics.Color
31 import androidx.compose.ui.res.painterResource 31 import androidx.compose.ui.res.painterResource
32 -import io.livekit.android.composesample.ui.VideoRenderer 32 +import io.livekit.android.compose.VideoRenderer
33 import io.livekit.android.room.Room 33 import io.livekit.android.room.Room
34 import io.livekit.android.room.participant.Participant 34 import io.livekit.android.room.participant.Participant
35 import io.livekit.android.room.track.CameraPosition 35 import io.livekit.android.room.track.CameraPosition