@vtx/player 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +11 -0
- package/.eslintrc.js +31 -0
- package/.prettierignore +10 -0
- package/.prettierrc.js +22 -0
- package/.storybook/main.js +58 -0
- package/.storybook/manager.js +7 -0
- package/.storybook/preview-head.html +5 -0
- package/.storybook/preview.js +9 -0
- package/.storybook/proxy.js +6 -0
- package/.storybook/theme.js +8 -0
- package/.stylelintrc.json +13 -0
- package/.vscode/extensions.json +10 -0
- package/.vscode/settings.json +16 -0
- package/README.md +3 -0
- package/commitlint.config.js +10 -0
- package/package.json +98 -0
- package/public/h5player.min.js +311 -0
- package/public/playctrl1/AudioRenderer.js +225 -0
- package/public/playctrl1/DecodeWorker.js +711 -0
- package/public/playctrl1/Decoder.js +1 -0
- package/public/playctrl1/SuperRender_10.js +396 -0
- package/public/playctrl2/Decoder.js +21 -0
- package/public/playctrl2/Decoder.wasm +0 -0
- package/public/playctrl2/Decoder.worker.js +1 -0
- package/public/playctrl3/Decoder.js +21 -0
- package/public/playctrl3/Decoder.wasm +0 -0
- package/public/playctrl3/Decoder.worker.js +1 -0
- package/rollup.config.js +52 -0
- package/src/api/fetch.ts +50 -0
- package/src/api/index.ts +44 -0
- package/src/api/types.ts +141 -0
- package/src/components/bill-player/index.less +58 -0
- package/src/components/bill-player/index.stories.mdx +24 -0
- package/src/components/bill-player/index.stories.tsx +14 -0
- package/src/components/bill-player/index.tsx +269 -0
- package/src/components/controls/images/arrow.png +0 -0
- package/src/components/controls/images/error.png +0 -0
- package/src/components/controls/index.less +182 -0
- package/src/components/controls/index.tsx +312 -0
- package/src/components/history-control/images/collapse.png +0 -0
- package/src/components/history-control/index.less +211 -0
- package/src/components/history-control/index.stories.mdx +64 -0
- package/src/components/history-control/index.stories.tsx +140 -0
- package/src/components/history-control/index.tsx +344 -0
- package/src/components/history-player/index.less +98 -0
- package/src/components/history-player/index.stories.mdx +38 -0
- package/src/components/history-player/index.stories.tsx +12 -0
- package/src/components/history-player/index.tsx +205 -0
- package/src/components/live-channel-player/index.stories.mdx +29 -0
- package/src/components/live-channel-player/index.stories.tsx +11 -0
- package/src/components/live-channel-player/index.tsx +79 -0
- package/src/components/live-control/index.less +65 -0
- package/src/components/live-control/index.stories.mdx +66 -0
- package/src/components/live-control/index.stories.tsx +69 -0
- package/src/components/live-control/index.tsx +255 -0
- package/src/components/live-player/index.less +71 -0
- package/src/components/live-player/index.stories.mdx +35 -0
- package/src/components/live-player/index.stories.tsx +12 -0
- package/src/components/live-player/index.tsx +118 -0
- package/src/components/player/index.ts +293 -0
- package/src/context/index.ts +13 -0
- package/src/hooks/useSettings.tsx +14 -0
- package/src/icons/index.less +27 -0
- package/src/icons/index.tsx +518 -0
- package/src/main.ts +9 -0
- package/src/stories/intro.stories.mdx +16 -0
- package/src/typings/@vtx/utils/index.d.ts +27 -0
- package/src/typings/h5player.d.ts +133 -0
- package/src/utils/index.ts +177 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import FlvJs from 'flv.js';
|
|
2
|
+
import Hls from 'hls.js';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
|
|
5
|
+
import { getHKTimeString, getPlayerParams } from '@/utils';
|
|
6
|
+
|
|
7
|
+
/** 视频流格式 */
|
|
8
|
+
export type VideoType = 'flv' | 'hls' | 'hk_ws';
|
|
9
|
+
interface ConstructorParams {
|
|
10
|
+
url: string;
|
|
11
|
+
/** 渲染容器 */
|
|
12
|
+
container: HTMLDivElement;
|
|
13
|
+
/** 是否直播,flv.js需要 */
|
|
14
|
+
live?: boolean;
|
|
15
|
+
/** 视频流格式 */
|
|
16
|
+
type?: VideoType;
|
|
17
|
+
/** 开始时间,历史回放需要 */
|
|
18
|
+
startTime?: moment.MomentInput;
|
|
19
|
+
/** 结束时间,历史回放需要 */
|
|
20
|
+
endTime?: moment.MomentInput;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 视频播放核心类
|
|
24
|
+
*/
|
|
25
|
+
class Player {
|
|
26
|
+
/** 视频地址 */
|
|
27
|
+
public url: string;
|
|
28
|
+
/** 视频流格式 */
|
|
29
|
+
public type: VideoType;
|
|
30
|
+
private player: FlvJs.Player | Hls | JSPlugin | null = null;
|
|
31
|
+
private video: HTMLVideoElement;
|
|
32
|
+
/** 开始时间,历史回放需要 */
|
|
33
|
+
private startTime?: moment.MomentInput;
|
|
34
|
+
/** 结束时间,历史回放需要 */
|
|
35
|
+
private endTime?: moment.MomentInput;
|
|
36
|
+
/** 跳转时间,用于处理进度条位置 */
|
|
37
|
+
private seekTime?: moment.Moment;
|
|
38
|
+
/** 之前的视频播放速度,用于处理海康音量控制 */
|
|
39
|
+
private preSpeed = 1;
|
|
40
|
+
constructor(params: ConstructorParams) {
|
|
41
|
+
const { url, container, live = true, type, startTime, endTime } = params;
|
|
42
|
+
const playerParams = getPlayerParams(url, live, startTime, endTime);
|
|
43
|
+
this.type = type || playerParams.type; // 如果不传递type,则通过url解析type值
|
|
44
|
+
this.url = playerParams.url;
|
|
45
|
+
this.startTime = startTime;
|
|
46
|
+
this.endTime = endTime;
|
|
47
|
+
|
|
48
|
+
const video = document.createElement('video'); // 生成video元素
|
|
49
|
+
video.className = 'vtx-player-video';
|
|
50
|
+
video.muted = true; //设置静音
|
|
51
|
+
this.video = video as HTMLVideoElement;
|
|
52
|
+
|
|
53
|
+
if (this.type === 'flv' && FlvJs.isSupported() && video) {
|
|
54
|
+
container.appendChild(video);
|
|
55
|
+
this.player = FlvJs.createPlayer({
|
|
56
|
+
type: 'flv',
|
|
57
|
+
isLive: live,
|
|
58
|
+
url: this.url,
|
|
59
|
+
});
|
|
60
|
+
this.player.attachMediaElement(this.video);
|
|
61
|
+
this.player.load();
|
|
62
|
+
}
|
|
63
|
+
// 处理hls格式视频
|
|
64
|
+
if (this.type === 'hls' && Hls.isSupported() && video) {
|
|
65
|
+
container.appendChild(video);
|
|
66
|
+
this.player = new Hls();
|
|
67
|
+
this.player.loadSource(this.url);
|
|
68
|
+
this.player.attachMedia(this.video);
|
|
69
|
+
}
|
|
70
|
+
// 处理海康视频
|
|
71
|
+
if (this.type === 'hk_ws') {
|
|
72
|
+
this.player = new JSPlugin({
|
|
73
|
+
szId: container.id,
|
|
74
|
+
szBasePath: '/',
|
|
75
|
+
iMaxSplit: 1,
|
|
76
|
+
iCurrentSplit: 1,
|
|
77
|
+
});
|
|
78
|
+
const video = document.getElementById(`${container.id}_playVideo0`) as HTMLVideoElement;
|
|
79
|
+
this.video = video;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** 播放 */
|
|
83
|
+
play() {
|
|
84
|
+
if (this.type === 'flv') {
|
|
85
|
+
const player = this.player as FlvJs.Player;
|
|
86
|
+
return player.play() as Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
if (this.type === 'hls') {
|
|
89
|
+
return this.video.play();
|
|
90
|
+
}
|
|
91
|
+
if (this.type === 'hk_ws') {
|
|
92
|
+
const player = this.player as JSPlugin;
|
|
93
|
+
return player.JS_Play(this.url, { playURL: this.url, mode: 0 }, 1);
|
|
94
|
+
}
|
|
95
|
+
return Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
/** 回放 */
|
|
98
|
+
playBack() {
|
|
99
|
+
if (this.type === 'hk_ws') {
|
|
100
|
+
const player = this.player as JSPlugin;
|
|
101
|
+
|
|
102
|
+
return player.JS_Play(
|
|
103
|
+
this.url,
|
|
104
|
+
{ playURL: this.url, mode: 0 },
|
|
105
|
+
1,
|
|
106
|
+
getHKTimeString(this.startTime),
|
|
107
|
+
getHKTimeString(this.endTime)
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
return this.play();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** 销毁 */
|
|
114
|
+
destroy() {
|
|
115
|
+
if (this.type === 'flv') {
|
|
116
|
+
const player = this.player as FlvJs.Player;
|
|
117
|
+
player.destroy();
|
|
118
|
+
this.player = null;
|
|
119
|
+
this.video.remove();
|
|
120
|
+
}
|
|
121
|
+
if (this.type === 'hls') {
|
|
122
|
+
const player = this.player as Hls;
|
|
123
|
+
player.destroy();
|
|
124
|
+
this.player = null;
|
|
125
|
+
this.video.remove();
|
|
126
|
+
}
|
|
127
|
+
if (this.type === 'hk_ws') {
|
|
128
|
+
const player = this.player as JSPlugin;
|
|
129
|
+
player.JS_Stop(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** 截图 */
|
|
133
|
+
capture(captureName = '视频截图', time = true) {
|
|
134
|
+
let name = captureName;
|
|
135
|
+
if (time) name += `-${moment().format('YYYYMMDDHHmmss')}`;
|
|
136
|
+
if (this.type === 'hk_ws') {
|
|
137
|
+
const player = this.player as JSPlugin;
|
|
138
|
+
if (player) player.JS_CapturePicture(1, name, 'JPEG');
|
|
139
|
+
} else {
|
|
140
|
+
Capture(this.video, name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** 尺寸重置 */
|
|
144
|
+
resize() {
|
|
145
|
+
if (this.type === 'hk_ws') {
|
|
146
|
+
const player = this.player as JSPlugin;
|
|
147
|
+
player.JS_Resize();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 设置音量
|
|
152
|
+
* @param value
|
|
153
|
+
*/
|
|
154
|
+
setVolume(value: number) {
|
|
155
|
+
if (this.type === 'flv') {
|
|
156
|
+
const player = this.player as FlvJs.Player;
|
|
157
|
+
player.volume = value / 100;
|
|
158
|
+
}
|
|
159
|
+
if (this.type === 'hls') {
|
|
160
|
+
this.video.volume = value / 100;
|
|
161
|
+
}
|
|
162
|
+
if (this.type === 'hk_ws') {
|
|
163
|
+
const player = this.player as JSPlugin;
|
|
164
|
+
player.JS_SetVolume(1, value);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 设置播放速度
|
|
169
|
+
* @param speed
|
|
170
|
+
*/
|
|
171
|
+
setSpeed(speed: 0.5 | 1 | 2) {
|
|
172
|
+
if (this.type === 'hk_ws') {
|
|
173
|
+
const player = this.player as JSPlugin;
|
|
174
|
+
if (this.preSpeed / speed === 2) {
|
|
175
|
+
player.JS_Slow(1);
|
|
176
|
+
}
|
|
177
|
+
if (this.preSpeed / speed === 4) {
|
|
178
|
+
player.JS_Slow(1).then(() => player.JS_Slow(1));
|
|
179
|
+
}
|
|
180
|
+
if (speed / this.preSpeed === 2) {
|
|
181
|
+
player.JS_Fast(1);
|
|
182
|
+
}
|
|
183
|
+
if (speed / this.preSpeed === 4) {
|
|
184
|
+
player.JS_Fast(1).then(() => player.JS_Fast(1));
|
|
185
|
+
}
|
|
186
|
+
this.preSpeed = speed;
|
|
187
|
+
} else {
|
|
188
|
+
this.video.playbackRate = speed;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 时间更新事件
|
|
193
|
+
* @param callback 回调函数
|
|
194
|
+
*/
|
|
195
|
+
onTimeUpdate(callback: (currentTime: number) => void) {
|
|
196
|
+
this.video.addEventListener('timeupdate', () => {
|
|
197
|
+
let currentTime = this.video.currentTime;
|
|
198
|
+
if (this.type === 'hk_ws' && this.seekTime)
|
|
199
|
+
currentTime = currentTime + this.seekTime.diff(this.startTime, 'seconds');
|
|
200
|
+
callback(currentTime);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/** 跳转到 */
|
|
204
|
+
async seekTo(time: number) {
|
|
205
|
+
if (this.type === 'hk_ws') {
|
|
206
|
+
const player = this.player as JSPlugin;
|
|
207
|
+
const seekTime = moment(this.startTime)?.add(time, 'seconds');
|
|
208
|
+
await player.JS_Seek(1, getHKTimeString(seekTime), getHKTimeString(this.endTime));
|
|
209
|
+
this.seekTime = seekTime;
|
|
210
|
+
} else {
|
|
211
|
+
this.video.currentTime = time;
|
|
212
|
+
return Promise.resolve();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 暂停播放
|
|
217
|
+
* @param callback 回调函数
|
|
218
|
+
*/
|
|
219
|
+
pause(callback?: () => void) {
|
|
220
|
+
if (this.type === 'hk_ws') {
|
|
221
|
+
const player = this.player as JSPlugin;
|
|
222
|
+
player.JS_Pause(1).then(() => callback && callback());
|
|
223
|
+
} else {
|
|
224
|
+
this.video.pause();
|
|
225
|
+
callback && callback();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 恢复播放
|
|
230
|
+
* @param callback 回调函数
|
|
231
|
+
*/
|
|
232
|
+
resume(callback?: () => void) {
|
|
233
|
+
if (this.type === 'hk_ws') {
|
|
234
|
+
const player = this.player as JSPlugin;
|
|
235
|
+
player.JS_Resume(1).then(() => callback && callback());
|
|
236
|
+
} else {
|
|
237
|
+
this.video.play();
|
|
238
|
+
callback && callback();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 开始对讲
|
|
243
|
+
* @param url 对讲地址
|
|
244
|
+
*/
|
|
245
|
+
startTalk(url: string) {
|
|
246
|
+
if (this.type === 'hk_ws') {
|
|
247
|
+
const player = this.player as JSPlugin;
|
|
248
|
+
return player.JS_StartTalk(url);
|
|
249
|
+
}
|
|
250
|
+
return Promise.reject();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 开始对讲
|
|
254
|
+
* @param state 状态
|
|
255
|
+
*/
|
|
256
|
+
stopTalk() {
|
|
257
|
+
if (this.type === 'hk_ws') {
|
|
258
|
+
const player = this.player as JSPlugin;
|
|
259
|
+
return player.JS_StopTalk();
|
|
260
|
+
}
|
|
261
|
+
return Promise.reject();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 截图
|
|
267
|
+
* @param video
|
|
268
|
+
*/
|
|
269
|
+
const Capture = (video: HTMLVideoElement, name: string) => {
|
|
270
|
+
const canvas = document.createElement('canvas');
|
|
271
|
+
canvas.width = video.videoWidth;
|
|
272
|
+
canvas.height = video.videoHeight;
|
|
273
|
+
const context = canvas.getContext('2d');
|
|
274
|
+
if (context) {
|
|
275
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
276
|
+
let dataURL;
|
|
277
|
+
canvas.toBlob((blob) => {
|
|
278
|
+
if (blob) {
|
|
279
|
+
dataURL = URL.createObjectURL(blob);
|
|
280
|
+
const link = document.createElement('a');
|
|
281
|
+
link.href = dataURL;
|
|
282
|
+
link.download = `${name}.png`;
|
|
283
|
+
link.style.display = 'none';
|
|
284
|
+
document.body.appendChild(link);
|
|
285
|
+
link.click();
|
|
286
|
+
document.body.removeChild(link);
|
|
287
|
+
URL.revokeObjectURL(dataURL);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export default Player;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const PlayerThemeContext = React.createContext<Context>({
|
|
4
|
+
theme: 'light',
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
interface Context {
|
|
8
|
+
/** 主题 */
|
|
9
|
+
theme: 'light' | 'dark';
|
|
10
|
+
}
|
|
11
|
+
const PlayerThemeProvider = PlayerThemeContext.Provider;
|
|
12
|
+
|
|
13
|
+
export default PlayerThemeProvider;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { PlayerThemeContext } from '@/context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取配置信息
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
const useSettings = () => {
|
|
10
|
+
const { theme } = useContext(PlayerThemeContext);
|
|
11
|
+
return { theme, isDark: theme === 'light' };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default useSettings;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.control-icon {
|
|
2
|
+
cursor: pointer;
|
|
3
|
+
fill: rgb(255 255 255 / 65%);
|
|
4
|
+
|
|
5
|
+
> svg {
|
|
6
|
+
transition: fill 0.3s;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&:hover {
|
|
10
|
+
fill: #40a9ff;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&.active {
|
|
14
|
+
fill: #1890ff;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.close-icon {
|
|
19
|
+
width: 14px;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
fill: rgb(255 255 255 / 65%);
|
|
22
|
+
transition: transform 0.3s;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
transform: rotate(180deg);
|
|
26
|
+
}
|
|
27
|
+
}
|