DVideo + vue3 使用

帮助文档

https://dplayer.diygod.dev/zh/guide.html#special-thanks

安装

1
2
pnpm install dplayer --save
pnpm install @types/dplayer --save-dev

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import DPlayer from 'dplayer';
import type {
DPlayerOptions,
DPlayerVideoOptions,
DPlayerDanmakuOptions,
DPlayerSubtitleOptions
} from '@/interfaces/DPlayerOptions.ts';

const prop = defineProps<{
url: string;
pic?: string;
subtitle?: string;
autoplay: boolean;
danmakuId?: string;
danmakuApi?: string;
}>()

/**
* 视频配置属性
*/
const videoOptions: DPlayerVideoOptions = {
url: prop.url,
pic: prop.pic ? prop.pic : undefined,
thumbnails: prop.pic ? prop.pic : undefined,
};

/**
* 弹幕配置属性
*/
const danmakuOptions: DPlayerDanmakuOptions = {
id: prop.danmakuId ? prop.danmakuId : 'no danmaku',
api: prop.danmakuApi ? prop.danmakuApi : 'no danmaku'
}

/**
* 字幕配置属性
*/
const subtitleOptions: DPlayerSubtitleOptions = {
url: prop.subtitle ? prop.subtitle : 'no subtitle',
type: 'webvtt',
bottom:'20%',
fontSize: '32px',
}

/**
* DPlayer 配置属性
*/
const playerOptions: DPlayerOptions = {
container: null,
autoplay: prop.autoplay,
video: videoOptions,
danmaku: prop.danmakuId ? danmakuOptions : undefined,
subtitle: prop.subtitle ? subtitleOptions : undefined
};

const dplayerContainer = ref(null); // 引用视频播放器的容器元素
const dplayerInstance = ref<DPlayer | null>(null); // DPlayer 实例

onMounted(() => {
if (dplayerContainer.value) { // 检查 dplayerContainer 是否已绑定到实际的 DOM 元素上
playerOptions.container = dplayerContainer.value; // 将容器元素绑定到 DPlayer 配置中
dplayerInstance.value = new DPlayer(playerOptions); // 创建 DPlayer 实例
}
});

onBeforeUnmount(() => {
// 在组件销毁前销毁 DPlayer 实例,避免内存泄漏
if (dplayerInstance.value) {
dplayerInstance.value.destroy(); // 销毁 DPlayer 实例
}
});
</script>

<template>
<div class="dplayer-container" ref="dplayerContainer" />
</template>

<style scoped lang="scss">
.dplayer-container {
width: 100%;
height: 500px;
/* Adjust based on your needs */
}
</style>

属性配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import type { SubTitleType } from "dplayer";

/**
* 视频属性
* */
export interface DPlayerVideoOptions {
url: string; // 视频文件的地址
pic?: string; // 视频封面图片地址(可选)
thumbnails?: string; // 视频缩略图地址(可选)
}

/**
* 弹幕自定义 API 后端接口
*/
export interface DanmakuAPIBackend {
read: (endpoint: { url: string }, callback: (data: any) => void) => void;
send: (
endpoint: { url: string },
danmakuData: any,
callback: () => void
) => void;
}

/**
* 弹幕属性
*/
export interface DPlayerDanmakuOptions {
id: string; // 弹幕池 ID
api: string; // 弹幕数据的接口 URL
token?: string; // 用于验证请求的令牌(可选)
maximum?: string; // 最大同时显示的弹幕数量(可选)
unlimited?: boolean; // 是否不限数量地显示弹幕(可选)
addition?: string[]; // 额外的弹幕池 URL 数组(可选)
user?: string; // 当前用户的标志(可选)
bottom?: string; // 弹幕在视频底部的距离(可选)
fontSize?: string; // 弹幕字体大小(可选)
speed?: number; // 弹幕滚动速度(可选)
opacity?: number; // 弹幕透明度,0 到 1 之间(可选)
callback?: () => void; // 弹幕加载成功后的回调函数(可选)
apiBackend?: DanmakuAPIBackend; // 自定义 API 后端方式(可选)
}

/**
* 字幕属性
*/
export interface DPlayerSubtitleOptions {
url: string; // 字幕文件的 URL
type?: SubTitleType; // 字幕文件的类型,默认 'webvtt'
fontSize?: string; // 字幕字体大小,默认 '16px'
bottom?: string; // 字幕距视频底部的距离,默认 '10%'
color?: string; // 字幕颜色,默认 'white'
backgroundColor?: string; // 字幕背景颜色,默认 'transparent'
offset?: number; // 字幕的时间偏移,单位:秒,默认 0
}

/**
* DPlayer 播放器
*/
export interface DPlayerOptions {
container: HTMLElement | null; // 播放器容器
autoplay?: boolean; // 是否自动播放(可选)
video: DPlayerVideoOptions; // 视频属性
danmaku?: DPlayerDanmakuOptions; // 弹幕属性(可选)
subtitle?: DPlayerSubtitleOptions; // 字幕属性(可选)
}

mkv格式视频提取字幕文件(python + webvtt)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import os
import subprocess
from pathlib import Path


def extract_subtitles_from_mkv(source_folder: str, target_folder: str):
Path(target_folder).mkdir(parents=True, exist_ok=True) # 创建目标文件夹(如果不存在)

# 遍历源文件夹中的所有 .mkv 文件
mkv_files = [f for f in os.listdir(source_folder) if f.endswith('.mkv')]

for mkv_file in mkv_files:
source_path = os.path.join(source_folder, mkv_file)
base_name = os.path.splitext(mkv_file)[0]

# 枚举字幕流,找到所有的字幕流并提取
command = ['ffmpeg', '-i', source_path]
try:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True,
encoding='utf-8')
stderr_output = result.stderr
except UnicodeDecodeError as e:
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stderr_output = result.stderr.decode('gbk') # 尝试使用gbk编码解码

# 解析字幕流信息
subtitle_streams = []
for line in stderr_output.split('\n'):
if 'Subtitle:' in line:
parts = line.split(',')
idx = parts[0].strip().split('Stream #')[1].split(':')[0] # 提取流编号
subtitle_streams.append(idx)

if not subtitle_streams:
print(f"No subtitles found in {mkv_file}")
continue

# 提取字幕流到目标文件夹,转换为 WebVTT (.vtt) 格式
for idx in subtitle_streams:
target_path = os.path.join(target_folder, f"{base_name}_sub{idx}.vtt")
ffmpeg_command = [
'ffmpeg',
'-i', source_path, # 输入文件路径
'-map', f'0:s:{idx}', # 映射字幕流
'-f', 'webvtt', # 转换为 WebVTT 格式
target_path # 输出文件路径
]

print(f"Extracting subtitles from '{mkv_file}' to '{target_path}'...")
try:
subprocess.run(ffmpeg_command, check=True)
print(f"Extracted subtitles to '{target_path}' successfully.")
except subprocess.CalledProcessError as e:
print(f"Failed to extract subtitles from '{mkv_file}': {e}")


def main():
source_folder = './input' # 输入文件夹路径
target_folder = './output' # 输出文件夹路径

extract_subtitles_from_mkv(source_folder, target_folder)


if __name__ == '__main__':
main()