From f578072997c21483f440d90f4fff5470d68d7ce1 Mon Sep 17 00:00:00 2001 From: Miao_Spring <539943419@qq.com> Date: Tue, 27 Aug 2024 20:42:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=86=E9=A2=91=E6=92=AD=E6=94=BE?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=9E=81=E5=85=B6=E7=AE=80=E9=99=8B=E7=9A=84=E5=AD=97=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- pnpm-lock.yaml | 3 + src/hooks/init.ts | 9 ++- src/views/miaoVideoPlayer.vue | 142 +++++++++++++++++++++++++++++----- 4 files changed, 135 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index e8e4359..d31e4b9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "naive-ui": "^2.39.0", "sass": "^1.77.8", "vue": "^3.4.35", - "xgplayer": "^3.0.20" + "xgplayer": "^3.0.20", + "xgplayer-subtitles": "^3.0.20" }, "devDependencies": { "@types/lodash": "^4.17.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6275140..ae7ed4f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: xgplayer: specifier: ^3.0.20 version: 3.0.20(core-js@3.38.1) + xgplayer-subtitles: + specifier: ^3.0.20 + version: 3.0.20(core-js@3.38.1) devDependencies: '@types/lodash': specifier: ^4.17.7 diff --git a/src/hooks/init.ts b/src/hooks/init.ts index 9f9b141..60d7bfe 100644 --- a/src/hooks/init.ts +++ b/src/hooks/init.ts @@ -17,6 +17,13 @@ export default function init() { if (vFiles.length === 0) { return false; } - return vFiles.length === 1 && vFiles[0].name.endsWith("mp4"); + for(let i of vFiles) { + for(let s of ['mp4', 'm3u8', 'hls']) { + if(i.name.endsWith(s)) { + return true + } + } + } + return false }); } diff --git a/src/views/miaoVideoPlayer.vue b/src/views/miaoVideoPlayer.vue index 5f5a2a8..469b529 100644 --- a/src/views/miaoVideoPlayer.vue +++ b/src/views/miaoVideoPlayer.vue @@ -11,35 +11,68 @@ import { VirtualFile } from '@/class/VirtualDirectory'; import miaoDropHandler from '@/components/miaoDropHandler.vue'; import { onBeforeUnmount, onMounted, ref } from 'vue'; -import useViretualPages from '@/hooks/useVirtualPages'; +// import useViretualPages from '@/hooks/useVirtualPages'; import XGPlayer from 'xgplayer' import '@/style/xgplayer.scss' +import TextTrack from 'xgplayer/es/plugins/track' +import 'xgplayer/es/plugins/track/index.css' +import axios from 'axios'; +import config from '@/config'; -const props = defineProps<{ - id: number -}>() +// const props = defineProps<{ +// id: number +// }>() const currentFiles = defineModel('currentFiles', { required: true }) -const views = useViretualPages() -const view = views.getView(props.id) +// const views = useViretualPages() +// const view = views.getView(props.id) -const videoPath = ref(''); +// const videoPath = ref(''); const xgPlayer = ref() let _player: XGPlayer -const setVideo = (() => { - return (url: string) => { - if (_player) { - _player.src = url - } else { +const { setVideo } = (() => { + return { + setVideo: async (videoVFile: VirtualFile, vttVFiles?: VirtualFile[]) => { + _player && _player.destroy() + const _list = [] + if (vttVFiles) { + for (let i of vttVFiles) { + if (i.name.endsWith('srt')) { + const response = await axios.get(i.url) + console.log(srtToVtt(response.data)) + const url = setUrl(new Blob([srtToVtt(response.data)])) + _list.push({ + id: i.name, + url, + language: 'zh-cn', + text: i.name, + default: i === vttVFiles[0] + }) + } else { + _list.push({ + id: i.name, + url: i.url, + language: 'zh-cn', + text: i.name, + default: i === vttVFiles[0] + }) + } + } + } + const texttrack = { + list: _list + } _player = new XGPlayer({ + plugins: [TextTrack], el: xgPlayer.value, - url, + url: videoVFile.url, width: '100%', height: '100%', + volume: 1, fluid: true, pip: true, videoFillMode: 'contain', @@ -56,27 +89,96 @@ const setVideo = (() => { playNext: { urlList: [ ], - } + }, + texttrack, }) } } })() +const { setUrl, destroyUrl } = (() => { + const urlList: string[] = [] + return { + setUrl(blob: Blob) { + const url = URL.createObjectURL(blob) + urlList.push(url) + return url + }, + destroyUrl() { + for (const url of urlList) { + URL.revokeObjectURL(url) + } + } + } +})() + const handleDropVFiles = (files: VirtualFile[]) => { - console.log(files) - view && (view.currentFiles[0] = files[0]) - videoPath.value = files[0].url; - setVideo(videoPath.value) + setVideo(findVideo(files), findTextTrack(files)) }; +function srtToVtt(srt: string) { + // 将 SRT 字幕按行分割 + const lines = srt.split('\n'); + let vtt = 'WEBVTT\n\n'; // VTT 文件的开头 + + let index = 0; + while (index < lines.length) { + // 跳过空行 + if (lines[index].trim() === '') { + index++; + continue; + } + + // SRT 中的序号 + const sequenceNumber = lines[index].trim(); + index++; + + // 时间戳 + const timeLine = lines[index].trim().replace(/,/g, '.'); // 将逗号替换为点 + index++; + + // 字幕文本 + let text = ''; + while (index < lines.length && lines[index].trim() !== '') { + text += lines[index].trim() + '\n'; + index++; + } + + // 添加到 VTT 字幕中 + vtt += `${timeLine}\n${text.trim()}\n\n`; + } + + return vtt; +} + + +const findVideo = (vFiles: VirtualFile[]) => vFiles.filter(v => { + for (let s of ['mp4', 'm3u8', 'hls']) { + if (v.name.endsWith(s)) { + return true + } + } + return false +})[0] + +const findTextTrack = (vFiles: VirtualFile[]) => vFiles.filter(v => { + for (let s of ['vtt', 'srt']) { + if (v.name.endsWith(s)) { + return true + } + } + return false +}) + onMounted(() => { - if(currentFiles.value.length) { - setVideo(currentFiles.value[0].url) + if (currentFiles.value.length) { + setVideo(findVideo(currentFiles.value), findTextTrack(currentFiles.value)) } }); onBeforeUnmount(() => { _player.destroy() + destroyUrl() })