coda-player 0.0.0-stage → 2.0.2
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/README.md +154 -3
- package/index.d.ts +5 -0
- package/index.d.ts.map +1 -0
- package/index.js +3 -0
- package/main.js +2 -0
- package/main.js.gz +0 -0
- package/main.js.map +1 -0
- package/package.json +8 -4
- package/player/base.player.d.ts +128 -0
- package/player/base.player.d.ts.map +1 -0
- package/player/base.player.js +195 -0
- package/player/coda.player.d.ts +115 -0
- package/player/coda.player.d.ts.map +1 -0
- package/player/coda.player.js +452 -0
- package/types/index.d.ts +87 -0
- package/types/index.d.ts.map +1 -0
- package/types/index.js +1 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import Hls from 'hls.js';
|
|
2
|
+
import { BasePlayer } from './base.player';
|
|
3
|
+
/**
|
|
4
|
+
* 实现类:负责具体 UI 渲染、控制栏交互、换肤、快捷键与 HLS 切片流式传输支持
|
|
5
|
+
*/
|
|
6
|
+
export class CodaPlayer extends BasePlayer {
|
|
7
|
+
/**hls对象实例 */
|
|
8
|
+
hlsInstance = null;
|
|
9
|
+
/**播放器控制栏DOM元素 */
|
|
10
|
+
controlsElement;
|
|
11
|
+
/**播放器中心暂停/播放按钮 */
|
|
12
|
+
centerPlayBtn;
|
|
13
|
+
/**控制栏播放按钮 */
|
|
14
|
+
playBtn;
|
|
15
|
+
/**控制栏播放进度条 */
|
|
16
|
+
progressBar;
|
|
17
|
+
progressFilled;
|
|
18
|
+
/**视频时间 */
|
|
19
|
+
timeDisplay;
|
|
20
|
+
/**进度条时间提示 */
|
|
21
|
+
timeTooltip;
|
|
22
|
+
/**音量进度条 */
|
|
23
|
+
volumeBar;
|
|
24
|
+
/**音量按钮 */
|
|
25
|
+
volumeBtn;
|
|
26
|
+
/**播放倍速按钮 */
|
|
27
|
+
speedBtn;
|
|
28
|
+
/**播放倍速菜单 */
|
|
29
|
+
speedMenu;
|
|
30
|
+
/**全屏按钮 */
|
|
31
|
+
fullscreenBtn;
|
|
32
|
+
/**鼠标是否悬浮在播放器容器上 */
|
|
33
|
+
isHoveringContainer = false;
|
|
34
|
+
/**播放器中心暂停/播放按钮显示时间 */
|
|
35
|
+
centerIconTimer = null;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
super(options);
|
|
38
|
+
this.initSource(options.url);
|
|
39
|
+
this.initControlUI();
|
|
40
|
+
this.bindControlEvents();
|
|
41
|
+
this.bindKeyboardShortcuts();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 初始化视频源
|
|
45
|
+
* 智能判断视频格式进行视频源初始化:
|
|
46
|
+
* 1. .m3u8格式启用HLS播放
|
|
47
|
+
* 2. 其他格式直接使用video原生播放
|
|
48
|
+
* @param url 视频源地址
|
|
49
|
+
*/
|
|
50
|
+
initSource(url) {
|
|
51
|
+
const isHls = url.endsWith('.m3u8') || url.includes('application/vnd.apple.mpegurl');
|
|
52
|
+
if (isHls) {
|
|
53
|
+
if (Hls.isSupported()) {
|
|
54
|
+
this.hlsInstance = new Hls();
|
|
55
|
+
this.hlsInstance.loadSource(url);
|
|
56
|
+
this.hlsInstance.attachMedia(this.videoElement);
|
|
57
|
+
}
|
|
58
|
+
else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {
|
|
59
|
+
this.videoElement.src = url;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.error('CodaPlayer: HLS is not supported in this browser.');
|
|
63
|
+
this.setState('error');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
this.videoElement.src = url;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 初始化完整控制栏 UI 组件
|
|
72
|
+
*/
|
|
73
|
+
initControlUI() {
|
|
74
|
+
// 1. 创建并挂载居中播放/暂停按钮 (初始默认隐藏)
|
|
75
|
+
this.centerPlayBtn = document.createElement('div');
|
|
76
|
+
this.centerPlayBtn.className = 'coda-center-play-overlay';
|
|
77
|
+
this.container.appendChild(this.centerPlayBtn);
|
|
78
|
+
// 2. 创建底部控制栏
|
|
79
|
+
this.controlsElement = document.createElement('div');
|
|
80
|
+
this.controlsElement.className = 'coda-player-controls';
|
|
81
|
+
this.controlsElement.innerHTML = `
|
|
82
|
+
<div class="coda-progress-container">
|
|
83
|
+
<div class="coda-time-tooltip">00:00</div>
|
|
84
|
+
<div class="coda-progress-track">
|
|
85
|
+
<div class="coda-progress-filled"></div>
|
|
86
|
+
<input type="range" class="coda-progress-slider" min="0" max="100" value="0" step="0.1" />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="coda-controls-bottom">
|
|
90
|
+
<div class="coda-controls-left">
|
|
91
|
+
<button class="coda-btn coda-play-btn" title="播放/暂停">▶</button>
|
|
92
|
+
<div class="coda-volume-wrapper">
|
|
93
|
+
<button class="coda-btn coda-volume-btn" title="静音切换">🔊</button>
|
|
94
|
+
<input type="range" class="coda-volume-slider" min="0" max="1" value="${this.videoElement.muted ? 0 : this.videoElement.volume}" step="0.05" />
|
|
95
|
+
</div>
|
|
96
|
+
<span class="coda-time-display">00:00 / 00:00</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="coda-controls-right">
|
|
99
|
+
<div class="coda-speed-wrapper">
|
|
100
|
+
<button class="coda-btn coda-speed-btn" title="倍速播放">1.0x</button>
|
|
101
|
+
<div class="coda-speed-menu">
|
|
102
|
+
<div data-speed="0.5">0.5x</div>
|
|
103
|
+
<div data-speed="0.75">0.75x</div>
|
|
104
|
+
<div data-speed="1.0" class="active">1.0x</div>
|
|
105
|
+
<div data-speed="1.25">1.25x</div>
|
|
106
|
+
<div data-speed="1.5">1.5x</div>
|
|
107
|
+
<div data-speed="2.0">2.0x</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<button class="coda-btn coda-fullscreen-btn" title="全屏切换">⛶</button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
114
|
+
/**3. 控制栏元素添加到播放器容器 */
|
|
115
|
+
this.container.appendChild(this.controlsElement);
|
|
116
|
+
// 缓存内部 DOM 引用
|
|
117
|
+
this.playBtn = this.controlsElement.querySelector('.coda-play-btn');
|
|
118
|
+
this.progressBar = this.controlsElement.querySelector('.coda-progress-slider');
|
|
119
|
+
this.progressFilled = this.controlsElement.querySelector('.coda-progress-filled');
|
|
120
|
+
this.timeDisplay = this.controlsElement.querySelector('.coda-time-display');
|
|
121
|
+
this.timeTooltip = this.controlsElement.querySelector('.coda-time-tooltip');
|
|
122
|
+
this.volumeBar = this.controlsElement.querySelector('.coda-volume-slider');
|
|
123
|
+
this.volumeBtn = this.controlsElement.querySelector('.coda-volume-btn');
|
|
124
|
+
this.speedBtn = this.controlsElement.querySelector('.coda-speed-btn');
|
|
125
|
+
this.speedMenu = this.controlsElement.querySelector('.coda-speed-menu');
|
|
126
|
+
this.fullscreenBtn = this.controlsElement.querySelector('.coda-fullscreen-btn');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 中心图标的控制显示
|
|
130
|
+
* @param icon 图标类型:播放/暂停
|
|
131
|
+
* @param autoHide 是否自动隐藏图标
|
|
132
|
+
*/
|
|
133
|
+
showCenterIcon(icon, autoHide) {
|
|
134
|
+
if (this.centerIconTimer) {
|
|
135
|
+
window.clearTimeout(this.centerIconTimer);
|
|
136
|
+
this.centerIconTimer = null;
|
|
137
|
+
}
|
|
138
|
+
if (icon === 'play') {
|
|
139
|
+
this.centerPlayBtn.innerHTML = `
|
|
140
|
+
<svg viewBox="0 0 24 24" width="50%" height="50%" fill="currentColor" style="display: block; margin-left: 3px;">
|
|
141
|
+
<path d="M8 5v14l11-7z"/>
|
|
142
|
+
</svg>
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.centerPlayBtn.innerHTML = `
|
|
147
|
+
<svg viewBox="0 0 24 24" width="50%" height="50%" fill="currentColor" style="display: block;">
|
|
148
|
+
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
|
149
|
+
</svg>
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
this.centerPlayBtn.setAttribute('data-icon', icon);
|
|
153
|
+
this.centerPlayBtn.classList.add('visible');
|
|
154
|
+
if (autoHide) {
|
|
155
|
+
this.centerIconTimer = window.setTimeout(() => {
|
|
156
|
+
this.centerPlayBtn.classList.remove('visible');
|
|
157
|
+
this.centerIconTimer = null;
|
|
158
|
+
}, 2000);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 绑定控制栏所有交互逻辑
|
|
163
|
+
*/
|
|
164
|
+
bindControlEvents() {
|
|
165
|
+
/**
|
|
166
|
+
* 播放器暂停/播放
|
|
167
|
+
*/
|
|
168
|
+
const togglePlay = () => {
|
|
169
|
+
if (this.videoElement.paused) {
|
|
170
|
+
this.play();
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.pause();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
this.centerPlayBtn.addEventListener('click', togglePlay); //播放器中心图标被点击事件
|
|
177
|
+
this.playBtn.addEventListener('click', togglePlay); //播放器控制栏播放按图标被点击事件
|
|
178
|
+
this.videoElement.addEventListener('click', togglePlay); //播放器video元素被点击事件
|
|
179
|
+
/**
|
|
180
|
+
* 播放进度条拖动事件
|
|
181
|
+
*/
|
|
182
|
+
this.progressBar.addEventListener('input', e => {
|
|
183
|
+
const val = parseFloat(e.target.value);
|
|
184
|
+
const targetTime = (val / 100) * this.duration;
|
|
185
|
+
this.seek(targetTime);
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* 播放进度条鼠标悬停显示时间
|
|
189
|
+
*/
|
|
190
|
+
const progressTrack = this.controlsElement.querySelector('.coda-progress-track');
|
|
191
|
+
progressTrack.addEventListener('mousemove', (e) => {
|
|
192
|
+
const rect = progressTrack.getBoundingClientRect();
|
|
193
|
+
const offsetX = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
|
194
|
+
const percent = offsetX / rect.width;
|
|
195
|
+
const hoverTime = percent * this.duration;
|
|
196
|
+
this.timeTooltip.style.left = `${offsetX}px`;
|
|
197
|
+
this.timeTooltip.textContent = this.formatTime(hoverTime);
|
|
198
|
+
this.timeTooltip.style.display = 'block';
|
|
199
|
+
});
|
|
200
|
+
/**
|
|
201
|
+
* 播放进度条鼠标移开事件
|
|
202
|
+
*/
|
|
203
|
+
progressTrack.addEventListener('mouseleave', () => {
|
|
204
|
+
this.timeTooltip.style.display = 'none';
|
|
205
|
+
});
|
|
206
|
+
/**
|
|
207
|
+
* 视频播放事件更新事件
|
|
208
|
+
*/
|
|
209
|
+
this.videoElement.addEventListener('timeupdate', () => {
|
|
210
|
+
this.updateProgressUI();
|
|
211
|
+
});
|
|
212
|
+
/**
|
|
213
|
+
* 音量控制条拖动事件
|
|
214
|
+
*/
|
|
215
|
+
this.volumeBar.addEventListener('input', e => {
|
|
216
|
+
const val = parseFloat(e.target.value);
|
|
217
|
+
this.setVolume(val);
|
|
218
|
+
if (val > 0 && this.muted)
|
|
219
|
+
this.setMuted(false);
|
|
220
|
+
});
|
|
221
|
+
/**
|
|
222
|
+
* 音量按钮点击事件
|
|
223
|
+
*/
|
|
224
|
+
this.volumeBtn.addEventListener('click', () => {
|
|
225
|
+
this.setMuted(!this.muted);
|
|
226
|
+
});
|
|
227
|
+
/**
|
|
228
|
+
* 倍速按钮点击事件
|
|
229
|
+
*/
|
|
230
|
+
this.speedBtn.addEventListener('click', e => {
|
|
231
|
+
e.stopPropagation();
|
|
232
|
+
const isVisible = this.speedMenu.style.display === 'block';
|
|
233
|
+
this.speedMenu.style.display = isVisible ? 'none' : 'block';
|
|
234
|
+
});
|
|
235
|
+
/**
|
|
236
|
+
* 点击空白处倍速菜单消失事件
|
|
237
|
+
*/
|
|
238
|
+
document.addEventListener('click', () => {
|
|
239
|
+
this.speedMenu.style.display = 'none';
|
|
240
|
+
});
|
|
241
|
+
/**
|
|
242
|
+
* 倍速菜单点击事件
|
|
243
|
+
*/
|
|
244
|
+
this.speedMenu.querySelectorAll('div').forEach(item => {
|
|
245
|
+
item.addEventListener('click', e => {
|
|
246
|
+
const speed = parseFloat(e.target.getAttribute('data-speed') || '1.0');
|
|
247
|
+
this.setPlaybackRate(speed);
|
|
248
|
+
this.speedMenu.querySelectorAll('div').forEach(el => el.classList.remove('active'));
|
|
249
|
+
e.target.classList.add('active');
|
|
250
|
+
this.speedBtn.textContent = `${speed}x`;
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
/**
|
|
254
|
+
* 全屏按钮点击事件
|
|
255
|
+
*/
|
|
256
|
+
this.fullscreenBtn.addEventListener('click', () => {
|
|
257
|
+
this.toggleFullScreen();
|
|
258
|
+
});
|
|
259
|
+
/**
|
|
260
|
+
* 鼠标进入播放器容器显示控制栏事件
|
|
261
|
+
*/
|
|
262
|
+
this.container.addEventListener('mouseenter', () => {
|
|
263
|
+
this.isHoveringContainer = true;
|
|
264
|
+
this.controlsElement.classList.add('show');
|
|
265
|
+
});
|
|
266
|
+
/**
|
|
267
|
+
* 鼠标离开播放器容器隐藏控制栏事件
|
|
268
|
+
*/
|
|
269
|
+
this.container.addEventListener('mouseleave', () => {
|
|
270
|
+
this.isHoveringContainer = false;
|
|
271
|
+
if (this.state === 'playing') {
|
|
272
|
+
this.controlsElement.classList.remove('show');
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* 绑定快捷键(感知悬停焦点,防多实例冲突)
|
|
278
|
+
*/
|
|
279
|
+
bindKeyboardShortcuts() {
|
|
280
|
+
window.addEventListener('keydown', (e) => {
|
|
281
|
+
if (!this.isHoveringContainer)
|
|
282
|
+
return;
|
|
283
|
+
if (['INPUT', 'TEXTAREA', 'BUTTON'].includes(document.activeElement?.tagName)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (e.code === 'Space') {
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
if (this.videoElement.paused) {
|
|
289
|
+
this.play();
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
this.pause();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (e.code === 'Enter') {
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
this.toggleFullScreen();
|
|
298
|
+
}
|
|
299
|
+
else if (e.code === 'ArrowUp') {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
const newVol = Math.min(1, this.videoElement.volume + 0.1);
|
|
302
|
+
this.setVolume(newVol);
|
|
303
|
+
if (this.muted)
|
|
304
|
+
this.setMuted(false);
|
|
305
|
+
}
|
|
306
|
+
else if (e.code === 'ArrowDown') {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
const newVol = Math.max(0, this.videoElement.volume - 0.1);
|
|
309
|
+
this.setVolume(newVol);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* 更新播放进度条
|
|
315
|
+
*/
|
|
316
|
+
updateProgressUI() {
|
|
317
|
+
const current = this.currentTime;
|
|
318
|
+
const dur = this.duration;
|
|
319
|
+
const percent = dur > 0 ? (current / dur) * 100 : 0;
|
|
320
|
+
this.progressBar.value = percent.toString();
|
|
321
|
+
this.progressFilled.style.width = `${percent}%`;
|
|
322
|
+
this.timeDisplay.textContent = `${this.formatTime(current)} / ${this.formatTime(dur)}`;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 格式化播放时间显示
|
|
326
|
+
* @param seconds 秒
|
|
327
|
+
* @returns 时间格式字符串
|
|
328
|
+
*/
|
|
329
|
+
formatTime(seconds) {
|
|
330
|
+
if (isNaN(seconds))
|
|
331
|
+
return '00:00';
|
|
332
|
+
const m = Math.floor(seconds / 60);
|
|
333
|
+
const s = Math.floor(seconds % 60);
|
|
334
|
+
return `${m < 10 ? '0' + m : m}:${s < 10 ? '0' + s : s}`;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* 响应状态变化,控制中心图标的显隐和形态
|
|
338
|
+
* @param state 播放器状态
|
|
339
|
+
*/
|
|
340
|
+
onStateChange(state) {
|
|
341
|
+
if (state === 'playing') {
|
|
342
|
+
/**
|
|
343
|
+
* 视频正在播放则控制栏显示暂停按钮
|
|
344
|
+
*/
|
|
345
|
+
this.playBtn.textContent = '⏸';
|
|
346
|
+
this.showCenterIcon('play', true);
|
|
347
|
+
}
|
|
348
|
+
else if (state === 'paused') {
|
|
349
|
+
/**
|
|
350
|
+
* 视频正在暂停则控制栏显示播放按钮
|
|
351
|
+
*/
|
|
352
|
+
this.playBtn.textContent = '▶';
|
|
353
|
+
this.showCenterIcon('pause', false);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
/**
|
|
357
|
+
* 控制播放器中心图标:
|
|
358
|
+
* 暂停状态:显示中心图标
|
|
359
|
+
* 播放状态:隐藏中心图标
|
|
360
|
+
*/
|
|
361
|
+
if (this.centerIconTimer) {
|
|
362
|
+
window.clearTimeout(this.centerIconTimer);
|
|
363
|
+
this.centerIconTimer = null;
|
|
364
|
+
}
|
|
365
|
+
this.centerPlayBtn.classList.remove('visible');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 实现播放方法
|
|
370
|
+
*/
|
|
371
|
+
async play() {
|
|
372
|
+
try {
|
|
373
|
+
await this.videoElement.play();
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
console.error('CodaPlayer play error:', err);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 实现暂停方法
|
|
381
|
+
*/
|
|
382
|
+
pause() {
|
|
383
|
+
this.videoElement.pause();
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 实现进度条快进方法
|
|
387
|
+
* @param time
|
|
388
|
+
*/
|
|
389
|
+
seek(time) {
|
|
390
|
+
this.videoElement.currentTime = time;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 实现音量设置方法
|
|
394
|
+
* @param volume 音量
|
|
395
|
+
*/
|
|
396
|
+
setVolume(volume) {
|
|
397
|
+
this.videoElement.volume = volume;
|
|
398
|
+
this.volumeBar.value = volume.toString();
|
|
399
|
+
if (volume === 0) {
|
|
400
|
+
this.volumeBtn.textContent = '🔇';
|
|
401
|
+
}
|
|
402
|
+
else if (volume < 0.5) {
|
|
403
|
+
this.volumeBtn.textContent = '🔉';
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
this.volumeBtn.textContent = '🔊';
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 实现静音控制方法
|
|
411
|
+
* @param muted 是否静音
|
|
412
|
+
*/
|
|
413
|
+
setMuted(muted) {
|
|
414
|
+
this.videoElement.muted = muted;
|
|
415
|
+
this.volumeBar.value = muted ? '0' : this.videoElement.volume.toString();
|
|
416
|
+
this.volumeBtn.textContent = muted ? '🔇' : '🔊';
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* 实现播放倍速设置方法
|
|
420
|
+
* @param rate 播放倍速
|
|
421
|
+
*/
|
|
422
|
+
setPlaybackRate(rate) {
|
|
423
|
+
this._playbackRate = rate;
|
|
424
|
+
this.videoElement.playbackRate = rate;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* 实现播放器全屏切换方法
|
|
428
|
+
*/
|
|
429
|
+
toggleFullScreen() {
|
|
430
|
+
if (!document.fullscreenElement) {
|
|
431
|
+
this.container.requestFullscreen().catch(err => {
|
|
432
|
+
console.error('CodaPlayer fullscreen error:', err);
|
|
433
|
+
});
|
|
434
|
+
this._isFullScreen = true;
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
document.exitFullscreen();
|
|
438
|
+
this._isFullScreen = false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 实现播放器对象注销方法
|
|
443
|
+
*/
|
|
444
|
+
destroy() {
|
|
445
|
+
if (this.hlsInstance) {
|
|
446
|
+
this.hlsInstance.destroy();
|
|
447
|
+
this.hlsInstance = null;
|
|
448
|
+
}
|
|
449
|
+
this.pause();
|
|
450
|
+
this.container.innerHTML = '';
|
|
451
|
+
}
|
|
452
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 播放器状态枚举
|
|
3
|
+
* idle:空闲
|
|
4
|
+
* loading: 加载中
|
|
5
|
+
* playing: 播放中
|
|
6
|
+
* paused: 暂停
|
|
7
|
+
* ended: 结束
|
|
8
|
+
* error: 错误
|
|
9
|
+
*/
|
|
10
|
+
export type PlayerState = 'idle' | 'loading' | 'playing' | 'paused' | 'ended' | 'error';
|
|
11
|
+
/**
|
|
12
|
+
* 支持的倍速类型
|
|
13
|
+
*/
|
|
14
|
+
export type PlaybackRate = 0.5 | 0.75 | 1.0 | 1.25 | 1.5 | 2.0;
|
|
15
|
+
/**
|
|
16
|
+
* 播放器主题枚举
|
|
17
|
+
* default: 默认主题
|
|
18
|
+
* dark: 暗色主题
|
|
19
|
+
* custom: 自定义主题
|
|
20
|
+
*/
|
|
21
|
+
export type PlayerTheme = 'default' | 'dark' | 'custom';
|
|
22
|
+
/**
|
|
23
|
+
* 播放器常见事件映射
|
|
24
|
+
* play: 播放事件
|
|
25
|
+
* pause: 暂停事件
|
|
26
|
+
* ended: 播放结束事件
|
|
27
|
+
* error: 播放错误事件
|
|
28
|
+
* timeupdate: 播放时间变更事件
|
|
29
|
+
* volumechange: 音量变更事件
|
|
30
|
+
* statechange: 状态变更
|
|
31
|
+
*/
|
|
32
|
+
export interface CodaPlayerEventMap {
|
|
33
|
+
play: () => void;
|
|
34
|
+
pause: () => void;
|
|
35
|
+
ended: () => void;
|
|
36
|
+
error: (e: Event | string) => void;
|
|
37
|
+
timeupdate: (currentTime: number) => void;
|
|
38
|
+
volumechange: (payload: {
|
|
39
|
+
volume: number;
|
|
40
|
+
muted: boolean;
|
|
41
|
+
}) => void;
|
|
42
|
+
statechange: (state: PlayerState) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 定义的常见事件 + HTMLVideoElement 的所有原生事件
|
|
46
|
+
*/
|
|
47
|
+
export type PlayerEventTypes = keyof CodaPlayerEventMap | keyof HTMLVideoElementEventMap;
|
|
48
|
+
/**
|
|
49
|
+
* 智能回调类型推导:
|
|
50
|
+
* 根据传入的事件名,自动推导其对应回调函数的参数类型
|
|
51
|
+
*/
|
|
52
|
+
export type PlayerEventCallback<K extends PlayerEventTypes> = K extends keyof CodaPlayerEventMap ? CodaPlayerEventMap[K] : K extends keyof HTMLVideoElementEventMap ? (event: HTMLVideoElementEventMap[K]) => void : (...args: any[]) => void;
|
|
53
|
+
/**
|
|
54
|
+
* 播放器初始化配置选项
|
|
55
|
+
*/
|
|
56
|
+
export type PlayerOptions = {
|
|
57
|
+
container: HTMLElement | string;
|
|
58
|
+
url: string;
|
|
59
|
+
poster?: string;
|
|
60
|
+
autoplay?: boolean;
|
|
61
|
+
muted?: boolean;
|
|
62
|
+
loop?: boolean;
|
|
63
|
+
themeColor?: string;
|
|
64
|
+
width?: string | number;
|
|
65
|
+
height?: string | number;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* 播放器核心接口契约
|
|
69
|
+
*/
|
|
70
|
+
export interface IPlayer {
|
|
71
|
+
readonly currentTime: number;
|
|
72
|
+
readonly duration: number;
|
|
73
|
+
readonly volume: number;
|
|
74
|
+
readonly muted: boolean;
|
|
75
|
+
readonly state: PlayerState;
|
|
76
|
+
readonly isFullScreen: boolean;
|
|
77
|
+
readonly playbackRate: PlaybackRate;
|
|
78
|
+
play(): Promise<void>;
|
|
79
|
+
pause(): void;
|
|
80
|
+
seek(time: number): void;
|
|
81
|
+
setVolume(volume: number): void;
|
|
82
|
+
setMuted(muted: boolean): void;
|
|
83
|
+
setPlaybackRate(rate: PlaybackRate): void;
|
|
84
|
+
toggleFullScreen(): void;
|
|
85
|
+
destroy(): void;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAExF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,GAAG,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG,MAAM,KAAK,IAAI,CAAC;IACnC,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,OAAO,EAAE;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAC,KAAK,IAAI,CAAC;IAClE,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,kBAAkB,GAAG,MAAM,wBAAwB,CAAC;AAEzF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,gBAAgB,IAAI,CAAC,SAAS,MAAM,kBAAkB,GAC5F,kBAAkB,CAAC,CAAC,CAAC,GACrB,CAAC,SAAS,MAAM,wBAAwB,GACtC,CAAC,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,IAAI,GAC5C,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;AAE/B;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAEpC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,gBAAgB,IAAI,IAAI,CAAC;IACzB,OAAO,IAAI,IAAI,CAAC;CACjB"}
|
package/types/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|