Autoplay controls
This commit is contained in:
@@ -1,16 +1,46 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.media.AudioManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.VolumeOff
|
||||||
|
import androidx.compose.material.icons.filled.VolumeUp
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.LayoutCoordinates
|
||||||
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -20,13 +50,16 @@ import com.google.android.exoplayer2.ExoPlayer
|
|||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.MediaMetadata
|
import com.google.android.exoplayer2.MediaMetadata
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||||
import com.google.android.exoplayer2.ui.StyledPlayerView
|
import com.google.android.exoplayer2.ui.StyledPlayerView
|
||||||
import com.google.android.exoplayer2.util.EventLogger
|
import com.google.android.exoplayer2.util.Util
|
||||||
import com.vitorpamplona.amethyst.VideoCache
|
import com.vitorpamplona.amethyst.VideoCache
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
private var muted = mutableStateOf(true)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoView(localFile: File, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
|
fun VideoView(localFile: File, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
|
||||||
VideoView(localFile.toUri(), description, onDialog)
|
VideoView(localFile.toUri(), description, onDialog)
|
||||||
@@ -42,6 +75,14 @@ fun VideoView(videoUri: Uri, description: String? = null, onDialog: ((Boolean) -
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
|
val lifecycleOwner = rememberUpdatedState(LocalLifecycleOwner.current)
|
||||||
|
|
||||||
|
val myAudioManager =
|
||||||
|
context.applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
|
val myAudioAttributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(C.USAGE_MEDIA)
|
||||||
|
.setContentType(C.AUDIO_CONTENT_TYPE_MOVIE)
|
||||||
|
.build()
|
||||||
|
|
||||||
val exoPlayer = remember(videoUri) {
|
val exoPlayer = remember(videoUri) {
|
||||||
val mediaBuilder = MediaItem.Builder().setUri(videoUri)
|
val mediaBuilder = MediaItem.Builder().setUri(videoUri)
|
||||||
|
|
||||||
@@ -56,6 +97,16 @@ fun VideoView(videoUri: Uri, description: String? = null, onDialog: ((Boolean) -
|
|||||||
ExoPlayer.Builder(context).build().apply {
|
ExoPlayer.Builder(context).build().apply {
|
||||||
repeatMode = Player.REPEAT_MODE_ALL
|
repeatMode = Player.REPEAT_MODE_ALL
|
||||||
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
|
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
|
||||||
|
setAudioAttributes(myAudioAttributes, true)
|
||||||
|
if (Util.SDK_INT >= 23) {
|
||||||
|
myAudioManager.adjustStreamVolume(
|
||||||
|
C.STREAM_TYPE_MUSIC,
|
||||||
|
if (muted.value) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
myAudioManager.setStreamMute(C.STREAM_TYPE_MUSIC, muted.value)
|
||||||
|
}
|
||||||
if (videoUri.scheme?.startsWith("file") == true) {
|
if (videoUri.scheme?.startsWith("file") == true) {
|
||||||
setMediaItem(media)
|
setMediaItem(media)
|
||||||
} else {
|
} else {
|
||||||
@@ -69,28 +120,53 @@ fun VideoView(videoUri: Uri, description: String? = null, onDialog: ((Boolean) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exoPlayer.addAnalyticsListener(EventLogger())
|
|
||||||
|
|
||||||
DisposableEffect(
|
DisposableEffect(
|
||||||
AndroidView(
|
Box() {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
AndroidView(
|
||||||
factory = {
|
modifier = Modifier
|
||||||
StyledPlayerView(context).apply {
|
.fillMaxWidth()
|
||||||
player = exoPlayer
|
.align(Alignment.Center)
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
.onVisibilityChanges { visible ->
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
if (visible) {
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
exoPlayer.play()
|
||||||
)
|
} else {
|
||||||
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
|
||||||
onDialog?.let { innerOnDialog ->
|
|
||||||
setFullscreenButtonClickListener {
|
|
||||||
exoPlayer.pause()
|
exoPlayer.pause()
|
||||||
innerOnDialog(it)
|
}
|
||||||
|
},
|
||||||
|
factory = {
|
||||||
|
StyledPlayerView(context).apply {
|
||||||
|
player = exoPlayer
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
controllerAutoShow = false
|
||||||
|
hideController()
|
||||||
|
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
||||||
|
onDialog?.let { innerOnDialog ->
|
||||||
|
setFullscreenButtonClickListener {
|
||||||
|
exoPlayer.pause()
|
||||||
|
innerOnDialog(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
MuteButton(muted, Modifier) {
|
||||||
|
muted.value = !muted.value
|
||||||
|
if (Util.SDK_INT >= 23) {
|
||||||
|
myAudioManager.adjustStreamVolume(
|
||||||
|
C.STREAM_TYPE_MUSIC,
|
||||||
|
if (muted.value) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
myAudioManager.setStreamMute(C.STREAM_TYPE_MUSIC, muted.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
) {
|
) {
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
@@ -109,3 +185,76 @@ fun VideoView(videoUri: Uri, description: String? = null, onDialog: ((Boolean) -
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Modifier.onVisibilityChanges(onVisibilityChanges: (Boolean) -> Unit): Modifier = composed {
|
||||||
|
val view = LocalView.current
|
||||||
|
var isVisible: Boolean? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(isVisible) {
|
||||||
|
onVisibilityChanges(isVisible == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onGloballyPositioned { coordinates ->
|
||||||
|
isVisible = coordinates.isCompletelyVisible(view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun LayoutCoordinates.isCompletelyVisible(view: View): Boolean {
|
||||||
|
if (!isAttached) return false
|
||||||
|
// Window relative bounds of our compose root view that are visible on the screen
|
||||||
|
val globalRootRect = android.graphics.Rect()
|
||||||
|
if (!view.getGlobalVisibleRect(globalRootRect)) {
|
||||||
|
// we aren't visible at all.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val bounds = boundsInWindow()
|
||||||
|
// Make sure we are completely in bounds.
|
||||||
|
return bounds.top >= globalRootRect.top &&
|
||||||
|
bounds.left >= globalRootRect.left &&
|
||||||
|
bounds.right <= globalRootRect.right &&
|
||||||
|
bounds.bottom <= globalRootRect.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MuteButton(muted: MutableState<Boolean>, modifier: Modifier, toggle: () -> Unit) {
|
||||||
|
Box(
|
||||||
|
modifier
|
||||||
|
.width(70.dp)
|
||||||
|
.height(70.dp)
|
||||||
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.fillMaxSize(0.6f)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.background(MaterialTheme.colors.background)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (muted.value) {
|
||||||
|
IconButton(
|
||||||
|
onClick = toggle,
|
||||||
|
modifier = Modifier.size(50.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.VolumeOff,
|
||||||
|
"Hash Verified",
|
||||||
|
tint = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier.size(30.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IconButton(
|
||||||
|
onClick = toggle,
|
||||||
|
modifier = Modifier.size(50.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.VolumeUp,
|
||||||
|
"Hash Verified",
|
||||||
|
tint = MaterialTheme.colors.onBackground,
|
||||||
|
modifier = Modifier.size(30.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
@@ -10,6 +10,7 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val savedScrollStates = mutableMapOf<String, ScrollState>()
|
private val savedScrollStates = mutableMapOf<String, ScrollState>()
|
||||||
|
|
||||||
private data class ScrollState(val index: Int, val scrollOffsetFraction: Float)
|
private data class ScrollState(val index: Int, val scrollOffsetFraction: Float)
|
||||||
|
|
||||||
object ScrollStateKeys {
|
object ScrollStateKeys {
|
||||||
@@ -45,7 +45,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
|||||||
Reference in New Issue
Block a user