Skip to content

Commit ea742f8

Browse files
committed
feat(player): Implement local subtitle upload functionality
1 parent c909bd3 commit ea742f8

File tree

2 files changed

+58
-23
lines changed

2 files changed

+58
-23
lines changed

composeApp/src/commonMain/kotlin/com/jankinwu/fntv/client/ui/component/common/dialog/AddNasSubtitleDialog.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ fun MainContent(
373373

374374
// 当选中的侧边栏项改变时,加载对应的文件列表
375375
LaunchedEffect(selectedSidebarItem) {
376-
if (selectedSidebarItem.title == "视频所在位置" && selectedSidebarItem.path.isNotEmpty()) {
376+
if (selectedSidebarItem.title == "视频所在位置" && selectedSidebarItem.path.isNotEmpty() && selectedSidebarItem.path.first().isNotEmpty()) {
377377
serverPathViewModel.loadFilesByServerPath(selectedSidebarItem.path.first())
378378
}
379379
}

composeApp/src/commonMain/kotlin/com/jankinwu/fntv/client/ui/screen/PlayerScreen.kt

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ import com.jankinwu.fntv.client.ui.component.player.VideoPlayerProgressBar
8787
import com.jankinwu.fntv.client.ui.component.player.VolumeControl
8888
import com.jankinwu.fntv.client.ui.component.player.formatDuration
8989
import com.jankinwu.fntv.client.ui.providable.IsoTagData
90+
import com.jankinwu.fntv.client.ui.providable.LocalFileInfo
91+
import com.jankinwu.fntv.client.ui.providable.LocalFrameWindowScope
9092
import com.jankinwu.fntv.client.ui.providable.LocalIsoTagData
9193
import com.jankinwu.fntv.client.ui.providable.LocalPlayerManager
9294
import com.jankinwu.fntv.client.ui.providable.LocalStore
@@ -95,6 +97,7 @@ import com.jankinwu.fntv.client.ui.providable.LocalTypography
9597
import com.jankinwu.fntv.client.ui.providable.LocalWindowState
9698
import com.jankinwu.fntv.client.ui.providable.defaultVariableFamily
9799
import com.jankinwu.fntv.client.utils.HiddenPointerIcon
100+
import com.jankinwu.fntv.client.utils.chooseFile
98101
import com.jankinwu.fntv.client.viewmodel.MediaPViewModel
99102
import com.jankinwu.fntv.client.viewmodel.PlayInfoViewModel
100103
import com.jankinwu.fntv.client.viewmodel.PlayPlayViewModel
@@ -103,6 +106,7 @@ import com.jankinwu.fntv.client.viewmodel.PlayerViewModel
103106
import com.jankinwu.fntv.client.viewmodel.StreamViewModel
104107
import com.jankinwu.fntv.client.viewmodel.SubtitleDeleteViewModel
105108
import com.jankinwu.fntv.client.viewmodel.SubtitleMarkViewModel
109+
import com.jankinwu.fntv.client.viewmodel.SubtitleUploadViewModel
106110
import com.jankinwu.fntv.client.viewmodel.TagViewModel
107111
import com.jankinwu.fntv.client.viewmodel.UiState
108112
import com.jankinwu.fntv.client.viewmodel.UserInfoViewModel
@@ -241,12 +245,12 @@ fun PlayerOverlay(
241245
isSpeedControlHovered || isVolumeControlHovered || isQualityControlHovered || isSettingsMenuHovered || isSubtitleControlHovered
242246
val currentPosition by mediaPlayer.currentPositionMillis.collectAsState()
243247
val playerManager = LocalPlayerManager.current
248+
val frameWindowScope = LocalFrameWindowScope.current
244249
val mediaPViewModel: MediaPViewModel = koinViewModel()
245250
val tagViewModel: TagViewModel = koinViewModel()
246251
val playerViewModel: PlayerViewModel = koinViewModel()
247252
val playingInfoCache by playerViewModel.playingInfoCache.collectAsState()
248253
val resetQualityState by mediaPViewModel.resetQualityState.collectAsState()
249-
250254
val iso6391State by tagViewModel.iso6391State.collectAsState()
251255
val iso6392State by tagViewModel.iso6392State.collectAsState()
252256
val iso3166State by tagViewModel.iso3166State.collectAsState()
@@ -312,6 +316,9 @@ fun PlayerOverlay(
312316
val subtitleDeleteViewModel: SubtitleDeleteViewModel = koinViewModel()
313317
val subtitleDeleteState by subtitleDeleteViewModel.uiState.collectAsState()
314318

319+
val subtitleUploadViewModel: SubtitleUploadViewModel = koinViewModel()
320+
val subtitleUploadState by subtitleUploadViewModel.uiState.collectAsState()
321+
315322
val streamViewModel: StreamViewModel = koinViewModel()
316323
val userInfoViewModel: UserInfoViewModel = koinViewModel()
317324

@@ -357,6 +364,13 @@ fun PlayerOverlay(
357364
}
358365
}
359366

367+
LaunchedEffect(subtitleUploadState) {
368+
if (subtitleUploadState is UiState.Success) {
369+
refreshSubtitleList()
370+
subtitleUploadViewModel.clearError()
371+
}
372+
}
373+
360374
var currentResolution by remember { mutableStateOf("") }
361375
var currentBitrate by remember { mutableStateOf<Int?>(null) }
362376

@@ -500,7 +514,8 @@ fun PlayerOverlay(
500514
}
501515
CompositionLocalProvider(
502516
LocalIsoTagData provides isoTagData,
503-
LocalToastManager provides toastManager
517+
LocalToastManager provides toastManager,
518+
LocalFileInfo provides playingInfoCache?.currentFileStream
504519
) {
505520
Box(
506521
modifier = Modifier
@@ -750,7 +765,22 @@ fun PlayerOverlay(
750765
showAddNasSubtitleDialog = true
751766
},
752767
onOpenAddLocalSubtitle = {
753-
// Placeholder for local subtitle
768+
val mediaGuid = playingInfoCache?.currentFileStream?.guid
769+
if (mediaGuid != null) {
770+
val file = chooseFile(
771+
frameWindowScope,
772+
arrayOf("ass", "srt", "vtt", "sub", "ssa"),
773+
"选择字幕文件"
774+
)
775+
file?.let { selectedFile ->
776+
val byteArray = selectedFile.readBytes()
777+
subtitleUploadViewModel.uploadSubtitle(
778+
mediaGuid,
779+
byteArray,
780+
selectedFile.name
781+
)
782+
}
783+
}
754784
},
755785
onSubtitleControlHoverChanged = { isHovered ->
756786
isSubtitleControlHovered = isHovered
@@ -1091,7 +1121,7 @@ fun rememberPlayMediaFunction(
10911121
playerManager,
10921122
mediaGuid,
10931123
currentAudioGuid,
1094-
currentSubtitleGuid
1124+
currentSubtitleGuid,
10951125
) {
10961126
{
10971127
scope.launch {
@@ -1157,6 +1187,22 @@ private suspend fun playMedia(
11571187
}
11581188
val subtitleGuid = currentSubtitleGuid ?: subtitleStream?.guid
11591189
val fileStream = streamInfo.fileStream
1190+
1191+
// 缓存播放信息 (提前初始化,确保LocalFileInfo可用)
1192+
val cache = PlayingInfoCache(
1193+
streamInfo,
1194+
"",
1195+
fileStream,
1196+
videoStream,
1197+
audioStream,
1198+
subtitleStream,
1199+
playInfoResponse.item.guid,
1200+
streamInfo.qualities,
1201+
currentAudioStreamList = streamInfo.audioStreams,
1202+
currentSubtitleStreamList = streamInfo.subtitleStreams
1203+
)
1204+
playerViewModel.updatePlayingInfo(cache)
1205+
11601206
// 显示播放器
11611207
val videoDuration = videoStream.duration * 1000L
11621208
if (playInfoResponse.type == FnTvMediaType.EPISODE.value) {
@@ -1200,19 +1246,8 @@ private suspend fun playMedia(
12001246
}
12011247

12021248
// 缓存播放信息
1203-
val cache = PlayingInfoCache(
1204-
streamInfo,
1205-
playLink,
1206-
fileStream,
1207-
videoStream,
1208-
audioStream,
1209-
subtitleStream,
1210-
playInfoResponse.item.guid,
1211-
streamInfo.qualities,
1212-
currentAudioStreamList = streamInfo.audioStreams,
1213-
currentSubtitleStreamList = streamInfo.subtitleStreams
1214-
)
1215-
playerViewModel.updatePlayingInfo(cache)
1249+
val finalCache = cache.copy(playLink = playLink)
1250+
playerViewModel.updatePlayingInfo(finalCache)
12161251

12171252
logger.i("startPosition: $startPosition")
12181253
// 设置字幕
@@ -1222,12 +1257,12 @@ private suspend fun playMedia(
12221257
} ?: MediaExtraFiles()
12231258
// 启动播放器
12241259
startPlayback(player, playLink, startPosition, extraFiles)
1225-
// 记录播放数据
1226-
callPlayRecord(
1260+
// 调用playRecord接口
1261+
callPlayRecord(
12271262
// itemGuid = guid,
1228-
ts = if ((startPosition / 1000).toInt() == 0) 1 else (startPosition / 1000).toInt(),
1229-
playingInfoCache = cache,
1230-
playRecordViewModel = playRecordViewModel,
1263+
ts = if ((startPosition / 1000).toInt() == 0) 1 else (startPosition / 1000).toInt(),
1264+
playingInfoCache = finalCache,
1265+
playRecordViewModel = playRecordViewModel,
12311266
onSuccess = {
12321267
logger.i("起播时调用playRecord成功")
12331268
},

0 commit comments

Comments
 (0)