mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
feat(danmaku): 支持快捷填入当前片名并优化重新搜索机制
This commit is contained in:
@@ -28,6 +28,8 @@ type DanmakuStageProps = {
|
||||
search?: string | null
|
||||
/** Explicit danmaku library chosen by the user; null = auto-resolve. */
|
||||
episodeId?: number | string | null
|
||||
/** Counter or token changed to trigger refetch even when search stays identical. */
|
||||
searchTrigger?: number
|
||||
/** Called after each fetch attempt (success or error) finishes with metadata. */
|
||||
onLoaded?: (info: DanmakuLoadedInfo | null) => void
|
||||
/** Called when multiple anime matched and the user must pick one. */
|
||||
@@ -50,6 +52,7 @@ export function DanmakuStage({
|
||||
area = 1,
|
||||
search = null,
|
||||
episodeId = null,
|
||||
searchTrigger = 0,
|
||||
onLoaded,
|
||||
onCandidates,
|
||||
}: DanmakuStageProps) {
|
||||
@@ -252,8 +255,8 @@ export function DanmakuStage({
|
||||
manager.unmount()
|
||||
managerRef.current = null
|
||||
}
|
||||
// search / episodeId 变化时重新拉取弹幕(含媒体/开关切换)。
|
||||
}, [media, videoRef, enabled, search, episodeId, onLoaded, onCandidates])
|
||||
// search / episodeId / searchTrigger 变化时重新拉取弹幕(含媒体/开关切换)。
|
||||
}, [media, videoRef, enabled, search, episodeId, searchTrigger, onLoaded, onCandidates])
|
||||
|
||||
// Live renderer knobs: opacity / area / font size without recreating the
|
||||
// engine. font size additionally rescales currently visible comments.
|
||||
|
||||
@@ -25,6 +25,8 @@ type PlayerDanmakuPanelProps = {
|
||||
candidates: DanmakuAnime[]
|
||||
/** Human-readable label of the currently selected library. */
|
||||
selectedSource?: string
|
||||
/** Title used by auto-matching (e.g. anime title, media title or filename). */
|
||||
autoMatchTitle?: string
|
||||
/** Loaded danmaku metadata (title, episode, count, match mode). */
|
||||
danmakuInfo?: DanmakuLoadedInfo | null
|
||||
onSelectEpisode: (episodeId: number, animeTitle: string, episodeTitle: string) => void
|
||||
@@ -47,6 +49,7 @@ export function PlayerDanmakuPanel({
|
||||
onFontSizeChange,
|
||||
candidates,
|
||||
selectedSource,
|
||||
autoMatchTitle,
|
||||
danmakuInfo,
|
||||
onSelectEpisode,
|
||||
onResetAuto,
|
||||
@@ -203,7 +206,19 @@ export function PlayerDanmakuPanel({
|
||||
|
||||
{/* 搜索弹幕 */}
|
||||
<div className="mb-4">
|
||||
<div className="mb-1 text-xs text-white/60">搜索弹幕(留空 = 按视频名自动匹配)</div>
|
||||
<div className="mb-1 flex items-center justify-between text-xs text-white/60">
|
||||
<span>搜索弹幕(留空 = 按视频名自动匹配)</span>
|
||||
{autoMatchTitle && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDraft(autoMatchTitle)}
|
||||
className="text-[11px] text-rose-300 transition hover:text-rose-200"
|
||||
title="填入当前识别到的视频名"
|
||||
>
|
||||
填入当前名
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<input
|
||||
value={draft}
|
||||
@@ -211,8 +226,8 @@ export function PlayerDanmakuPanel({
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') onSearch(draft.trim())
|
||||
}}
|
||||
placeholder="输入番剧或电影名…"
|
||||
className="min-w-0 flex-1 rounded-lg border border-white/15 bg-white/5 px-2.5 py-1.5 text-xs outline-none placeholder:text-white/35 focus:border-rose-400/60"
|
||||
placeholder={autoMatchTitle ? `自动匹配:${autoMatchTitle}` : '输入番剧或电影名…'}
|
||||
className="min-w-0 flex-1 rounded-lg border border-white/15 bg-white/5 px-2.5 py-1.5 text-xs outline-none placeholder:text-white/40 focus:border-rose-400/60"
|
||||
/>
|
||||
<button
|
||||
onClick={() => onSearch(draft.trim())}
|
||||
|
||||
@@ -74,6 +74,7 @@ export function PlayerPage() {
|
||||
const [danmakuOpen, setDanmakuOpen] = useState(false)
|
||||
const [danmakuEnabled, setDanmakuEnabled] = useState(true)
|
||||
const [danmakuSearch, setDanmakuSearch] = useState<string | null>(null)
|
||||
const [danmakuSearchTrigger, setDanmakuSearchTrigger] = useState(0)
|
||||
const [danmakuSearching, setDanmakuSearching] = useState(false)
|
||||
// 用户从候选列表选定的弹幕库;null = 自动匹配。
|
||||
const [danmakuEpisodeId, setDanmakuEpisodeId] = useState<number | string | null>(null)
|
||||
@@ -141,6 +142,7 @@ export function PlayerPage() {
|
||||
setDanmakuEpisodeId(null)
|
||||
setDanmakuInfo(null)
|
||||
setDanmakuSearch(kw || null)
|
||||
setDanmakuSearchTrigger((prev) => prev + 1)
|
||||
}, [])
|
||||
|
||||
const danmakuLoaded = useCallback((info: DanmakuLoadedInfo | null) => {
|
||||
@@ -164,6 +166,7 @@ export function PlayerPage() {
|
||||
setDanmakuSearching(true)
|
||||
// 展示当前所选来源(面板标题处可见)。
|
||||
setDanmakuSelectedSource(episodeTitle ? `${animeTitle}・${episodeTitle}` : animeTitle)
|
||||
setDanmakuSearchTrigger((prev) => prev + 1)
|
||||
}, [])
|
||||
|
||||
// 回到自动匹配(清除用户手动选择)。
|
||||
@@ -174,6 +177,7 @@ export function PlayerPage() {
|
||||
setDanmakuSearch(null)
|
||||
setDanmakuSelectedSource('')
|
||||
setDanmakuInfo(null)
|
||||
setDanmakuSearchTrigger((prev) => prev + 1)
|
||||
}, [])
|
||||
|
||||
// 切换视频时重置媒体与弹幕状态,确保新视频自动重新识别并加载弹幕
|
||||
@@ -549,6 +553,12 @@ export function PlayerPage() {
|
||||
toast.error('视频播放失败,请检查文件是否存在')
|
||||
}, [directOnly, hlsUnavailable, media, mode, params, setParams])
|
||||
|
||||
const danmakuAutoTitle =
|
||||
danmakuInfo?.animeTitle ||
|
||||
media?.original_name?.trim() ||
|
||||
media?.title?.trim() ||
|
||||
''
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-1 flex-col overflow-hidden bg-black">
|
||||
<PlayerTopBar
|
||||
@@ -580,6 +590,7 @@ export function PlayerPage() {
|
||||
danmakuArea={danmakuArea}
|
||||
danmakuSearch={danmakuSearch}
|
||||
danmakuEpisodeId={danmakuEpisodeId}
|
||||
danmakuSearchTrigger={danmakuSearchTrigger}
|
||||
danmakuOpen={danmakuOpen}
|
||||
onToggleDanmaku={toggleDanmakuOpen}
|
||||
onDanmakuLoaded={danmakuLoaded}
|
||||
@@ -619,6 +630,7 @@ export function PlayerPage() {
|
||||
onFontSizeChange={setDanmakuFontSize}
|
||||
candidates={danmakuCandidates}
|
||||
selectedSource={danmakuSelectedSource}
|
||||
autoMatchTitle={danmakuAutoTitle}
|
||||
danmakuInfo={danmakuInfo}
|
||||
onSelectEpisode={danmakuSelectEpisode}
|
||||
onResetAuto={danmakuResetAuto}
|
||||
|
||||
@@ -24,6 +24,7 @@ type PlayerVideoStageProps = {
|
||||
danmakuArea: number
|
||||
danmakuSearch: string | null
|
||||
danmakuEpisodeId: number | string | null
|
||||
danmakuSearchTrigger?: number
|
||||
danmakuOpen: boolean
|
||||
onToggleDanmaku: () => void
|
||||
onDanmakuLoaded: (info: DanmakuLoadedInfo | null) => void
|
||||
@@ -58,6 +59,7 @@ export function PlayerVideoStage({
|
||||
danmakuArea,
|
||||
danmakuSearch,
|
||||
danmakuEpisodeId,
|
||||
danmakuSearchTrigger = 0,
|
||||
danmakuOpen,
|
||||
onToggleDanmaku,
|
||||
onDanmakuLoaded,
|
||||
@@ -280,6 +282,7 @@ export function PlayerVideoStage({
|
||||
area={danmakuArea}
|
||||
search={danmakuSearch}
|
||||
episodeId={danmakuEpisodeId}
|
||||
searchTrigger={danmakuSearchTrigger}
|
||||
onLoaded={onDanmakuLoaded}
|
||||
onCandidates={onDanmakuCandidates}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user