@viewlift/player-backup 1.0.1-lighteningjs → 1.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/cjs/autoNextVideo-823821e8.js +1 -0
- package/cjs/bundle.css +657 -0
- package/cjs/caption-settings-67212c17.js +1 -0
- package/cjs/caption-settings-d42c056e.js +1 -0
- package/cjs/debugSelection-cbfdabb7.js +1 -0
- package/cjs/dvr_skin-f7f770ae.js +1 -0
- package/cjs/error-26d5a3c3.js +1 -0
- package/cjs/features-164c3cc6.js +1 -0
- package/cjs/generic-8b99b151.js +1 -0
- package/cjs/icons-7afb9c13.js +1 -0
- package/cjs/index-3dfe6459.js +1 -0
- package/cjs/index.js +1 -0
- package/cjs/keyboardShortcuts-cfd998c2.js +1 -0
- package/cjs/loader-393b838a.js +1 -0
- package/cjs/multicam-21c9a90b.js +1 -0
- package/cjs/multicam-a41fe4a6.js +1 -0
- package/cjs/multicamv2-76d8530b.js +1 -0
- package/cjs/multicamv2-c148fa24.js +1 -0
- package/cjs/mwebPotrait-3c11d7cf.js +1 -0
- package/cjs/mwebPotraitTray-b9d5d45a.js +1 -0
- package/cjs/nextEpisodeButton-300afadc.js +1 -0
- package/cjs/nextEpisodeButton-b6e6586f.js +1 -0
- package/cjs/nondvr_skin-ef2cb3b2.js +1 -0
- package/cjs/playlist-925f808b.js +1 -0
- package/cjs/playlist-df87ca90.js +1 -0
- package/cjs/renditions-bc841552.js +1 -0
- package/cjs/resolution-6a9adf54.js +1 -0
- package/cjs/series-82ec79d3.js +1 -0
- package/cjs/series-9c237975.js +1 -0
- package/cjs/series-v2-24855152.js +1 -0
- package/cjs/series-v2-70137d8c.js +1 -0
- package/cjs/settings-icon-a63f46fc.js +1 -0
- package/cjs/settings-icon-e6a8feee.js +1 -0
- package/cjs/skin-095cc078.js +1 -0
- package/cjs/skin-225252ff.js +1 -0
- package/cjs/skin-39ab30e2.js +1 -0
- package/cjs/skin-84083352.js +1 -0
- package/cjs/skin-93a3e5ae.js +1 -0
- package/cjs/skin-983a2ea7.js +1 -0
- package/cjs/skin-afbec6d7.js +1 -0
- package/cjs/skin-ec49b877.js +1 -0
- package/cjs/skin-f225a42e.js +1 -0
- package/cjs/skin-fb394911.js +1 -0
- package/cjs/util-fa21734a.js +1 -0
- package/cjs/videoSelector-01a9ef43.js +1 -0
- package/cjs/videoSelector-fcdf11eb.js +1 -0
- package/esm/autoNextVideo-1672740e.js +1 -0
- package/esm/bundle.css +657 -0
- package/esm/caption-settings-2e47813b.js +1 -0
- package/esm/caption-settings-3eacf3a0.js +1 -0
- package/esm/debugSelection-2601b8b3.js +1 -0
- package/esm/dvr_skin-9cc1b144.js +1 -0
- package/esm/error-2395864b.js +1 -0
- package/esm/features-f86404ce.js +1 -0
- package/esm/generic-7c43020f.js +1 -0
- package/esm/icons-c18c70b7.js +1 -0
- package/esm/index-9f022e82.js +1 -0
- package/esm/index.js +1 -0
- package/esm/keyboardShortcuts-c06f3c7b.js +1 -0
- package/esm/loader-580278fe.js +1 -0
- package/esm/multicam-94da3759.js +1 -0
- package/esm/multicam-de741974.js +1 -0
- package/esm/multicamv2-0105c40f.js +1 -0
- package/esm/multicamv2-d006cffa.js +1 -0
- package/esm/mwebPotrait-3c1805d8.js +1 -0
- package/esm/mwebPotraitTray-5ab39989.js +1 -0
- package/esm/nextEpisodeButton-2ea69b56.js +1 -0
- package/esm/nextEpisodeButton-a1556cdf.js +1 -0
- package/esm/nondvr_skin-64be70e5.js +1 -0
- package/esm/playlist-032fbbf3.js +1 -0
- package/esm/playlist-7ecf432a.js +1 -0
- package/esm/renditions-97276745.js +1 -0
- package/esm/resolution-38cd4f5c.js +1 -0
- package/esm/series-041152aa.js +1 -0
- package/esm/series-977edced.js +1 -0
- package/esm/series-v2-0727ee8c.js +1 -0
- package/esm/series-v2-516c91c2.js +1 -0
- package/esm/settings-icon-0125bffd.js +1 -0
- package/esm/settings-icon-2b713fbf.js +1 -0
- package/esm/skin-2c638dc1.js +1 -0
- package/esm/skin-42c2f81d.js +1 -0
- package/esm/skin-5e2fa9d4.js +1 -0
- package/esm/skin-6093bfb3.js +1 -0
- package/esm/skin-60a41d44.js +1 -0
- package/esm/skin-665ec17e.js +1 -0
- package/esm/skin-93e320fc.js +1 -0
- package/esm/skin-b939c35e.js +1 -0
- package/esm/skin-cd537931.js +1 -0
- package/esm/skin-d5510f70.js +1 -0
- package/esm/util-d9f5a7be.js +1 -0
- package/esm/videoSelector-8889c58e.js +1 -0
- package/esm/videoSelector-8e258d45.js +1 -0
- package/package.json +15 -31
- package/.babelrc +0 -8
- package/.browserslistrc +0 -2
- package/README.md +0 -29
- package/babel.config.json +0 -8
- package/dist/index.cjs.css +0 -2
- package/dist/index.cjs.css.map +0 -1
- package/dist/index.cjs.js +0 -1
- package/dist/index.d.ts +0 -45
- package/dist/index.esm.css +0 -2
- package/dist/index.esm.css.map +0 -1
- package/dist/index.esm.js +0 -1
- package/dist/main-player/player-ui.d.ts +0 -19
- package/dist/player-components/player-buttons/playerButtons.d.ts +0 -38
- package/dist/player-components/player-seekbar/playerSeekbar.d.ts +0 -25
- package/dist/player-ui/player-ui.d.ts +0 -18
- package/dist/playerInitializer.d.ts +0 -13
- package/rollup.config.mjs +0 -37
- package/src/index.ts +0 -242
- package/src/main-player/player-ui.ts +0 -65
- package/src/player-components/player-buttons/playerButtons.css +0 -0
- package/src/player-components/player-buttons/playerButtons.ts +0 -127
- package/src/player-components/player-seekbar/playerSeekbar.css +0 -75
- package/src/player-components/player-seekbar/playerSeekbar.ts +0 -158
- package/src/player-ui/player-ui.css +0 -8
- package/src/player-ui/player-ui.ts +0 -79
- package/src/playerInitializer.ts +0 -54
- package/tsconfig.json +0 -14
package/src/index.ts
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
// LightningJS-compatible Video Player Component (ES5 compatible)
|
|
2
|
-
|
|
3
|
-
export interface VideoPlayerOptions {
|
|
4
|
-
// Optional playback callbacks
|
|
5
|
-
onPlaybackSuccess?: () => void;
|
|
6
|
-
onPlaybackEnded?: () => void;
|
|
7
|
-
onPlaybackError?: (error?: any) => void;
|
|
8
|
-
src: string;
|
|
9
|
-
autoplay?: boolean;
|
|
10
|
-
controls?: boolean;
|
|
11
|
-
width?: number;
|
|
12
|
-
height?: number;
|
|
13
|
-
// Event callbacks
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type EventHandler = (...args: any[]) => void;
|
|
17
|
-
|
|
18
|
-
type VideoPlayerEvent =
|
|
19
|
-
| 'play'
|
|
20
|
-
| 'pause'
|
|
21
|
-
| 'ended'
|
|
22
|
-
| 'timeupdate'
|
|
23
|
-
| 'error'
|
|
24
|
-
| 'loadedmetadata'
|
|
25
|
-
| 'seeking'
|
|
26
|
-
| 'seeked'
|
|
27
|
-
| 'volumechange';
|
|
28
|
-
|
|
29
|
-
export class LightningVideoPlayer {
|
|
30
|
-
/**
|
|
31
|
-
* Reinitialize the current videoElement with a new set of options and handle autoplay.
|
|
32
|
-
*/
|
|
33
|
-
reinitialize(options: VideoPlayerOptions) {
|
|
34
|
-
this.options = options;
|
|
35
|
-
this.videoElement.src = options.src;
|
|
36
|
-
this.videoElement.autoplay = !!options.autoplay;
|
|
37
|
-
this.videoElement.controls = !!options.controls;
|
|
38
|
-
if (options.width) {
|
|
39
|
-
this.videoElement.width = options.width;
|
|
40
|
-
} else {
|
|
41
|
-
this.videoElement.removeAttribute('width');
|
|
42
|
-
}
|
|
43
|
-
if (options.height) {
|
|
44
|
-
this.videoElement.height = options.height;
|
|
45
|
-
} else {
|
|
46
|
-
this.videoElement.removeAttribute('height');
|
|
47
|
-
}
|
|
48
|
-
// Reset playback state
|
|
49
|
-
this.videoElement.currentTime = 0;
|
|
50
|
-
this.videoElement.pause();
|
|
51
|
-
// If autoplay is set, start playback
|
|
52
|
-
if (options.autoplay) {
|
|
53
|
-
// Wait for metadata to be loaded before playing (for some browsers)
|
|
54
|
-
const playOnLoaded = () => {
|
|
55
|
-
this.videoElement.play();
|
|
56
|
-
this.videoElement.removeEventListener('loadedmetadata', playOnLoaded);
|
|
57
|
-
};
|
|
58
|
-
this.videoElement.addEventListener('loadedmetadata', playOnLoaded);
|
|
59
|
-
// If already loaded, play immediately
|
|
60
|
-
if (this.videoElement.readyState >= 1) {
|
|
61
|
-
this.videoElement.play();
|
|
62
|
-
this.videoElement.removeEventListener('loadedmetadata', playOnLoaded);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
private videoElement: HTMLVideoElement;
|
|
67
|
-
private options: VideoPlayerOptions;
|
|
68
|
-
private listeners: { [event: string]: EventHandler[] } = {};
|
|
69
|
-
private boundEmitters: { [event: string]: EventListener } = {};
|
|
70
|
-
private boundHandlerMap: { [K in VideoPlayerEvent]?: (e: Event) => void } = {};
|
|
71
|
-
|
|
72
|
-
constructor(options: VideoPlayerOptions) {
|
|
73
|
-
this.options = options;
|
|
74
|
-
this.videoElement = document.createElement('video');
|
|
75
|
-
this.videoElement.src = options.src;
|
|
76
|
-
this.videoElement.autoplay = !!options.autoplay;
|
|
77
|
-
this.videoElement.controls = !!options.controls;
|
|
78
|
-
if (options.width) this.videoElement.width = options.width;
|
|
79
|
-
if (options.height) this.videoElement.height = options.height;
|
|
80
|
-
this.videoElement.style.display = 'block';
|
|
81
|
-
this.videoElement.style.background = 'black';
|
|
82
|
-
|
|
83
|
-
// Attach class event handlers for all player events
|
|
84
|
-
const handlerMap: { [K in VideoPlayerEvent]: (e: Event) => void } = {
|
|
85
|
-
play: this.onPlay,
|
|
86
|
-
pause: this.onPause,
|
|
87
|
-
ended: this.onEnded,
|
|
88
|
-
timeupdate: this.onTimeupdate,
|
|
89
|
-
error: this.onError,
|
|
90
|
-
loadedmetadata: this.onLoadedmetadata,
|
|
91
|
-
seeking: this.onSeeking,
|
|
92
|
-
seeked: this.onSeeked,
|
|
93
|
-
volumechange: this.onVolumechange,
|
|
94
|
-
};
|
|
95
|
-
(Object.keys(handlerMap) as VideoPlayerEvent[]).forEach(event => {
|
|
96
|
-
const boundHandler = handlerMap[event].bind(this);
|
|
97
|
-
this.boundHandlerMap[event] = boundHandler;
|
|
98
|
-
this.videoElement.addEventListener(event, boundHandler);
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
attachTo(container: HTMLElement) {
|
|
103
|
-
container.appendChild(this.videoElement);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
play() {
|
|
107
|
-
this.videoElement.play();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
pause() {
|
|
111
|
-
this.videoElement.pause();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
stop() {
|
|
115
|
-
this.videoElement.pause();
|
|
116
|
-
this.videoElement.currentTime = 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
destroy() {
|
|
120
|
-
if (this.videoElement.parentNode) {
|
|
121
|
-
this.videoElement.parentNode.removeChild(this.videoElement);
|
|
122
|
-
}
|
|
123
|
-
this.removeAllListeners();
|
|
124
|
-
// Remove class event handlers
|
|
125
|
-
(Object.keys(this.boundHandlerMap) as VideoPlayerEvent[]).forEach(event => {
|
|
126
|
-
if (this.boundHandlerMap[event]) {
|
|
127
|
-
this.videoElement.removeEventListener(event, this.boundHandlerMap[event]!);
|
|
128
|
-
delete this.boundHandlerMap[event];
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
get element(): HTMLVideoElement {
|
|
134
|
-
return this.videoElement;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Event callback function definitions
|
|
138
|
-
onPlay(e: Event) {
|
|
139
|
-
// Called when playback starts
|
|
140
|
-
if (typeof this.options.onPlaybackSuccess === 'function') {
|
|
141
|
-
this.options.onPlaybackSuccess();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
onPause(e: Event) {
|
|
146
|
-
// Called when playback is paused
|
|
147
|
-
// Override or pass as option
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
onEnded(e: Event) {
|
|
151
|
-
// Called when playback ends
|
|
152
|
-
if (typeof this.options.onPlaybackEnded === 'function') {
|
|
153
|
-
this.options.onPlaybackEnded();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
onTimeupdate(e: Event) {
|
|
158
|
-
// Called when playback position changes
|
|
159
|
-
// Override or pass as option
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
onError(e: Event) {
|
|
163
|
-
// Called on playback error
|
|
164
|
-
if (typeof this.options.onPlaybackError === 'function') {
|
|
165
|
-
this.options.onPlaybackError(e);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
onLoadedmetadata(e: Event) {
|
|
170
|
-
// Called when metadata is loaded
|
|
171
|
-
// Override or pass as option
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
onSeeking(e: Event) {
|
|
175
|
-
// Called when seeking starts
|
|
176
|
-
// Override or pass as option
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
onSeeked(e: Event) {
|
|
180
|
-
// Called when seeking ends
|
|
181
|
-
// Override or pass as option
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
onVolumechange(e: Event) {
|
|
185
|
-
// Called when volume changes
|
|
186
|
-
// Override or pass as option
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Event system
|
|
190
|
-
on(event: VideoPlayerEvent, handler: EventHandler) {
|
|
191
|
-
if (!this.listeners[event]) {
|
|
192
|
-
this.listeners[event] = [];
|
|
193
|
-
if (!this.boundEmitters[event]) {
|
|
194
|
-
this.boundEmitters[event] = this.emitEvent(event);
|
|
195
|
-
this.videoElement.addEventListener(event, this.boundEmitters[event]);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
this.listeners[event].push(handler);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
off(event: VideoPlayerEvent, handler: EventHandler) {
|
|
202
|
-
if (!this.listeners[event]) return;
|
|
203
|
-
this.listeners[event] = this.listeners[event].filter(h => h !== handler);
|
|
204
|
-
if (this.listeners[event].length === 0) {
|
|
205
|
-
if (this.boundEmitters[event]) {
|
|
206
|
-
this.videoElement.removeEventListener(event, this.boundEmitters[event]);
|
|
207
|
-
delete this.boundEmitters[event];
|
|
208
|
-
}
|
|
209
|
-
delete this.listeners[event];
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
once(event: VideoPlayerEvent, handler: EventHandler) {
|
|
214
|
-
const onceHandler: EventHandler = (...args) => {
|
|
215
|
-
handler(...args);
|
|
216
|
-
this.off(event, onceHandler);
|
|
217
|
-
};
|
|
218
|
-
this.on(event, onceHandler);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private emitEvent(event: VideoPlayerEvent) {
|
|
222
|
-
return (e: Event) => {
|
|
223
|
-
if (this.listeners[event]) {
|
|
224
|
-
this.listeners[event].forEach(handler => handler(e));
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
private removeAllListeners() {
|
|
230
|
-
(Object.keys(this.listeners) as VideoPlayerEvent[]).forEach(event => {
|
|
231
|
-
if (this.boundEmitters[event]) {
|
|
232
|
-
this.videoElement.removeEventListener(event, this.boundEmitters[event]);
|
|
233
|
-
delete this.boundEmitters[event];
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
this.listeners = {};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function getHandlerMethodName(event: string): string {
|
|
241
|
-
return 'on' + event.charAt(0).toUpperCase() + event.slice(1);
|
|
242
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Button } from '../player-components/player-buttons/playerButtons';
|
|
2
|
-
import { PlayerSeekbar } from '../player-components/player-seekbar/playerSeekbar';
|
|
3
|
-
|
|
4
|
-
interface VideoPlayerUIOptions {
|
|
5
|
-
player: { video: HTMLVideoElement; play: () => void; pause: () => void };
|
|
6
|
-
width?: string;
|
|
7
|
-
height?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
class VideoPlayerUI {
|
|
11
|
-
private player: { video: HTMLVideoElement; play: () => void; pause: () => void };
|
|
12
|
-
private container: HTMLDivElement;
|
|
13
|
-
private playPauseBtn?: Button;
|
|
14
|
-
private seekbar?: PlayerSeekbar;
|
|
15
|
-
|
|
16
|
-
constructor({ player, width = '1280px', height = '720px' }: VideoPlayerUIOptions) {
|
|
17
|
-
this.player = player;
|
|
18
|
-
this.container = document.createElement('div');
|
|
19
|
-
this.container.className = 'video-player-ui';
|
|
20
|
-
if (width) this.container.style.width = width;
|
|
21
|
-
if (height) this.container.style.height = height;
|
|
22
|
-
this.createControls(width, height);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private createControls(width?: string, height?: string) {
|
|
26
|
-
// Play/Pause button using Button class
|
|
27
|
-
this.playPauseBtn = new Button({
|
|
28
|
-
imgSrc: '', // Optionally provide an image
|
|
29
|
-
imgAltText: 'Play/Pause',
|
|
30
|
-
buttonText: 'Play',
|
|
31
|
-
width: 80,
|
|
32
|
-
height: 40,
|
|
33
|
-
klassname: 'play-pause-btn',
|
|
34
|
-
focused: false,
|
|
35
|
-
disabled: false,
|
|
36
|
-
handleClick: () => {
|
|
37
|
-
if (this.player.video.paused) {
|
|
38
|
-
this.player.play();
|
|
39
|
-
if (this.playPauseBtn && this.playPauseBtn.buttonElement) {
|
|
40
|
-
this.playPauseBtn.buttonElement.textContent = 'Pause';
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
this.player.pause();
|
|
44
|
-
if (this.playPauseBtn && this.playPauseBtn.buttonElement) {
|
|
45
|
-
this.playPauseBtn.buttonElement.textContent = 'Play';
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
this.playPauseBtn.mount(this.container);
|
|
51
|
-
// Attach the video element
|
|
52
|
-
if (width) this.player.video.style.width = width;
|
|
53
|
-
if (height) this.player.video.style.height = height;
|
|
54
|
-
this.container.appendChild(this.player.video);
|
|
55
|
-
// Add the seekbar below the video
|
|
56
|
-
this.seekbar = new PlayerSeekbar(this.player.video);
|
|
57
|
-
this.seekbar.mount(this.container);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
attachTo(parent: HTMLElement) {
|
|
61
|
-
parent.appendChild(this.container);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export default VideoPlayerUI;
|
|
File without changes
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import './playerButtons.css';
|
|
2
|
-
|
|
3
|
-
type EventHandler = (event?: any) => void;
|
|
4
|
-
|
|
5
|
-
type ButtonProps = {
|
|
6
|
-
imgSrc: string;
|
|
7
|
-
imgAltText: string;
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
klassname: string;
|
|
11
|
-
buttonText: string;
|
|
12
|
-
focused: boolean;
|
|
13
|
-
disabled: boolean;
|
|
14
|
-
handleClick: EventHandler
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export class Button {
|
|
18
|
-
|
|
19
|
-
private imgSrc: string;
|
|
20
|
-
private imgAltText: string;
|
|
21
|
-
private klassname: string;
|
|
22
|
-
private width: number;
|
|
23
|
-
private height: number;
|
|
24
|
-
private focused: boolean;
|
|
25
|
-
private disabled: boolean;
|
|
26
|
-
private handleClick: EventHandler;
|
|
27
|
-
private buttonText: string;
|
|
28
|
-
public buttonElement?: HTMLButtonElement;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
constructor(props: ButtonProps) {
|
|
33
|
-
this.imgSrc = props.imgSrc || '';
|
|
34
|
-
this.imgAltText = props.imgAltText || '';
|
|
35
|
-
this.buttonText = props.buttonText || '';
|
|
36
|
-
this.klassname = props.klassname || 'default-button';
|
|
37
|
-
this.width = props.width || 100;
|
|
38
|
-
this.height = props.height || 50;
|
|
39
|
-
this.focused = props.focused || false;
|
|
40
|
-
this.disabled = props.disabled || false;
|
|
41
|
-
this.handleClick = props.handleClick || (() => {});
|
|
42
|
-
|
|
43
|
-
if (typeof props.handleClick !== 'function') {
|
|
44
|
-
throw new Error('Button Class: handleClick must be a function');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (this.disabled) {
|
|
48
|
-
this.klassname += ' disabled';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (this.focused) {
|
|
52
|
-
this.klassname += ' focused';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.handleClick = this.handleClick.bind(this);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Mounts the button to the specified mount point
|
|
60
|
-
* @param mountPoint The HTML element to which the button will be appended
|
|
61
|
-
*/
|
|
62
|
-
mount(mountPoint: HTMLElement) {
|
|
63
|
-
// Create the button element
|
|
64
|
-
const button = document.createElement('button');
|
|
65
|
-
button.className = this.klassname;
|
|
66
|
-
button.style.width = `${this.width}px`;
|
|
67
|
-
button.style.height = `${this.height}px`;
|
|
68
|
-
button.disabled = this.disabled;
|
|
69
|
-
button.tabIndex = 0;
|
|
70
|
-
|
|
71
|
-
// Add image or text
|
|
72
|
-
if (this.imgSrc) {
|
|
73
|
-
const img = document.createElement('img');
|
|
74
|
-
img.src = this.imgSrc;
|
|
75
|
-
img.alt = this.imgAltText;
|
|
76
|
-
button.appendChild(img);
|
|
77
|
-
} else {
|
|
78
|
-
const span = document.createElement('span');
|
|
79
|
-
span.textContent = this.buttonText;
|
|
80
|
-
button.appendChild(span);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Attach event listeners
|
|
84
|
-
button.addEventListener('click', this.handleClick);
|
|
85
|
-
button.addEventListener('focus', () => {
|
|
86
|
-
this.focused = true;
|
|
87
|
-
button.classList.add('focused');
|
|
88
|
-
});
|
|
89
|
-
button.addEventListener('blur', () => {
|
|
90
|
-
this.focused = false;
|
|
91
|
-
button.classList.remove('focused');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Store reference and append
|
|
95
|
-
this.buttonElement = button;
|
|
96
|
-
mountPoint.appendChild(button);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Removes the click event listener from the button
|
|
101
|
-
*/
|
|
102
|
-
unmount() {
|
|
103
|
-
if (this.buttonElement) {
|
|
104
|
-
this.buttonElement.removeEventListener('click', this.handleClick);
|
|
105
|
-
this.buttonElement.removeEventListener('focus', this.handleClick);
|
|
106
|
-
this.buttonElement.removeEventListener('blur', this.handleClick);
|
|
107
|
-
this.buttonElement = undefined;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
focus() {
|
|
112
|
-
if (this.buttonElement) {
|
|
113
|
-
this.buttonElement.focus();
|
|
114
|
-
this.buttonElement.classList.add('focused');
|
|
115
|
-
this.focused = true;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
blur() {
|
|
120
|
-
if (this.buttonElement) {
|
|
121
|
-
this.buttonElement.blur();
|
|
122
|
-
this.buttonElement.classList.remove('focused');
|
|
123
|
-
this.focused = false;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
.seekbar-container {
|
|
2
|
-
position: absolute;
|
|
3
|
-
left: 50%;
|
|
4
|
-
transform: translateX(-50%);
|
|
5
|
-
bottom: 50px;
|
|
6
|
-
height: 12px;
|
|
7
|
-
background: #444;
|
|
8
|
-
width: 90%;
|
|
9
|
-
cursor: pointer;
|
|
10
|
-
border-radius: 6px;
|
|
11
|
-
margin: 0;
|
|
12
|
-
user-select: none;
|
|
13
|
-
z-index: 20;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.seekbar-buffer {
|
|
17
|
-
position: absolute;
|
|
18
|
-
height: 100%;
|
|
19
|
-
background: #888;
|
|
20
|
-
width: 0;
|
|
21
|
-
border-radius: 6px;
|
|
22
|
-
z-index: 1;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.seekbar-progress {
|
|
26
|
-
position: absolute;
|
|
27
|
-
height: 100%;
|
|
28
|
-
background: #4169E1;
|
|
29
|
-
width: 0;
|
|
30
|
-
border-radius: 6px;
|
|
31
|
-
z-index: 2;
|
|
32
|
-
transition: width 0.1s linear;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.seekbar-thumb {
|
|
36
|
-
position: absolute;
|
|
37
|
-
top: 50%;
|
|
38
|
-
transform: translate(-50%, -50%);
|
|
39
|
-
width: 16px;
|
|
40
|
-
height: 16px;
|
|
41
|
-
background: #fff;
|
|
42
|
-
border-radius: 50%;
|
|
43
|
-
border: 2px solid #4169E1;
|
|
44
|
-
z-index: 3;
|
|
45
|
-
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
46
|
-
cursor: pointer;
|
|
47
|
-
transition: background 0.2s;
|
|
48
|
-
}
|
|
49
|
-
.seekbar-thumb:hover {
|
|
50
|
-
background: #ffeaea;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.seekbar-tooltip {
|
|
54
|
-
position: absolute;
|
|
55
|
-
bottom: 24px;
|
|
56
|
-
left: 0;
|
|
57
|
-
transform: translateX(-50%);
|
|
58
|
-
background: #222;
|
|
59
|
-
color: #fff;
|
|
60
|
-
padding: 2px 8px;
|
|
61
|
-
border-radius: 4px;
|
|
62
|
-
font-size: 12px;
|
|
63
|
-
white-space: nowrap;
|
|
64
|
-
pointer-events: none;
|
|
65
|
-
z-index: 10;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.seekbar-time-label, .seekbar-duration-label {
|
|
69
|
-
position: relative;
|
|
70
|
-
top: 18px;
|
|
71
|
-
font-size: 13px;
|
|
72
|
-
color: #eee;
|
|
73
|
-
margin-right: 4px;
|
|
74
|
-
z-index: 5;
|
|
75
|
-
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import './playerSeekbar.css';
|
|
2
|
-
|
|
3
|
-
export class PlayerSeekbar {
|
|
4
|
-
private container: HTMLDivElement;
|
|
5
|
-
private progress: HTMLDivElement;
|
|
6
|
-
private buffer: HTMLDivElement;
|
|
7
|
-
private thumb: HTMLDivElement;
|
|
8
|
-
private tooltip: HTMLDivElement;
|
|
9
|
-
private video: HTMLVideoElement;
|
|
10
|
-
private timeLabel: HTMLSpanElement;
|
|
11
|
-
private durationLabel: HTMLSpanElement;
|
|
12
|
-
private isDragging: boolean = false;
|
|
13
|
-
private focused: boolean = false;
|
|
14
|
-
|
|
15
|
-
constructor(video: HTMLVideoElement) {
|
|
16
|
-
this.video = video;
|
|
17
|
-
this.container = document.createElement('div');
|
|
18
|
-
this.container.className = 'seekbar-container';
|
|
19
|
-
this.container.tabIndex = 0; // Make focusable
|
|
20
|
-
|
|
21
|
-
this.buffer = document.createElement('div');
|
|
22
|
-
this.buffer.className = 'seekbar-buffer';
|
|
23
|
-
|
|
24
|
-
this.progress = document.createElement('div');
|
|
25
|
-
this.progress.className = 'seekbar-progress';
|
|
26
|
-
|
|
27
|
-
this.thumb = document.createElement('div');
|
|
28
|
-
this.thumb.className = 'seekbar-thumb';
|
|
29
|
-
|
|
30
|
-
this.tooltip = document.createElement('div');
|
|
31
|
-
this.tooltip.className = 'seekbar-tooltip';
|
|
32
|
-
this.tooltip.style.display = 'none';
|
|
33
|
-
|
|
34
|
-
this.timeLabel = document.createElement('span');
|
|
35
|
-
this.timeLabel.className = 'seekbar-time-label';
|
|
36
|
-
this.durationLabel = document.createElement('span');
|
|
37
|
-
this.durationLabel.className = 'seekbar-duration-label';
|
|
38
|
-
|
|
39
|
-
this.container.appendChild(this.buffer);
|
|
40
|
-
this.container.appendChild(this.progress);
|
|
41
|
-
this.container.appendChild(this.thumb);
|
|
42
|
-
this.container.appendChild(this.tooltip);
|
|
43
|
-
this.container.appendChild(this.timeLabel);
|
|
44
|
-
this.container.appendChild(this.durationLabel);
|
|
45
|
-
|
|
46
|
-
// Update UI as video plays
|
|
47
|
-
this.video.addEventListener('timeupdate', () => this.update());
|
|
48
|
-
this.video.addEventListener('progress', () => this.updateBuffer());
|
|
49
|
-
this.video.addEventListener('durationchange', () => this.update());
|
|
50
|
-
|
|
51
|
-
// Seek on click
|
|
52
|
-
this.container.addEventListener('click', (e) => this.seek(e));
|
|
53
|
-
// Dragging
|
|
54
|
-
this.thumb.addEventListener('mousedown', (e) => this.startDrag(e));
|
|
55
|
-
// Tooltip on hover
|
|
56
|
-
this.container.addEventListener('mousemove', (e) => this.showTooltip(e));
|
|
57
|
-
this.container.addEventListener('mouseleave', () => this.hideTooltip());
|
|
58
|
-
|
|
59
|
-
// Focus/blur events for accessibility and visual feedback
|
|
60
|
-
this.container.addEventListener('focus', () => {
|
|
61
|
-
this.focused = true;
|
|
62
|
-
this.container.classList.add('focused');
|
|
63
|
-
});
|
|
64
|
-
this.container.addEventListener('blur', () => {
|
|
65
|
-
this.focused = false;
|
|
66
|
-
this.container.classList.remove('focused');
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public focus() {
|
|
71
|
-
this.container.focus();
|
|
72
|
-
this.container.classList.add('focused');
|
|
73
|
-
this.focused = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
public blur() {
|
|
77
|
-
this.container.blur();
|
|
78
|
-
this.container.classList.remove('focused');
|
|
79
|
-
this.focused = false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private formatTime(seconds: number): string {
|
|
83
|
-
if (isNaN(seconds)) return '0:00';
|
|
84
|
-
const m = Math.floor(seconds / 60);
|
|
85
|
-
const s = Math.floor(seconds % 60);
|
|
86
|
-
return `${m}:${String(s).padStart(2, '0')}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private update() {
|
|
90
|
-
const percent = (this.video.currentTime / this.video.duration) * 100;
|
|
91
|
-
this.progress.style.width = percent + '%';
|
|
92
|
-
this.thumb.style.left = percent + '%';
|
|
93
|
-
this.timeLabel.textContent = this.formatTime(this.video.currentTime);
|
|
94
|
-
this.durationLabel.textContent = ' / ' + this.formatTime(this.video.duration);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private updateBuffer() {
|
|
98
|
-
if (this.video.buffered.length) {
|
|
99
|
-
const end = this.video.buffered.end(this.video.buffered.length - 1);
|
|
100
|
-
const percent = (end / this.video.duration) * 100;
|
|
101
|
-
this.buffer.style.width = percent + '%';
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private seek(e: MouseEvent) {
|
|
106
|
-
if (this.isDragging) return;
|
|
107
|
-
const rect = this.container.getBoundingClientRect();
|
|
108
|
-
const percent = (e.clientX - rect.left) / rect.width;
|
|
109
|
-
this.video.currentTime = percent * this.video.duration;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private startDrag(e: MouseEvent) {
|
|
113
|
-
e.preventDefault();
|
|
114
|
-
this.isDragging = true;
|
|
115
|
-
const onMove = (moveEvent: MouseEvent) => {
|
|
116
|
-
const rect = this.container.getBoundingClientRect();
|
|
117
|
-
let percent = (moveEvent.clientX - rect.left) / rect.width;
|
|
118
|
-
percent = Math.max(0, Math.min(1, percent));
|
|
119
|
-
this.progress.style.width = percent * 100 + '%';
|
|
120
|
-
this.thumb.style.left = percent * 100 + '%';
|
|
121
|
-
this.timeLabel.textContent = this.formatTime(percent * this.video.duration);
|
|
122
|
-
};
|
|
123
|
-
const onUp = (upEvent: MouseEvent) => {
|
|
124
|
-
const rect = this.container.getBoundingClientRect();
|
|
125
|
-
let percent = (upEvent.clientX - rect.left) / rect.width;
|
|
126
|
-
percent = Math.max(0, Math.min(1, percent));
|
|
127
|
-
this.video.currentTime = percent * this.video.duration;
|
|
128
|
-
this.isDragging = false;
|
|
129
|
-
document.removeEventListener('mousemove', onMove);
|
|
130
|
-
document.removeEventListener('mouseup', onUp);
|
|
131
|
-
};
|
|
132
|
-
document.addEventListener('mousemove', onMove);
|
|
133
|
-
document.addEventListener('mouseup', onUp);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private showTooltip(e: MouseEvent) {
|
|
137
|
-
const rect = this.container.getBoundingClientRect();
|
|
138
|
-
const percent = (e.clientX - rect.left) / rect.width;
|
|
139
|
-
const time = percent * this.video.duration;
|
|
140
|
-
this.tooltip.textContent = this.formatTime(time);
|
|
141
|
-
this.tooltip.style.display = 'block';
|
|
142
|
-
this.tooltip.style.left = `${percent * 100}%`;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private hideTooltip() {
|
|
146
|
-
this.tooltip.style.display = 'none';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
public mount(parent: HTMLElement) {
|
|
150
|
-
parent.appendChild(this.container);
|
|
151
|
-
this.update();
|
|
152
|
-
this.updateBuffer();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
public getElement() {
|
|
156
|
-
return this.container;
|
|
157
|
-
}
|
|
158
|
-
}
|