优化视频播放器UI
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
2025.10.07 新增文本朗读器
|
||||
2025.10.07 新增文本朗读器,优化了视频播放器UI
|
||||
2025.10.06 更新了主页面UI,移除IPA生成与文本朗读功能,新增全语言翻译器
|
||||
2025.10.05 新增IPA生成与文本朗读功能
|
||||
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) || [];
|
||||
let i = 0;
|
||||
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) => (
|
||||
<span
|
||||
@@ -1,7 +1,7 @@
|
||||
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 Button from "@/components/Button";
|
||||
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||
|
||||
type VideoPanelProps = {
|
||||
videoUrl: string | null;
|
||||
@@ -154,15 +154,15 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>((
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-9/12" onKeyDown={handleKeyDownEvent}>
|
||||
<video className="w-12/12" ref={videoRef} onTimeUpdate={timeUpdate}></video>
|
||||
<div className="w-full flex flex-col" onKeyDown={handleKeyDownEvent}>
|
||||
<video className="bg-gray-200" ref={videoRef} onTimeUpdate={timeUpdate}></video>
|
||||
<SubtitleDisplay subtitle={subtitle}></SubtitleDisplay>
|
||||
<div className="buttons flex mt-2 gap-2 flex-wrap">
|
||||
<Button label={isPlaying ? 'PAUSE' : 'PLAY'} onClick={togglePlayPause}></Button>
|
||||
<Button label="NEXT" onClick={next}></Button>
|
||||
<Button label="PREVIOUS" onClick={previous}></Button>
|
||||
<Button label="RESTART" onClick={restart}></Button>
|
||||
<Button label={`AUTOPAUSE(${autoPause ? 'Y' : 'N'})`} onClick={handleAutoPauseToggle}></Button>
|
||||
<Button label={isPlaying ? '暂停' : '播放'} onClick={togglePlayPause}></Button>
|
||||
<Button label="上句" onClick={previous}></Button>
|
||||
<Button label="下句" onClick={next}></Button>
|
||||
<Button label="句首" onClick={restart}></Button>
|
||||
<Button label={`自动播放(${autoPause ? '是' : '否'})`} onClick={handleAutoPauseToggle}></Button>
|
||||
</div>
|
||||
<input className="seekbar" type="range" min={0} max={srtLength} onChange={handleSeek} step={1} value={progress}></input>
|
||||
<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() {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
|
||||
const [videoUrl, setVideoUrl] = useState<string | null>(null);
|
||||
const [srtUrl, setSrtUrl] = useState<string | null>(null);
|
||||
return (
|
||||
<div className="flex w-screen h-screen items-center justify-center">
|
||||
<AppCard />
|
||||
<div className="flex w-screen pt-8 items-center justify-center">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user