vidply 1.0.0
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/LICENSE +22 -0
- package/README.md +461 -0
- package/dist/vidply.css +1717 -0
- package/dist/vidply.esm.js +5558 -0
- package/dist/vidply.esm.js.map +7 -0
- package/dist/vidply.esm.min.js +17 -0
- package/dist/vidply.esm.min.meta.json +336 -0
- package/dist/vidply.js +5575 -0
- package/dist/vidply.js.map +7 -0
- package/dist/vidply.min.css +6 -0
- package/dist/vidply.min.js +17 -0
- package/dist/vidply.min.meta.json +332 -0
- package/package.json +54 -0
- package/src/controls/CaptionManager.js +250 -0
- package/src/controls/ControlBar.js +1870 -0
- package/src/controls/KeyboardManager.js +196 -0
- package/src/controls/SettingsDialog.js +417 -0
- package/src/controls/TranscriptManager.js +728 -0
- package/src/core/Player.js +1108 -0
- package/src/features/PlaylistManager.js +437 -0
- package/src/i18n/i18n.js +66 -0
- package/src/i18n/translations.js +511 -0
- package/src/icons/Icons.js +183 -0
- package/src/index.js +34 -0
- package/src/renderers/HLSRenderer.js +302 -0
- package/src/renderers/HTML5Renderer.js +298 -0
- package/src/renderers/VimeoRenderer.js +257 -0
- package/src/renderers/YouTubeRenderer.js +274 -0
- package/src/styles/vidply.css +1711 -0
- package/src/utils/DOMUtils.js +154 -0
- package/src/utils/EventEmitter.js +53 -0
- package/src/utils/TimeUtils.js +82 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG Icon Library for VidPly
|
|
3
|
+
* Optimized with common viewBox and fill attributes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// SVG paths only (viewBox and fill added by wrapper)
|
|
7
|
+
const iconPaths = {
|
|
8
|
+
play: `<path d="M8 5v14l11-7z"/>`,
|
|
9
|
+
|
|
10
|
+
pause: `<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>`,
|
|
11
|
+
|
|
12
|
+
stop: `<rect x="6" y="6" width="12" height="12"/>`,
|
|
13
|
+
|
|
14
|
+
rewind: `<path d="M11 18V6l-8.5 6 8.5 6zm.5-6l8.5 6V6l-8.5 6z"/>`,
|
|
15
|
+
|
|
16
|
+
forward: `<path d="M4 18l8.5-6L4 6v12zm9-12v12l8.5-6L13 6z"/>`,
|
|
17
|
+
|
|
18
|
+
skipPrevious: `<path d="M6 6h2v12H6V6zm3 6l8.5 6V6L9 12z"/>`,
|
|
19
|
+
|
|
20
|
+
skipNext: `<path d="M16 6h2v12h-2V6zM6 6l8.5 6L6 18V6z"/>`,
|
|
21
|
+
|
|
22
|
+
restart: `<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>`,
|
|
23
|
+
|
|
24
|
+
volumeHigh: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>`,
|
|
25
|
+
|
|
26
|
+
volumeMedium: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
|
|
27
|
+
|
|
28
|
+
volumeLow: `<path d="M7 9v6h4l5 5V4l-5 5H7z"/>`,
|
|
29
|
+
|
|
30
|
+
volumeMuted: `<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>`,
|
|
31
|
+
|
|
32
|
+
fullscreen: `<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>`,
|
|
33
|
+
|
|
34
|
+
fullscreenExit: `<path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>`,
|
|
35
|
+
|
|
36
|
+
settings: `<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>`,
|
|
37
|
+
|
|
38
|
+
captions: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/>`,
|
|
39
|
+
|
|
40
|
+
captionsOff: `<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1c0 .55-.45 1-1 1H7c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1c0 .55-.45 1-1 1h-3c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v1z"/><path d="M0 0h24v24H0z" fill="none"/>`,
|
|
41
|
+
|
|
42
|
+
pip: `<path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/>`,
|
|
43
|
+
|
|
44
|
+
speed: `<path d="M20.38 8.57l-1.23 1.85a8 8 0 0 1-.22 7.58H5.07A8 8 0 0 1 15.58 6.85l1.85-1.23A10 10 0 0 0 3.35 19a2 2 0 0 0 1.72 1h13.85a2 2 0 0 0 1.74-1 10 10 0 0 0-.27-10.44z"/><path d="M10.59 15.41a2 2 0 0 0 2.83 0l5.66-8.49-8.49 5.66a2 2 0 0 0 0 2.83z"/>`,
|
|
45
|
+
|
|
46
|
+
close: `<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>`,
|
|
47
|
+
|
|
48
|
+
check: `<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>`,
|
|
49
|
+
|
|
50
|
+
arrowUp: `<path d="M7 14l5-5 5 5z"/>`,
|
|
51
|
+
|
|
52
|
+
arrowDown: `<path d="M7 10l5 5 5-5z"/>`,
|
|
53
|
+
|
|
54
|
+
arrowLeft: `<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>`,
|
|
55
|
+
|
|
56
|
+
arrowRight: `<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/>`,
|
|
57
|
+
|
|
58
|
+
loading: `<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>`,
|
|
59
|
+
|
|
60
|
+
error: `<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>`,
|
|
61
|
+
|
|
62
|
+
download: `<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>`,
|
|
63
|
+
|
|
64
|
+
link: `<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/>`,
|
|
65
|
+
|
|
66
|
+
playlist: `<path d="M15 6H3v2h12V6zm0 4H3v2h12v-2zM3 16h8v-2H3v2zM17 6v8.18c-.31-.11-.65-.18-1-.18-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3V8h3V6h-5z"/>`,
|
|
67
|
+
|
|
68
|
+
language: `<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm6.93 6h-2.95c-.32-1.25-.78-2.45-1.38-3.56 1.84.63 3.37 1.91 4.33 3.56zM12 4.04c.83 1.2 1.48 2.53 1.91 3.96h-3.82c.43-1.43 1.08-2.76 1.91-3.96zM4.26 14C4.1 13.36 4 12.69 4 12s.1-1.36.26-2h3.38c-.08.66-.14 1.32-.14 2 0 .68.06 1.34.14 2H4.26zm.82 2h2.95c.32 1.25.78 2.45 1.38 3.56-1.84-.63-3.37-1.9-4.33-3.56zm2.95-8H5.08c.96-1.66 2.49-2.93 4.33-3.56C8.81 5.55 8.35 6.75 8.03 8zM12 19.96c-.83-1.2-1.48-2.53-1.91-3.96h3.82c-.43 1.43-1.08 2.76-1.91 3.96zM14.34 14H9.66c-.09-.66-.16-1.32-.16-2 0-.68.07-1.35.16-2h4.68c.09.65.16 1.32.16 2 0 .68-.07 1.34-.16 2zm.25 5.56c.6-1.11 1.06-2.31 1.38-3.56h2.95c-.96 1.65-2.49 2.93-4.33 3.56zM16.36 14c.08-.66.14-1.32.14-2 0-.68-.06-1.34-.14-2h3.38c.16.64.26 1.31.26 2s-.1 1.36-.26 2h-3.38z"/>`,
|
|
69
|
+
|
|
70
|
+
hd: `<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-8 12H9.5v-2h-2v2H6V9h1.5v2.5h2V9H11v6zm7-1c0 .55-.45 1-1 1h-.75v1.5h-1.5V15H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3c.55 0 1 .45 1 1v4zm-3.5-.5h2v-3h-2v3z"/>`,
|
|
71
|
+
|
|
72
|
+
transcript: `<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>`,
|
|
73
|
+
|
|
74
|
+
audioDescription: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
|
|
75
|
+
|
|
76
|
+
audioDescriptionOn: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7z"/><path d="M10.5 19c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/><circle cx="19" cy="16" r="3" fill="#3b82f6"/><path d="M18.5 17.5l1-1 1.5 1.5" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
77
|
+
|
|
78
|
+
signLanguage: `<path d="M12 2C10.34 2 9 3.34 9 5v4c0 .34.07.66.18.96L7.5 8.29C7.19 8.1 6.85 8 6.5 8 5.12 8 4 9.12 4 10.5v3c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-3c0-.28.22-.5.5-.5s.5.22.5.5V14l2 2v-1c0-.55.45-1 1-1s1 .45 1 1v2c0 .55.45 1 1 1s1-.45 1-1V9c0-.55.45-1 1-1s1 .45 1 1v8c0 2.21-1.79 4-4 4s-4-1.79-4-4v-2.83l-2.93-2.93A3.93 3.93 0 0 1 4 8c0-1.66 1.34-3 3-3 .83 0 1.58.34 2.12.88L11 7.76V5c0-.55.45-1 1-1s1 .45 1 1v4c0 .55.45 1 1 1s1-.45 1-1V5c0-1.66-1.34-3-3-3z"/>`,
|
|
79
|
+
|
|
80
|
+
signLanguageOn: `<path d="M12 2C10.34 2 9 3.34 9 5v4c0 .34.07.66.18.96L7.5 8.29C7.19 8.1 6.85 8 6.5 8 5.12 8 4 9.12 4 10.5v3c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5v-3c0-.28.22-.5.5-.5s.5.22.5.5V14l2 2v-1c0-.55.45-1 1-1s1 .45 1 1v2c0 .55.45 1 1 1s1-.45 1-1V9c0-.55.45-1 1-1s1 .45 1 1v8c0 2.21-1.79 4-4 4s-4-1.79-4-4v-2.83l-2.93-2.93A3.93 3.93 0 0 1 4 8c0-1.66 1.34-3 3-3 .83 0 1.58.34 2.12.88L11 7.76V5c0-.55.45-1 1-1s1 .45 1 1v4c0 .55.45 1 1 1s1-.45 1-1V5c0-1.66-1.34-3-3-3z"/><circle cx="19" cy="16" r="3" fill="#3b82f6"/><path d="M18.5 17.5l1-1 1.5 1.5" stroke="white" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`,
|
|
81
|
+
|
|
82
|
+
speaker: `<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>`,
|
|
83
|
+
|
|
84
|
+
music: `<path d="M12 3v9.28c-.47-.17-.97-.28-1.5-.28C8.01 12 6 14.01 6 16.5S8.01 21 10.5 21c2.31 0 4.2-1.75 4.45-4H15V6h4V3h-7zm-1.5 16c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>`,
|
|
85
|
+
|
|
86
|
+
moreVertical: `<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`,
|
|
87
|
+
|
|
88
|
+
moreHorizontal: `<path d="M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/>`
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Optimized wrapper that adds common SVG attributes
|
|
92
|
+
const svgWrapper = (paths) => `<svg viewBox="0 0 24 24" fill="currentColor">${paths}</svg>`;
|
|
93
|
+
|
|
94
|
+
// Export Icons object with SVG wrappers
|
|
95
|
+
export const Icons = Object.fromEntries(
|
|
96
|
+
Object.entries(iconPaths).map(([key, value]) => [key, svgWrapper(value)])
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export function getIcon(name) {
|
|
100
|
+
return Icons[name] || Icons.play;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createIconElement(name, className = '') {
|
|
104
|
+
const wrapper = document.createElement('span');
|
|
105
|
+
wrapper.className = `vidply-icon ${className}`.trim();
|
|
106
|
+
wrapper.innerHTML = getIcon(name);
|
|
107
|
+
wrapper.setAttribute('aria-hidden', 'true');
|
|
108
|
+
return wrapper;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create the large centered play button overlay (complete SVG)
|
|
113
|
+
* @returns {SVGElement} Complete SVG element with circle background and play icon
|
|
114
|
+
*/
|
|
115
|
+
export function createPlayOverlay() {
|
|
116
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
117
|
+
svg.setAttribute('class', 'vidply-play-overlay');
|
|
118
|
+
svg.setAttribute('viewBox', '0 0 80 80');
|
|
119
|
+
svg.setAttribute('width', '80');
|
|
120
|
+
svg.setAttribute('height', '80');
|
|
121
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
122
|
+
svg.setAttribute('role', 'presentation');
|
|
123
|
+
svg.style.cursor = 'pointer';
|
|
124
|
+
|
|
125
|
+
// Create filter for drop shadow
|
|
126
|
+
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
127
|
+
const filterId = `vidply-play-shadow-${Math.random().toString(36).substr(2, 9)}`;
|
|
128
|
+
const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
|
|
129
|
+
filter.setAttribute('id', filterId);
|
|
130
|
+
filter.setAttribute('x', '-50%');
|
|
131
|
+
filter.setAttribute('y', '-50%');
|
|
132
|
+
filter.setAttribute('width', '200%');
|
|
133
|
+
filter.setAttribute('height', '200%');
|
|
134
|
+
|
|
135
|
+
const feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
|
|
136
|
+
feGaussianBlur.setAttribute('in', 'SourceAlpha');
|
|
137
|
+
feGaussianBlur.setAttribute('stdDeviation', '3');
|
|
138
|
+
|
|
139
|
+
const feOffset = document.createElementNS('http://www.w3.org/2000/svg', 'feOffset');
|
|
140
|
+
feOffset.setAttribute('dx', '0');
|
|
141
|
+
feOffset.setAttribute('dy', '2');
|
|
142
|
+
feOffset.setAttribute('result', 'offsetblur');
|
|
143
|
+
|
|
144
|
+
const feComponentTransfer = document.createElementNS('http://www.w3.org/2000/svg', 'feComponentTransfer');
|
|
145
|
+
const feFuncA = document.createElementNS('http://www.w3.org/2000/svg', 'feFuncA');
|
|
146
|
+
feFuncA.setAttribute('type', 'linear');
|
|
147
|
+
feFuncA.setAttribute('slope', '0.3');
|
|
148
|
+
feComponentTransfer.appendChild(feFuncA);
|
|
149
|
+
|
|
150
|
+
const feMerge = document.createElementNS('http://www.w3.org/2000/svg', 'feMerge');
|
|
151
|
+
const feMergeNode1 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
|
|
152
|
+
const feMergeNode2 = document.createElementNS('http://www.w3.org/2000/svg', 'feMergeNode');
|
|
153
|
+
feMergeNode2.setAttribute('in', 'SourceGraphic');
|
|
154
|
+
feMerge.appendChild(feMergeNode1);
|
|
155
|
+
feMerge.appendChild(feMergeNode2);
|
|
156
|
+
|
|
157
|
+
filter.appendChild(feGaussianBlur);
|
|
158
|
+
filter.appendChild(feOffset);
|
|
159
|
+
filter.appendChild(feComponentTransfer);
|
|
160
|
+
filter.appendChild(feMerge);
|
|
161
|
+
defs.appendChild(filter);
|
|
162
|
+
svg.appendChild(defs);
|
|
163
|
+
|
|
164
|
+
// White circle background
|
|
165
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
166
|
+
circle.setAttribute('cx', '40');
|
|
167
|
+
circle.setAttribute('cy', '40');
|
|
168
|
+
circle.setAttribute('r', '40');
|
|
169
|
+
circle.setAttribute('fill', 'rgba(255, 255, 255, 0.95)');
|
|
170
|
+
circle.setAttribute('filter', `url(#${filterId})`);
|
|
171
|
+
circle.setAttribute('class', 'vidply-play-overlay-bg');
|
|
172
|
+
svg.appendChild(circle);
|
|
173
|
+
|
|
174
|
+
// Play icon triangle (centered with optical adjustment)
|
|
175
|
+
const playTriangle = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
176
|
+
playTriangle.setAttribute('points', '32,28 32,52 54,40');
|
|
177
|
+
playTriangle.setAttribute('fill', '#0a406e');
|
|
178
|
+
playTriangle.setAttribute('class', 'vidply-play-overlay-icon');
|
|
179
|
+
svg.appendChild(playTriangle);
|
|
180
|
+
|
|
181
|
+
return svg;
|
|
182
|
+
}
|
|
183
|
+
|
package/src/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VidPly - Universal Video Player
|
|
3
|
+
* Main Entry Point
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Player } from './core/Player.js';
|
|
7
|
+
import { PlaylistManager } from './features/PlaylistManager.js';
|
|
8
|
+
|
|
9
|
+
// Auto-initialize players
|
|
10
|
+
function initializePlayers() {
|
|
11
|
+
const elements = document.querySelectorAll('[data-vidply]');
|
|
12
|
+
|
|
13
|
+
elements.forEach(element => {
|
|
14
|
+
// Parse options from data attribute
|
|
15
|
+
const options = element.dataset.vidplyOptions
|
|
16
|
+
? JSON.parse(element.dataset.vidplyOptions)
|
|
17
|
+
: {};
|
|
18
|
+
|
|
19
|
+
// Create player instance
|
|
20
|
+
new Player(element, options);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Auto-initialize on DOM ready
|
|
25
|
+
if (document.readyState === 'loading') {
|
|
26
|
+
document.addEventListener('DOMContentLoaded', initializePlayers);
|
|
27
|
+
} else {
|
|
28
|
+
initializePlayers();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Export for manual initialization
|
|
32
|
+
export { Player, PlaylistManager };
|
|
33
|
+
export default Player;
|
|
34
|
+
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HLS Streaming Renderer
|
|
3
|
+
* Uses hls.js for browsers that don't natively support HLS
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class HLSRenderer {
|
|
7
|
+
constructor(player) {
|
|
8
|
+
this.player = player;
|
|
9
|
+
this.media = player.element;
|
|
10
|
+
this.hls = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async init() {
|
|
14
|
+
// Check if browser natively supports HLS (Safari)
|
|
15
|
+
if (this.canPlayNatively()) {
|
|
16
|
+
this.player.log('Using native HLS support');
|
|
17
|
+
await this.initNative();
|
|
18
|
+
} else {
|
|
19
|
+
this.player.log('Using hls.js for HLS support');
|
|
20
|
+
await this.initHlsJs();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
canPlayNatively() {
|
|
25
|
+
const video = document.createElement('video');
|
|
26
|
+
return video.canPlayType('application/vnd.apple.mpegurl') !== '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initNative() {
|
|
30
|
+
// Use HTML5 renderer for native HLS support
|
|
31
|
+
const HTML5Renderer = (await import('./HTML5Renderer.js')).HTML5Renderer;
|
|
32
|
+
const renderer = new HTML5Renderer(this.player);
|
|
33
|
+
await renderer.init();
|
|
34
|
+
|
|
35
|
+
// Copy methods
|
|
36
|
+
Object.getOwnPropertyNames(Object.getPrototypeOf(renderer)).forEach(method => {
|
|
37
|
+
if (method !== 'constructor' && typeof renderer[method] === 'function') {
|
|
38
|
+
this[method] = renderer[method].bind(renderer);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async initHlsJs() {
|
|
44
|
+
// Hide native controls
|
|
45
|
+
this.media.controls = false;
|
|
46
|
+
this.media.removeAttribute('controls');
|
|
47
|
+
|
|
48
|
+
// Load hls.js if not already loaded
|
|
49
|
+
if (!window.Hls) {
|
|
50
|
+
await this.loadHlsJs();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!window.Hls.isSupported()) {
|
|
54
|
+
throw new Error('HLS is not supported in this browser');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Create hls.js instance with better error recovery
|
|
58
|
+
this.hls = new window.Hls({
|
|
59
|
+
debug: this.player.options.debug,
|
|
60
|
+
enableWorker: true,
|
|
61
|
+
lowLatencyMode: false,
|
|
62
|
+
backBufferLength: 90,
|
|
63
|
+
maxBufferLength: 30,
|
|
64
|
+
maxMaxBufferLength: 600,
|
|
65
|
+
maxBufferSize: 60 * 1000 * 1000,
|
|
66
|
+
maxBufferHole: 0.5,
|
|
67
|
+
// Network retry settings
|
|
68
|
+
manifestLoadingTimeOut: 10000,
|
|
69
|
+
manifestLoadingMaxRetry: 4,
|
|
70
|
+
manifestLoadingRetryDelay: 1000,
|
|
71
|
+
manifestLoadingMaxRetryTimeout: 64000,
|
|
72
|
+
levelLoadingTimeOut: 10000,
|
|
73
|
+
levelLoadingMaxRetry: 4,
|
|
74
|
+
levelLoadingRetryDelay: 1000,
|
|
75
|
+
levelLoadingMaxRetryTimeout: 64000,
|
|
76
|
+
fragLoadingTimeOut: 20000,
|
|
77
|
+
fragLoadingMaxRetry: 6,
|
|
78
|
+
fragLoadingRetryDelay: 1000,
|
|
79
|
+
fragLoadingMaxRetryTimeout: 64000
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Attach media element
|
|
83
|
+
this.hls.attachMedia(this.media);
|
|
84
|
+
|
|
85
|
+
// Load source - Get from attribute to avoid blob URL conversion
|
|
86
|
+
let src;
|
|
87
|
+
const sourceElement = this.player.element.querySelector('source');
|
|
88
|
+
if (sourceElement) {
|
|
89
|
+
// Use getAttribute to get the original URL, not the blob-converted one
|
|
90
|
+
src = sourceElement.getAttribute('src');
|
|
91
|
+
} else {
|
|
92
|
+
// Fallback to element's src attribute
|
|
93
|
+
src = this.player.element.getAttribute('src') || this.player.element.src;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.player.log(`Loading HLS source: ${src}`, 'log');
|
|
97
|
+
|
|
98
|
+
if (!src) {
|
|
99
|
+
throw new Error('No HLS source found');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.hls.loadSource(src);
|
|
103
|
+
|
|
104
|
+
// Attach events
|
|
105
|
+
this.attachHlsEvents();
|
|
106
|
+
this.attachMediaEvents();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async loadHlsJs() {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
const script = document.createElement('script');
|
|
112
|
+
script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
|
|
113
|
+
script.onload = () => resolve();
|
|
114
|
+
script.onerror = () => reject(new Error('Failed to load hls.js'));
|
|
115
|
+
document.head.appendChild(script);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
attachHlsEvents() {
|
|
120
|
+
this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event, data) => {
|
|
121
|
+
this.player.log('HLS manifest loaded, found ' + data.levels.length + ' quality levels');
|
|
122
|
+
this.player.emit('hlsmanifestparsed', data);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
this.hls.on(window.Hls.Events.LEVEL_SWITCHED, (event, data) => {
|
|
126
|
+
this.player.log('HLS level switched to ' + data.level);
|
|
127
|
+
this.player.emit('hlslevelswitched', data);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
this.hls.on(window.Hls.Events.ERROR, (event, data) => {
|
|
131
|
+
this.handleHlsError(data);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.hls.on(window.Hls.Events.FRAG_BUFFERED, () => {
|
|
135
|
+
this.player.state.buffering = false;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
attachMediaEvents() {
|
|
140
|
+
// Use same events as HTML5 renderer
|
|
141
|
+
this.media.addEventListener('loadedmetadata', () => {
|
|
142
|
+
this.player.state.duration = this.media.duration;
|
|
143
|
+
this.player.emit('loadedmetadata');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
this.media.addEventListener('play', () => {
|
|
147
|
+
this.player.state.playing = true;
|
|
148
|
+
this.player.state.paused = false;
|
|
149
|
+
this.player.state.ended = false;
|
|
150
|
+
this.player.emit('play');
|
|
151
|
+
|
|
152
|
+
if (this.player.options.onPlay) {
|
|
153
|
+
this.player.options.onPlay.call(this.player);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
this.media.addEventListener('pause', () => {
|
|
158
|
+
this.player.state.playing = false;
|
|
159
|
+
this.player.state.paused = true;
|
|
160
|
+
this.player.emit('pause');
|
|
161
|
+
|
|
162
|
+
if (this.player.options.onPause) {
|
|
163
|
+
this.player.options.onPause.call(this.player);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.media.addEventListener('ended', () => {
|
|
168
|
+
this.player.state.playing = false;
|
|
169
|
+
this.player.state.paused = true;
|
|
170
|
+
this.player.state.ended = true;
|
|
171
|
+
this.player.emit('ended');
|
|
172
|
+
|
|
173
|
+
if (this.player.options.onEnded) {
|
|
174
|
+
this.player.options.onEnded.call(this.player);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (this.player.options.loop) {
|
|
178
|
+
this.player.seek(0);
|
|
179
|
+
this.player.play();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
this.media.addEventListener('timeupdate', () => {
|
|
184
|
+
this.player.state.currentTime = this.media.currentTime;
|
|
185
|
+
this.player.emit('timeupdate', this.media.currentTime);
|
|
186
|
+
|
|
187
|
+
if (this.player.options.onTimeUpdate) {
|
|
188
|
+
this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
this.media.addEventListener('volumechange', () => {
|
|
193
|
+
this.player.state.volume = this.media.volume;
|
|
194
|
+
this.player.state.muted = this.media.muted;
|
|
195
|
+
this.player.emit('volumechange', this.media.volume);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
this.media.addEventListener('waiting', () => {
|
|
199
|
+
this.player.state.buffering = true;
|
|
200
|
+
this.player.emit('waiting');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.media.addEventListener('canplay', () => {
|
|
204
|
+
this.player.state.buffering = false;
|
|
205
|
+
this.player.emit('canplay');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
this.media.addEventListener('error', () => {
|
|
209
|
+
this.player.handleError(this.media.error);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
handleHlsError(data) {
|
|
214
|
+
// Log detailed error info
|
|
215
|
+
this.player.log(`HLS Error - Type: ${data.type}, Details: ${data.details}, Fatal: ${data.fatal}`, 'warn');
|
|
216
|
+
if (data.response) {
|
|
217
|
+
this.player.log(`Response code: ${data.response.code}, URL: ${data.response.url}`, 'warn');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (data.fatal) {
|
|
221
|
+
switch (data.type) {
|
|
222
|
+
case window.Hls.ErrorTypes.NETWORK_ERROR:
|
|
223
|
+
this.player.log('Fatal network error, trying to recover...', 'error');
|
|
224
|
+
this.player.log(`Network error details: ${data.details}`, 'error');
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
this.hls.startLoad();
|
|
227
|
+
}, 1000);
|
|
228
|
+
break;
|
|
229
|
+
|
|
230
|
+
case window.Hls.ErrorTypes.MEDIA_ERROR:
|
|
231
|
+
this.player.log('Fatal media error, trying to recover...', 'error');
|
|
232
|
+
this.hls.recoverMediaError();
|
|
233
|
+
break;
|
|
234
|
+
|
|
235
|
+
default:
|
|
236
|
+
this.player.log('Fatal error, cannot recover', 'error');
|
|
237
|
+
this.player.handleError(new Error(`HLS Error: ${data.type} - ${data.details}`));
|
|
238
|
+
this.hls.destroy();
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
this.player.log('Non-fatal HLS error: ' + data.details, 'warn');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
play() {
|
|
247
|
+
const promise = this.media.play();
|
|
248
|
+
|
|
249
|
+
if (promise !== undefined) {
|
|
250
|
+
promise.catch(error => {
|
|
251
|
+
this.player.log('Play failed:', error, 'warn');
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
pause() {
|
|
257
|
+
this.media.pause();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
seek(time) {
|
|
261
|
+
this.media.currentTime = time;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
setVolume(volume) {
|
|
265
|
+
this.media.volume = volume;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setMuted(muted) {
|
|
269
|
+
this.media.muted = muted;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
setPlaybackSpeed(speed) {
|
|
273
|
+
this.media.playbackRate = speed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
switchQuality(levelIndex) {
|
|
277
|
+
if (this.hls) {
|
|
278
|
+
this.hls.currentLevel = levelIndex;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getQualities() {
|
|
283
|
+
if (this.hls && this.hls.levels) {
|
|
284
|
+
return this.hls.levels.map((level, index) => ({
|
|
285
|
+
index,
|
|
286
|
+
height: level.height,
|
|
287
|
+
width: level.width,
|
|
288
|
+
bitrate: level.bitrate,
|
|
289
|
+
name: `${level.height}p`
|
|
290
|
+
}));
|
|
291
|
+
}
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
destroy() {
|
|
296
|
+
if (this.hls) {
|
|
297
|
+
this.hls.destroy();
|
|
298
|
+
this.hls = null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|