优化视频播放器UI
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
2025.10.07 新增文本朗读器
|
2025.10.07 新增文本朗读器,优化了视频播放器UI
|
||||||
2025.10.06 更新了主页面UI,移除IPA生成与文本朗读功能,新增全语言翻译器
|
2025.10.06 更新了主页面UI,移除IPA生成与文本朗读功能,新增全语言翻译器
|
||||||
2025.10.05 新增IPA生成与文本朗读功能
|
2025.10.05 新增IPA生成与文本朗读功能
|
||||||
2025.09.25 优化了主界面UI
|
2025.09.25 优化了主界面UI
|
||||||
|
|||||||
53
src/app/srt-player/UploadArea.tsx
Normal file
53
src/app/srt-player/UploadArea.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import Button from "@/components/Button";
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
|
||||||
|
export default function UploadArea(
|
||||||
|
{
|
||||||
|
setVideoUrl,
|
||||||
|
setSrtUrl
|
||||||
|
}: {
|
||||||
|
setVideoUrl: (url: string | null) => void;
|
||||||
|
setSrtUrl: (url: string | null) => void;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [videoFile, setVideoFile] = useState<File | null>(null);
|
||||||
|
const [SrtFile, setSrtFile] = useState<File | null>(null);
|
||||||
|
|
||||||
|
const uploadVideo = () => {
|
||||||
|
const input = inputRef.current;
|
||||||
|
if (input) {
|
||||||
|
input.setAttribute('accept', 'video/*');
|
||||||
|
input.click();
|
||||||
|
input.onchange = () => {
|
||||||
|
const file = input.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setVideoFile(file);
|
||||||
|
setVideoUrl(URL.createObjectURL(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uploadSRT = () => {
|
||||||
|
const input = inputRef.current;
|
||||||
|
if (input) {
|
||||||
|
input.setAttribute('accept', '.srt');
|
||||||
|
input.click();
|
||||||
|
input.onchange = () => {
|
||||||
|
const file = input.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setSrtFile(file);
|
||||||
|
setSrtUrl(URL.createObjectURL(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="w-full flex flex-col gap-2 m-2">
|
||||||
|
<Button label="上传视频" onClick={uploadVideo} />
|
||||||
|
<Button label="上传字幕" onClick={uploadSRT} />
|
||||||
|
<input type="file" className="hidden" ref={inputRef} />
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ export default function SubtitleDisplay({ subtitle }: { subtitle: string }) {
|
|||||||
const words = subtitle.match(/\b[\w']+(?:-[\w']+)*\b/g) || [];
|
const words = subtitle.match(/\b[\w']+(?:-[\w']+)*\b/g) || [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return (
|
return (
|
||||||
<div className="subtitle overflow-y-auto h-16 mt-2 break-words bg-black/50 font-sans text-white text-center text-2xl">
|
<div className="w-full subtitle overflow-auto h-16 mt-2 break-words bg-black/50 font-sans text-white text-center text-2xl">
|
||||||
{
|
{
|
||||||
words.map((v) => (
|
words.map((v) => (
|
||||||
<span
|
<span
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useRef, forwardRef, useEffect, KeyboardEvent, useCallback } from "react";
|
import { useState, useRef, forwardRef, useEffect, KeyboardEvent, useCallback } from "react";
|
||||||
import Button from "../../../../components/Button";
|
|
||||||
import { getIndex, getNearistIndex, parseSrt } from "../../subtitle";
|
|
||||||
import SubtitleDisplay from "./SubtitleDisplay";
|
import SubtitleDisplay from "./SubtitleDisplay";
|
||||||
|
import Button from "@/components/Button";
|
||||||
|
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||||
|
|
||||||
type VideoPanelProps = {
|
type VideoPanelProps = {
|
||||||
videoUrl: string | null;
|
videoUrl: string | null;
|
||||||
@@ -154,15 +154,15 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>((
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-9/12" onKeyDown={handleKeyDownEvent}>
|
<div className="w-full flex flex-col" onKeyDown={handleKeyDownEvent}>
|
||||||
<video className="w-12/12" ref={videoRef} onTimeUpdate={timeUpdate}></video>
|
<video className="bg-gray-200" ref={videoRef} onTimeUpdate={timeUpdate}></video>
|
||||||
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
||||||
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
||||||
<Button label={isPlaying ? 'PAUSE' : 'PLAY'} onClick={togglePlayPause}></Button>
|
<Button label={isPlaying ? '暂停' : '播放'} onClick={togglePlayPause}></Button>
|
||||||
<Button label="NEXT" onClick={next}></Button>
|
<Button label="上句" onClick={previous}></Button>
|
||||||
<Button label="PREVIOUS" onClick={previous}></Button>
|
<Button label="下句" onClick={next}></Button>
|
||||||
<Button label="RESTART" onClick={restart}></Button>
|
<Button label="句首" onClick={restart}></Button>
|
||||||
<Button label={`AUTOPAUSE(${autoPause ? 'Y' : 'N'})`} onClick={handleAutoPauseToggle}></Button>
|
<Button label={`自动播放(${autoPause ? '是' : '否'})`} onClick={handleAutoPauseToggle}></Button>
|
||||||
</div>
|
</div>
|
||||||
<input className="seekbar" type="range" min={0} max={srtLength} onChange={handleSeek} step={1} value={progress}></input>
|
<input className="seekbar" type="range" min={0} max={srtLength} onChange={handleSeek} step={1} value={progress}></input>
|
||||||
<span>{spanText}</span>
|
<span>{spanText}</span>
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { useRef, useState } from "react";
|
|
||||||
import UploadArea from "./UploadArea";
|
|
||||||
import VideoPanel from "./VideoPlayer/VideoPanel";
|
|
||||||
|
|
||||||
export default function AppCard() {
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
|
||||||
|
|
||||||
const [videoUrl, setVideoUrl] = useState<string | null>(null);
|
|
||||||
const [srtUrl, setSrtUrl] = useState<string | null>(null);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-w-[410px] max-w-[1000px] w-10/12 flex items-center flex-col bg-gray-200 rounded shadow-2xl py-12">
|
|
||||||
<p className="text-4xl font-extrabold">SRT Video Player</p>
|
|
||||||
<VideoPanel
|
|
||||||
videoUrl={videoUrl}
|
|
||||||
srtUrl={srtUrl}
|
|
||||||
ref={videoRef} />
|
|
||||||
<UploadArea
|
|
||||||
setVideoUrl={setVideoUrl}
|
|
||||||
setSrtUrl={setSrtUrl} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { useRef, useState } from "react";
|
|
||||||
import Button from "../../../components/Button";
|
|
||||||
|
|
||||||
export default function UploadArea(
|
|
||||||
{
|
|
||||||
setVideoUrl,
|
|
||||||
setSrtUrl
|
|
||||||
}: {
|
|
||||||
setVideoUrl: (url: string | null) => void;
|
|
||||||
setSrtUrl: (url: string | null) => void;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
const [videoFile, setVideoFile] = useState<File | null>(null);
|
|
||||||
const [SrtFile, setSrtFile] = useState<File | null>(null);
|
|
||||||
|
|
||||||
const uploadVideo = () => {
|
|
||||||
const input = inputRef.current;
|
|
||||||
if (input) {
|
|
||||||
input.setAttribute('accept', 'video/*');
|
|
||||||
input.click();
|
|
||||||
input.onchange = () => {
|
|
||||||
const file = input.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
setVideoFile(file);
|
|
||||||
setVideoUrl(URL.createObjectURL(file));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const uploadSRT = () => {
|
|
||||||
const input = inputRef.current;
|
|
||||||
if (input) {
|
|
||||||
input.setAttribute('accept', '.srt');
|
|
||||||
input.click();
|
|
||||||
input.onchange = () => {
|
|
||||||
const file = input.files?.[0];
|
|
||||||
if (file) {
|
|
||||||
setSrtFile(file);
|
|
||||||
setSrtUrl(URL.createObjectURL(file));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<table className="border border-black border-collapse">
|
|
||||||
<thead>
|
|
||||||
<tr className="divide-x divide-black">
|
|
||||||
<th className="border border-black px-2 py-1">File Name</th>
|
|
||||||
<th className="border border-black px-2 py-1">Type</th>
|
|
||||||
<th className="border border-black px-2 py-1">Size</th>
|
|
||||||
<th className="border border-black px-2 py-1">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-black">
|
|
||||||
<tr className="divide-x divide-black">
|
|
||||||
<td className="px-2 py-1">{videoFile?.name}</td>
|
|
||||||
<td className="px-2 py-1">Video</td>
|
|
||||||
<td className="px-2 py-1">{videoFile ? (videoFile.size / 1024 / 1024).toFixed(2) + 'MB' : null}</td>
|
|
||||||
<td className="px-2 py-1"><Button label="Upload" onClick={uploadVideo} /></td>
|
|
||||||
</tr>
|
|
||||||
<tr className="divide-x divide-black">
|
|
||||||
<td className="px-2 py-1">{SrtFile?.name}</td>
|
|
||||||
<td className="px-2 py-1">SRT</td>
|
|
||||||
<td className="px-2 py-1">{SrtFile ? (SrtFile.size / 1024 / 1024).toFixed(2) + 'MB' : null}</td>
|
|
||||||
<td className="px-2 py-1"><Button label="Upload" onClick={uploadSRT} /></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<input type="file" className="hidden" ref={inputRef} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,25 @@
|
|||||||
import AppCard from "./components/AppCard";
|
'use client';
|
||||||
|
|
||||||
|
import { useRef, useState } from "react";
|
||||||
|
import UploadArea from "./UploadArea";
|
||||||
|
import VideoPanel from "./VideoPlayer/VideoPanel";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
|
||||||
|
const [videoUrl, setVideoUrl] = useState<string | null>(null);
|
||||||
|
const [srtUrl, setSrtUrl] = useState<string | null>(null);
|
||||||
return (
|
return (
|
||||||
<div className="flex w-screen h-screen items-center justify-center">
|
<div className="flex w-screen pt-8 items-center justify-center">
|
||||||
<AppCard />
|
<div className="w-[80vw] md:w-[45vw] flex items-center flex-col">
|
||||||
|
<VideoPanel
|
||||||
|
videoUrl={videoUrl}
|
||||||
|
srtUrl={srtUrl}
|
||||||
|
ref={videoRef} />
|
||||||
|
<UploadArea
|
||||||
|
setVideoUrl={setVideoUrl}
|
||||||
|
setSrtUrl={setSrtUrl} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user