Skip to content

Commit

Permalink
feat: 播放器优化
Browse files Browse the repository at this point in the history
- 新增视频信息(链接,分辨率)
- 优化缓冲加载页面
- 优化播放文字提示
  • Loading branch information
Greatwallcorner committed Jun 23, 2024
1 parent f142994 commit 9555763
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 48 deletions.
83 changes: 60 additions & 23 deletions composeApp/src/commonMain/kotlin/com/corner/ui/Player.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
package com.corner.ui

import androidx.compose.animation.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.onClick
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
Expand All @@ -25,6 +22,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
Expand All @@ -34,8 +32,10 @@ import com.corner.bean.SettingStore
import com.corner.catvodcore.viewmodel.GlobalModel
import com.corner.ui.decompose.DetailComponent
import com.corner.ui.player.DefaultControls
import com.corner.ui.player.PlayerState
import com.corner.ui.player.frame.FrameContainer
import com.corner.ui.player.vlcj.VlcjFrameController
import com.corner.ui.scene.Dialog
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.update
Expand Down Expand Up @@ -74,6 +74,7 @@ fun Player(
val showTip = controller.showTip.collectAsState()
val tip = controller.tip.collectAsState()
val videoFullScreen = GlobalModel.videoFullScreen.subscribeAsState()
val showMediaInfoDialog = remember { mutableStateOf(false) }

LaunchedEffect(Unit){
val volume = SettingStore.getCache("playerState")
Expand Down Expand Up @@ -136,10 +137,22 @@ fun Player(
delay(3000)
showCursor.value = false
}
}.onClick{
showControllerBar.value = !showControllerBar.value
}.pointerHoverIcon(PointerIcon(if (!showCursor.value) createEmptyCursor() else Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)))) {
FrameContainer(Modifier.fillMaxSize().focusTarget().focusable().focusRequester(focusRequester), controller)
FrameContainer(Modifier.fillMaxSize().focusTarget().focusable().focusRequester(focusRequester), controller){
showControllerBar.value = !showControllerBar.value
}
AnimatedVisibility(showControllerBar.value,
modifier = Modifier.align(Alignment.TopEnd),
enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut()){
Row (Modifier.height(40.dp).fillMaxWidth(), horizontalArrangement = Arrangement.End){
IconButton(onClick = {
showMediaInfoDialog.value = true
}){
Icon(Icons.Default.Info, contentDescription = "media info", tint = MaterialTheme.colorScheme.primary)
}
}
}
AnimatedVisibility(
showControllerBar.value,
modifier = Modifier.align(Alignment.BottomEnd),
Expand Down Expand Up @@ -170,22 +183,46 @@ fun Player(
)
}
}
MediaInfoDialog(Modifier.fillMaxWidth(0.5f).fillMaxHeight(0.4f), controller.state.value, showMediaInfoDialog.value){
showMediaInfoDialog.value = false
}
}
}

@Composable
fun MediaInfoDialog(modifier: Modifier, playerState: PlayerState, show:Boolean, onClose:()->Unit){
val mediaInfo = rememberUpdatedState(playerState.mediaInfo)
Dialog(modifier, showDialog = show, onClose = onClose){
val scrollbar = rememberLazyListState(0)
LazyColumn(verticalArrangement = Arrangement.spacedBy(30.dp), modifier = Modifier.padding(30.dp), state = scrollbar,
horizontalAlignment = Alignment.CenterHorizontally) {
item {
SelectionContainer {
Text(text = AnnotatedString(mediaInfo.value?.url ?: ""))
}
}
item {
Text("${mediaInfo.value?.width ?: ""} * ${mediaInfo.value?.height ?: ""}")
}
}
VerticalScrollbar(rememberScrollbarAdapter(scrollbar))
}
}

//@Preview
//@Composable
//fun previewMediaInfoDialog(){
// AppTheme {
// MediaInfoDialog(Modifier.fillMaxSize(), MediaInfo(800, 1200, "http://xxxxxx.com/dddd"), true){
//
// }
// }
//}

private fun createEmptyCursor(): Cursor {
return Toolkit.getDefaultToolkit().createCustomCursor(
BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB),
Point(1, 1),
"Empty Cursor"
)
}

enum class PlayState{
INIT,
LOADING,
PLAYING,
REFRESH,
CHANGEEP,

}
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,9 @@ class DefaultDetailComponent(componentContext: ComponentContext) : DetailCompone
detail.subEpisode?.parallelStream()?.forEach {
it.activated = it == ep
}
SnackBar.postMsg(if(internalPlayer) "开始播放" else "上次播放" + ": ${ep.name}")
if(!internalPlayer){
SnackBar.postMsg("上次播放" + ": ${ep.name}")
}
}

override fun nextEP() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ package com.corner.ui.player

data class PlayerState(
val isPlaying: Boolean = false,
val isBuffering:Boolean = false,
val isMuted: Boolean = false,
var isFullScreen: Boolean = false,
val volume: Float = .5f,
val timestamp: Long = 0L,
val duration: Long = 0L,
val speed: Float = 1F,
var opening:Long = -1,
val ending:Long = -1
val ending:Long = -1,
val mediaInfo: MediaInfo? = null,
)

data class MediaInfo(
val height:Int,
val width:Int,
val url:String
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.text.isTypedEvent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
Expand All @@ -30,8 +27,9 @@ import uk.co.caprica.vlcj.player.base.State
fun FrameContainer(
modifier: Modifier = Modifier,
controller: VlcjFrameController,
onClick:()->Unit
) {
val playerState = rememberUpdatedState(controller.getPlayer()?.status()?.state())
val playerState = controller.state.collectAsState()
val bitmap by remember { controller.imageBitmapState }
val interactionSource = remember { MutableInteractionSource() }
Box(modifier = modifier.background(Color.Black)
Expand All @@ -44,6 +42,7 @@ fun FrameContainer(
indication = null
) {
// onClick
onClick()
}
.onPointerEvent(PointerEventType.Scroll) { e ->
val y = e.changes.first().scrollDelta.y
Expand Down Expand Up @@ -86,16 +85,15 @@ fun FrameContainer(
}
}else{
Box(modifier = Modifier.fillMaxSize().background(Color.Black)) {
when (playerState.value?.name ?: "") {
State.BUFFERING.name ->
androidx.compose.material3.CircularProgressIndicator(Modifier.align(Alignment.Center))
else ->
Image(
modifier = Modifier.align(Alignment.Center),
painter = painterResource("/pic/TV-icon-x.png"),
contentDescription = "nothing here",
contentScale = ContentScale.Crop
)
if(playerState.value.isBuffering){
androidx.compose.material3.CircularProgressIndicator(Modifier.align(Alignment.Center))
}else {
Image(
modifier = Modifier.align(Alignment.Center),
painter = painterResource("/pic/TV-icon-x.png"),
contentDescription = "nothing here",
contentScale = ContentScale.Crop
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.corner.catvod.enum.bean.Vod
import com.corner.catvodcore.viewmodel.GlobalModel
import com.corner.database.History
import com.corner.ui.decompose.DetailComponent
import com.corner.ui.player.MediaInfo
import com.corner.ui.player.PlayerController
import com.corner.ui.player.PlayerState
import com.corner.ui.scene.SnackBar
Expand Down Expand Up @@ -41,8 +42,19 @@ class VlcjController(val component: DetailComponent) : PlayerController {
override var tip = MutableStateFlow("")
override var history: MutableStateFlow<History?> = MutableStateFlow(null)
var scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

internal val factory by lazy { MediaPlayerFactory() }
private val vlcjArgs = listOf(
"--no-video-title-show", // 禁用视频标题显示
"--no-snapshot-preview", // 禁用快照预览
"--no-autoscale", // 禁用自动缩放
"--no-disable-screensaver", // 禁用屏保
"--avcodec-fast", // 使用快速解码模式
"--network-caching=3000", // 设置网络缓存为 3000ms
"--file-caching=3000", // 设置文件缓存为 3000ms
"--live-caching=3000", // 设置直播缓存为 3000ms
"--sout-mux-caching=3000" // 设置输出缓存为 3000ms
)

internal val factory by lazy { MediaPlayerFactory(vlcjArgs) }

override fun doWithMediaPlayer(block: (MediaPlayer) -> Unit) {
player?.let {
Expand All @@ -69,6 +81,23 @@ class VlcjController(val component: DetailComponent) : PlayerController {
play()
}

override fun videoOutput(mediaPlayer: MediaPlayer?, newCount: Int) {

val trackInfo = mediaPlayer?.media()?.info()?.videoTracks()?.first()
if(trackInfo != null){
_state.update { it.copy(mediaInfo = MediaInfo(url = mediaPlayer.media()?.info()?.mrl() ?: "", height = trackInfo.height(), width = trackInfo.width())) }
}
}

override fun buffering(mediaPlayer: MediaPlayer?, newCache: Float) {
_state.update { it.copy(isBuffering = newCache != 100F) }
}

override fun opening(mediaPlayer: MediaPlayer?) {
_state.update { it.copy(isBuffering = true) }
}


override fun playing(mediaPlayer: MediaPlayer) {
_state.update { it.copy(isPlaying = true) }
}
Expand Down Expand Up @@ -217,11 +246,6 @@ class VlcjController(val component: DetailComponent) : PlayerController {
tip.emit(text)
showTip.emit(true)
}
// tipJob?.cancel()
// tipJob = scope.launch {
// delay(1000)
// showTip = false
// }
}

override fun stop() = catch {
Expand Down Expand Up @@ -279,7 +303,7 @@ class VlcjController(val component: DetailComponent) : PlayerController {
override fun toggleFullscreen() = catch {
val videoFullScreen = GlobalModel.toggleVideoFullScreen()
runBlocking {
if(videoFullScreen) showTips("[ESC]退出全屏")
if (videoFullScreen) showTips("[ESC]退出全屏")
}
}

Expand Down

0 comments on commit 9555763

Please sign in to comment.