Skip to content

Commit

Permalink
支持滑动垂直屏幕调整音量
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Mar 30, 2024
1 parent 7276268 commit 6165b97
Show file tree
Hide file tree
Showing 9 changed files with 346 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package me.him188.ani.app.platform

import android.media.AudioManager as SystemAudioManager

actual fun getComponentAccessorsImpl(context: Context): PlatformComponentAccessors =
AndroidPlatformComponentAccessors(context)

private class AndroidPlatformComponentAccessors(
private val context: Context
) : PlatformComponentAccessors {
override val audioManager: AudioManager by lazy {
AndroidAudioManager(
context.getSystemService(Context.AUDIO_SERVICE) as SystemAudioManager
)
}
}

private class AndroidAudioManager(
private val delegate: SystemAudioManager,
) : AudioManager {
private val StreamType.android: Int
get() {
return when (this) {
StreamType.MUSIC -> SystemAudioManager.STREAM_MUSIC
}
}

override fun getVolume(streamType: StreamType): Float {
return delegate.getStreamVolume(streamType.android).toFloat() / delegate.getStreamMaxVolume(streamType.android)
}

override fun setVolume(streamType: StreamType, levelPercentage: Float) {
val max = delegate.getStreamMaxVolume(streamType.android)
return delegate.setStreamVolume(
streamType.android,
(levelPercentage * max).toInt()
.coerceIn(minimumValue = 0, maximumValue = max),
SystemAudioManager.FLAG_SHOW_UI
)
}
}
14 changes: 14 additions & 0 deletions app/shared/foundation/common/platform/AudioManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.him188.ani.app.platform

interface AudioManager {
/**
* @return 0..1
*/
fun getVolume(streamType: StreamType): Float

fun setVolume(streamType: StreamType, levelPercentage: Float)
}

enum class StreamType {
MUSIC,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package me.him188.ani.app.platform

interface PlatformComponentAccessors {
val audioManager: AudioManager?
}

fun getComponentAccessors(
context: Context
): PlatformComponentAccessors = getComponentAccessorsImpl(context)

expect fun getComponentAccessorsImpl(
context: Context
): PlatformComponentAccessors
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package me.him188.ani.app.platform

actual fun getComponentAccessorsImpl(context: Context): PlatformComponentAccessors = TODO("Not yet implemented")
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.him188.ani.app.videoplayer.ui

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -53,7 +54,8 @@ private fun PreviewVideoScaffoldFullscreen() = ProvideCompositionLocalsForPrevie

},
onClickScreen = {},
onDoubleClickScreen = {}
onDoubleClickScreen = {},
Modifier.fillMaxSize()
)
},
floatingMessage = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import me.him188.ani.app.ui.foundation.ProvideCompositionLocalsForPreview
import me.him188.ani.app.ui.theme.aniDarkColorTheme
import me.him188.ani.app.ui.theme.slightlyWeaken
import me.him188.ani.app.videoplayer.DummyPlayerController
import me.him188.ani.app.videoplayer.ui.top.PlayerTopBar


/**
Expand Down Expand Up @@ -78,7 +79,7 @@ internal fun PreviewPlayerControllerOverlayImpl() {
Box(modifier = Modifier.background(Color.Black)) {
PlayerControllerOverlay(
topBar = {
PlayerNavigationBar(
PlayerTopBar(
title = null,
actions = {
},
Expand Down
182 changes: 182 additions & 0 deletions app/shared/video-player/commonMain/ui/guesture/SteppedDraggable.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package me.him188.ani.app.videoplayer.ui.guesture

import androidx.annotation.MainThread
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.DragScope
import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Dp
import kotlinx.coroutines.CoroutineScope


interface SteppedDraggableState : DraggableState {
fun onDragStarted(offset: Offset, orientation: Orientation)
fun onDragStopped(velocity: Float)
}

enum class StepDirection {
/**
* - [Orientation.Horizontal]: To the right
* - [Orientation.Vertical]: Down
*/
FORWARD,
BACKWARD,
}

private class SteppedDraggableStateImpl(
@MainThread private val onStep: (StepDirection) -> Unit,
private val stepSizePx: Float,
) : SteppedDraggableState {
var startOffset: Float by mutableFloatStateOf(Float.NaN)
var currentOffset: Float by mutableFloatStateOf(0f)
var lastCallbackOffset: Float by mutableFloatStateOf(0f)
override fun onDragStarted(offset: Offset, orientation: Orientation) {
startOffset = if (orientation == Orientation.Horizontal) {
offset.x
} else {
offset.y
}
currentOffset = startOffset
}

override fun onDragStopped(velocity: Float) {
startOffset = Float.NaN
currentOffset = 0f
}

override fun dispatchRawDelta(delta: Float) {
draggableState.dispatchRawDelta(delta)
}

override suspend fun drag(dragPriority: MutatePriority, block: suspend DragScope.() -> Unit) {
draggableState.drag(dragPriority, block)
}

private val draggableState: DraggableState = DraggableState { delta ->
currentOffset += delta
val deltaOffset = currentOffset - startOffset
val step = (deltaOffset / stepSizePx).toInt()
val callbackOffset = step * stepSizePx
if (callbackOffset != lastCallbackOffset) {
if (callbackOffset > lastCallbackOffset) {
onStep(StepDirection.BACKWARD) // delta is inverted
} else {
onStep(StepDirection.FORWARD)
}
lastCallbackOffset = callbackOffset
}
}
}

@Composable
fun rememberSteppedDraggableState(
stepSize: Dp,
@MainThread onStep: (StepDirection) -> Unit,
): SteppedDraggableState {
val onStepState by rememberUpdatedState(onStep)
val stepSizePx by rememberUpdatedState(with(LocalDensity.current) { stepSize.toPx() })
return remember {
SteppedDraggableStateImpl(
onStep = { onStepState(it) },
stepSizePx = stepSizePx,
)
}
}

fun Modifier.steppedDraggable(
state: SteppedDraggableState,
orientation: Orientation,
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = null,
startDragImmediately: Boolean = false,
onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
reverseDirection: Boolean = false,
): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = "steppedDraggable"
properties["state"] = state
properties["orientation"] = orientation
properties["enabled"] = enabled
properties["interactionSource"] = interactionSource
properties["startDragImmediately"] = startDragImmediately
properties["onDragStarted"] = onDragStarted
properties["onDragStopped"] = onDragStopped
properties["reverseDirection"] = reverseDirection
}
) {
val onDragStartedState by rememberUpdatedState(onDragStarted)
val onDragStoppedState by rememberUpdatedState(onDragStopped)
draggable(
state = state,
orientation = orientation,
enabled = enabled,
interactionSource = interactionSource,
startDragImmediately = startDragImmediately,
onDragStarted = { offset ->
state.onDragStarted(offset, orientation)
onDragStartedState(offset)
},
onDragStopped = {
state.onDragStopped(it)
onDragStoppedState(it)
},
reverseDirection = reverseDirection,
)
}


//fun Modifier.combinedSteppedDraggable(
// division: List<Pair<Float, SteppedDraggableState>>,
// orientation: Orientation,
// enabled: Boolean = true,
// interactionSource: MutableInteractionSource? = null,
// startDragImmediately: Boolean = false,
// onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit = {},
// onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit = {},
// reverseDirection: Boolean = false,
//): Modifier = composed(
// inspectorInfo = debugInspectorInfo {
// name = "steppedDraggable"
// properties["division"] = division
// properties["orientation"] = orientation
// properties["enabled"] = enabled
// properties["interactionSource"] = interactionSource
// properties["startDragImmediately"] = startDragImmediately
// properties["onDragStarted"] = onDragStarted
// properties["onDragStopped"] = onDragStopped
// properties["reverseDirection"] = reverseDirection
// }
//) {
// val onDragStartedState by rememberUpdatedState(onDragStarted)
// val onDragStoppedState by rememberUpdatedState(onDragStopped)
// draggable(
// state = state.draggableState,
// orientation = orientation,
// enabled = enabled,
// interactionSource = interactionSource,
// startDragImmediately = startDragImmediately,
// onDragStarted = { offset ->
// state.onDragStarted(offset, orientation)
// onDragStartedState(offset)
// },
// onDragStopped = {
// state.onDragStopped(it)
// onDragStoppedState(it)
// },
// reverseDirection = reverseDirection,
// )
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package me.him188.ani.app.videoplayer.ui.guesture

import androidx.annotation.MainThread
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.Dp
import me.him188.ani.app.platform.AudioManager
import me.him188.ani.app.platform.StreamType

interface LevelController {
@MainThread
fun increaseLevel()

@MainThread
fun decreaseLevel()
}

fun AudioManager.asLevelController(
streamType: StreamType,
): LevelController = object : LevelController {
override fun increaseLevel() {
val current = getVolume(streamType)
setVolume(streamType, (current + 0.05f).coerceAtMost(1f))
}

override fun decreaseLevel() {
val current = getVolume(streamType)
setVolume(streamType, (current - 0.05f).coerceAtLeast(0f))
}
}

fun Modifier.swipeLevelControl(
controller: LevelController,
stepSize: Dp,
orientation: Orientation,
): Modifier = composed(
inspectorInfo = debugInspectorInfo {
name = "swipeLevelControl"
properties["controller"] = controller
properties["stepSize"] = stepSize
properties["orientation"] = orientation
}
) {
steppedDraggable(
rememberSteppedDraggableState(
stepSize = stepSize,
onStep = { direction ->
when (direction) {
StepDirection.FORWARD -> controller.increaseLevel()
StepDirection.BACKWARD -> controller.decreaseLevel()
}
},
),
orientation = orientation,
)

}
Loading

0 comments on commit 6165b97

Please sign in to comment.