feat: 视频播放插件添加了一个极其简陋的字幕
This commit is contained in:
1 parent
632a562348
commit
f578072997
4 files changed
+135
-22
No files matched your search
+2
-1
@@ -16,7 +16,8 @@
|
|||||||
"naive-ui": "^2.39.0",
|
"naive-ui": "^2.39.0",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"vue": "^3.4.35",
|
"vue": "^3.4.35",
|
||||||
"xgplayer": "^3.0.20"
|
"xgplayer": "^3.0.20",
|
||||||
|
"xgplayer-subtitles": "^3.0.20"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
|
|||||||
Generated
+3
@@ -29,6 +29,9 @@ importers:
|
|||||||
xgplayer:
|
xgplayer:
|
||||||
specifier: ^3.0.20
|
specifier: ^3.0.20
|
||||||
version: 3.0.20(core-js@3.38.1)
|
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:
|
devDependencies:
|
||||||
'@types/lodash':
|
'@types/lodash':
|
||||||
specifier: ^4.17.7
|
specifier: ^4.17.7
|
||||||
|
|||||||
+8
-1
@@ -17,6 +17,13 @@ export default function init() {
|
|||||||
if (vFiles.length === 0) {
|
if (vFiles.length === 0) {
|
||||||
return false;
|
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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
+122
-20
@@ -11,35 +11,68 @@
|
|||||||
import { VirtualFile } from '@/class/VirtualDirectory';
|
import { VirtualFile } from '@/class/VirtualDirectory';
|
||||||
import miaoDropHandler from '@/components/miaoDropHandler.vue';
|
import miaoDropHandler from '@/components/miaoDropHandler.vue';
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||||
import useViretualPages from '@/hooks/useVirtualPages';
|
// import useViretualPages from '@/hooks/useVirtualPages';
|
||||||
import XGPlayer from 'xgplayer'
|
import XGPlayer from 'xgplayer'
|
||||||
import '@/style/xgplayer.scss'
|
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<{
|
// const props = defineProps<{
|
||||||
id: number
|
// id: number
|
||||||
}>()
|
// }>()
|
||||||
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
const currentFiles = defineModel<VirtualFile[]>('currentFiles', {
|
||||||
required: true
|
required: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const views = useViretualPages()
|
// const views = useViretualPages()
|
||||||
const view = views.getView(props.id)
|
// const view = views.getView(props.id)
|
||||||
|
|
||||||
const videoPath = ref<string>('');
|
// const videoPath = ref<string>('');
|
||||||
const xgPlayer = ref()
|
const xgPlayer = ref()
|
||||||
|
|
||||||
let _player: XGPlayer
|
let _player: XGPlayer
|
||||||
const setVideo = (() => {
|
const { setVideo } = (() => {
|
||||||
return (url: string) => {
|
return {
|
||||||
if (_player) {
|
setVideo: async (videoVFile: VirtualFile, vttVFiles?: VirtualFile[]) => {
|
||||||
_player.src = url
|
_player && _player.destroy()
|
||||||
} else {
|
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({
|
_player = new XGPlayer({
|
||||||
|
plugins: [TextTrack],
|
||||||
el: xgPlayer.value,
|
el: xgPlayer.value,
|
||||||
url,
|
url: videoVFile.url,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
volume: 1,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
pip: true,
|
pip: true,
|
||||||
videoFillMode: 'contain',
|
videoFillMode: 'contain',
|
||||||
@@ -56,27 +89,96 @@ const setVideo = (() => {
|
|||||||
playNext: {
|
playNext: {
|
||||||
urlList: [
|
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[]) => {
|
const handleDropVFiles = (files: VirtualFile[]) => {
|
||||||
console.log(files)
|
setVideo(findVideo(files), findTextTrack(files))
|
||||||
view && (view.currentFiles[0] = files[0])
|
|
||||||
videoPath.value = files[0].url;
|
|
||||||
setVideo(videoPath.value)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
onMounted(() => {
|
||||||
if(currentFiles.value.length) {
|
if (currentFiles.value.length) {
|
||||||
setVideo(currentFiles.value[0].url)
|
setVideo(findVideo(currentFiles.value), findTextTrack(currentFiles.value))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
_player.destroy()
|
_player.destroy()
|
||||||
|
destroyUrl()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user