myetv-player 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/.github/workflows/npm-publish.yml +30 -0
- package/LICENSE +21 -0
- package/README.md +866 -0
- package/build.js +189 -0
- package/css/README.md +1 -0
- package/css/myetv-player.css +13702 -0
- package/css/myetv-player.min.css +1 -0
- package/dist/README.md +1 -0
- package/dist/myetv-player.js +6408 -0
- package/dist/myetv-player.min.js +6183 -0
- package/package.json +27 -0
- package/plugins/README.md +1 -0
- package/plugins/google-analytics/README.md +1 -0
- package/plugins/google-analytics/myetv-player-g-analytics-plugin.js +548 -0
- package/plugins/youtube/README.md +1 -0
- package/plugins/youtube/myetv-player-youtube-plugin.js +418 -0
- package/scss/README.md +1 -0
- package/scss/_audio-player.scss +21 -0
- package/scss/_base.scss +131 -0
- package/scss/_controls.scss +30 -0
- package/scss/_loading.scss +111 -0
- package/scss/_menus.scss +4070 -0
- package/scss/_mixins.scss +112 -0
- package/scss/_poster.scss +8 -0
- package/scss/_progress-bar.scss +2203 -0
- package/scss/_resolution.scss +68 -0
- package/scss/_responsive.scss +1532 -0
- package/scss/_themes.scss +30 -0
- package/scss/_title-overlay.scss +2262 -0
- package/scss/_tooltips.scss +7 -0
- package/scss/_variables.scss +49 -0
- package/scss/_video.scss +2401 -0
- package/scss/_volume.scss +1981 -0
- package/scss/_watermark.scss +8 -0
- package/scss/myetv-player.scss +51 -0
- package/scss/package.json +16 -0
- package/src/README.md +1 -0
- package/src/chapters.js +521 -0
- package/src/controls.js +1005 -0
- package/src/core.js +1650 -0
- package/src/events.js +330 -0
- package/src/fullscreen.js +82 -0
- package/src/i18n.js +348 -0
- package/src/playlist.js +177 -0
- package/src/plugins.js +384 -0
- package/src/quality.js +921 -0
- package/src/streaming.js +346 -0
- package/src/subtitles.js +426 -0
- package/src/utils.js +51 -0
- package/src/watermark.js +195 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MYETV Player - YouTube Plugin (Enhanced)
|
|
3
|
+
* File: myetv-player-youtube-plugin.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
(function () {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
class YouTubePlugin {
|
|
10
|
+
constructor(player, options = {}) {
|
|
11
|
+
this.player = player;
|
|
12
|
+
this.options = {
|
|
13
|
+
videoId: options.videoId || null, // Direct video ID
|
|
14
|
+
apiKey: options.apiKey || null,
|
|
15
|
+
autoplay: options.autoplay !== undefined ? options.autoplay : false,
|
|
16
|
+
showYouTubeUI: options.showYouTubeUI !== undefined ? options.showYouTubeUI : false,
|
|
17
|
+
autoLoadFromData: options.autoLoadFromData !== undefined ? options.autoLoadFromData : true,
|
|
18
|
+
quality: options.quality || 'default', // 'default', 'hd720', 'hd1080'
|
|
19
|
+
...options
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
this.ytPlayer = null;
|
|
23
|
+
this.isYouTubeReady = false;
|
|
24
|
+
this.videoId = this.options.videoId;
|
|
25
|
+
|
|
26
|
+
// Get plugin API
|
|
27
|
+
this.api = player.getPluginAPI();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Setup plugin (called automatically after instantiation)
|
|
32
|
+
*/
|
|
33
|
+
setup() {
|
|
34
|
+
this.api.debug('YouTube plugin setup started');
|
|
35
|
+
|
|
36
|
+
// Load YouTube IFrame API
|
|
37
|
+
this.loadYouTubeAPI();
|
|
38
|
+
|
|
39
|
+
// Add custom methods to player
|
|
40
|
+
this.addPlayerMethods();
|
|
41
|
+
|
|
42
|
+
// Register hooks
|
|
43
|
+
this.registerHooks();
|
|
44
|
+
|
|
45
|
+
// Auto-load video ID from various sources
|
|
46
|
+
if (this.options.autoLoadFromData) {
|
|
47
|
+
this.autoDetectVideoId();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If video ID is provided in options, load it immediately
|
|
51
|
+
if (this.videoId) {
|
|
52
|
+
this.waitForAPIThenLoad();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.api.debug('YouTube plugin setup completed');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add custom methods to player instance
|
|
60
|
+
*/
|
|
61
|
+
addPlayerMethods() {
|
|
62
|
+
// Load YouTube video by ID
|
|
63
|
+
this.player.loadYouTubeVideo = (videoId, options = {}) => {
|
|
64
|
+
return this.loadVideo(videoId, options);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Get current YouTube video ID
|
|
68
|
+
this.player.getYouTubeVideoId = () => {
|
|
69
|
+
return this.videoId;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Check if YouTube is active
|
|
73
|
+
this.player.isYouTubeActive = () => {
|
|
74
|
+
return this.ytPlayer !== null;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Register plugin hooks
|
|
80
|
+
*/
|
|
81
|
+
registerHooks() {
|
|
82
|
+
// Check for YouTube URL before play
|
|
83
|
+
this.api.registerHook('beforePlay', (data) => {
|
|
84
|
+
this.checkForYouTubeUrl();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Auto-detect video ID from various sources
|
|
90
|
+
*/
|
|
91
|
+
autoDetectVideoId() {
|
|
92
|
+
// Priority 1: Check data-video-id attribute
|
|
93
|
+
const dataVideoId = this.api.video.getAttribute('data-video-id');
|
|
94
|
+
const dataVideoType = this.api.video.getAttribute('data-video-type');
|
|
95
|
+
|
|
96
|
+
if (dataVideoId && dataVideoType === 'youtube') {
|
|
97
|
+
this.videoId = dataVideoId;
|
|
98
|
+
this.api.debug('YouTube video ID detected from data-video-id: ' + this.videoId);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Priority 2: Check video source URL
|
|
103
|
+
const src = this.api.video.src || this.api.video.currentSrc;
|
|
104
|
+
if (src) {
|
|
105
|
+
const extractedId = this.extractYouTubeVideoId(src);
|
|
106
|
+
if (extractedId) {
|
|
107
|
+
this.videoId = extractedId;
|
|
108
|
+
this.api.debug('YouTube video ID detected from src: ' + this.videoId);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Priority 3: Check source elements
|
|
114
|
+
const sources = this.api.video.querySelectorAll('source');
|
|
115
|
+
for (const source of sources) {
|
|
116
|
+
const sourceSrc = source.getAttribute('src');
|
|
117
|
+
const sourceType = source.getAttribute('type');
|
|
118
|
+
|
|
119
|
+
if (sourceType === 'video/youtube' || sourceType === 'video/x-youtube') {
|
|
120
|
+
const extractedId = this.extractYouTubeVideoId(sourceSrc);
|
|
121
|
+
if (extractedId) {
|
|
122
|
+
this.videoId = extractedId;
|
|
123
|
+
this.api.debug('YouTube video ID detected from source element: ' + this.videoId);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Wait for API to be ready then load video
|
|
132
|
+
*/
|
|
133
|
+
waitForAPIThenLoad() {
|
|
134
|
+
if (this.isYouTubeReady) {
|
|
135
|
+
this.loadVideo(this.videoId);
|
|
136
|
+
} else {
|
|
137
|
+
// Wait for API to be ready
|
|
138
|
+
const checkInterval = setInterval(() => {
|
|
139
|
+
if (this.isYouTubeReady) {
|
|
140
|
+
clearInterval(checkInterval);
|
|
141
|
+
this.loadVideo(this.videoId);
|
|
142
|
+
}
|
|
143
|
+
}, 100);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Load YouTube IFrame API
|
|
149
|
+
*/
|
|
150
|
+
loadYouTubeAPI() {
|
|
151
|
+
if (window.YT && window.YT.Player) {
|
|
152
|
+
this.isYouTubeReady = true;
|
|
153
|
+
this.api.debug('YouTube API already loaded');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Load YouTube IFrame API script
|
|
158
|
+
const tag = document.createElement('script');
|
|
159
|
+
tag.src = 'https://www.youtube.com/iframe_api';
|
|
160
|
+
const firstScriptTag = document.getElementsByTagName('script')[0];
|
|
161
|
+
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
|
162
|
+
|
|
163
|
+
// Set callback for when API is ready
|
|
164
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
165
|
+
this.isYouTubeReady = true;
|
|
166
|
+
this.api.debug('YouTube API loaded and ready');
|
|
167
|
+
|
|
168
|
+
// Trigger custom event
|
|
169
|
+
this.api.triggerEvent('youtubeplugin:ready', {});
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if current video source is a YouTube URL
|
|
175
|
+
*/
|
|
176
|
+
checkForYouTubeUrl() {
|
|
177
|
+
const src = this.api.video.src || this.api.video.currentSrc;
|
|
178
|
+
const videoId = this.extractYouTubeVideoId(src);
|
|
179
|
+
|
|
180
|
+
if (videoId && videoId !== this.videoId) {
|
|
181
|
+
this.loadVideo(videoId);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Extract YouTube video ID from URL
|
|
190
|
+
* Supports various YouTube URL formats
|
|
191
|
+
* @param {String} url - YouTube URL or video ID
|
|
192
|
+
* @returns {String|null} Video ID or null
|
|
193
|
+
*/
|
|
194
|
+
extractYouTubeVideoId(url) {
|
|
195
|
+
if (!url) return null;
|
|
196
|
+
|
|
197
|
+
// Remove whitespace
|
|
198
|
+
url = url.trim();
|
|
199
|
+
|
|
200
|
+
const patterns = [
|
|
201
|
+
// Standard watch URL
|
|
202
|
+
/(?:youtube\.com\/watch\?v=)([^&\n?#]+)/,
|
|
203
|
+
// Shortened youtu.be URL
|
|
204
|
+
/(?:youtu\.be\/)([^&\n?#]+)/,
|
|
205
|
+
// Embed URL
|
|
206
|
+
/(?:youtube\.com\/embed\/)([^&\n?#]+)/,
|
|
207
|
+
// Direct video ID (11 characters)
|
|
208
|
+
/^([a-zA-Z0-9_-]{11})$/
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
for (const pattern of patterns) {
|
|
212
|
+
const match = url.match(pattern);
|
|
213
|
+
if (match && match[1]) {
|
|
214
|
+
return match[1];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Load YouTube video by ID
|
|
223
|
+
* @param {String} videoId - YouTube video ID
|
|
224
|
+
* @param {Object} options - Load options
|
|
225
|
+
*/
|
|
226
|
+
loadVideo(videoId, options = {}) {
|
|
227
|
+
if (!videoId) {
|
|
228
|
+
this.api.debug('No video ID provided to loadVideo()');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!this.isYouTubeReady) {
|
|
233
|
+
this.api.debug('YouTube API not ready yet, waiting...');
|
|
234
|
+
setTimeout(() => this.loadVideo(videoId, options), 100);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.videoId = videoId;
|
|
239
|
+
|
|
240
|
+
// Hide native video element
|
|
241
|
+
this.api.video.style.display = 'none';
|
|
242
|
+
|
|
243
|
+
// Create YouTube player container if not exists
|
|
244
|
+
if (!this.ytPlayerContainer) {
|
|
245
|
+
this.ytPlayerContainer = document.createElement('div');
|
|
246
|
+
this.ytPlayerContainer.id = 'yt-player-' + Date.now();
|
|
247
|
+
this.ytPlayerContainer.className = 'yt-player-container';
|
|
248
|
+
|
|
249
|
+
// Style to match video element
|
|
250
|
+
this.ytPlayerContainer.style.position = 'absolute';
|
|
251
|
+
this.ytPlayerContainer.style.top = '0';
|
|
252
|
+
this.ytPlayerContainer.style.left = '0';
|
|
253
|
+
this.ytPlayerContainer.style.width = '100%';
|
|
254
|
+
this.ytPlayerContainer.style.height = '100%';
|
|
255
|
+
|
|
256
|
+
this.api.container.insertBefore(this.ytPlayerContainer, this.api.controls);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Destroy existing player if present
|
|
260
|
+
if (this.ytPlayer) {
|
|
261
|
+
this.ytPlayer.destroy();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Merge options
|
|
265
|
+
const playerVars = {
|
|
266
|
+
autoplay: this.options.autoplay ? 1 : 0,
|
|
267
|
+
controls: this.options.showYouTubeUI ? 1 : 0,
|
|
268
|
+
modestbranding: 1,
|
|
269
|
+
rel: 0,
|
|
270
|
+
...options.playerVars
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Initialize YouTube player
|
|
274
|
+
this.ytPlayer = new YT.Player(this.ytPlayerContainer.id, {
|
|
275
|
+
videoId: videoId,
|
|
276
|
+
playerVars: playerVars,
|
|
277
|
+
events: {
|
|
278
|
+
'onReady': (event) => this.onPlayerReady(event),
|
|
279
|
+
'onStateChange': (event) => this.onPlayerStateChange(event),
|
|
280
|
+
'onError': (event) => this.onPlayerError(event)
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.api.debug('YouTube video loaded: ' + videoId);
|
|
285
|
+
this.api.triggerEvent('youtubeplugin:videoloaded', { videoId });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* YouTube player ready callback
|
|
290
|
+
*/
|
|
291
|
+
onPlayerReady(event) {
|
|
292
|
+
this.api.debug('YouTube player ready');
|
|
293
|
+
|
|
294
|
+
// Sync controls with YouTube player
|
|
295
|
+
this.syncControls();
|
|
296
|
+
|
|
297
|
+
// Set quality if specified
|
|
298
|
+
if (this.options.quality && this.options.quality !== 'default') {
|
|
299
|
+
this.ytPlayer.setPlaybackQuality(this.options.quality);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.api.triggerEvent('youtubeplugin:playerready', {});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* YouTube player state change callback
|
|
307
|
+
*/
|
|
308
|
+
onPlayerStateChange(event) {
|
|
309
|
+
switch (event.data) {
|
|
310
|
+
case YT.PlayerState.PLAYING:
|
|
311
|
+
this.api.triggerEvent('played', {});
|
|
312
|
+
break;
|
|
313
|
+
case YT.PlayerState.PAUSED:
|
|
314
|
+
this.api.triggerEvent('paused', {});
|
|
315
|
+
break;
|
|
316
|
+
case YT.PlayerState.ENDED:
|
|
317
|
+
this.api.triggerEvent('ended', {});
|
|
318
|
+
break;
|
|
319
|
+
case YT.PlayerState.BUFFERING:
|
|
320
|
+
this.api.debug('YouTube player buffering');
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* YouTube player error callback
|
|
327
|
+
*/
|
|
328
|
+
onPlayerError(event) {
|
|
329
|
+
const errorMessages = {
|
|
330
|
+
2: 'Invalid video ID',
|
|
331
|
+
5: 'HTML5 player error',
|
|
332
|
+
100: 'Video not found or private',
|
|
333
|
+
101: 'Video not allowed to be played in embedded players',
|
|
334
|
+
150: 'Video not allowed to be played in embedded players'
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const errorMsg = errorMessages[event.data] || 'Unknown error';
|
|
338
|
+
this.api.debug('YouTube player error: ' + errorMsg);
|
|
339
|
+
|
|
340
|
+
this.api.triggerEvent('youtubeplugin:error', {
|
|
341
|
+
errorCode: event.data,
|
|
342
|
+
errorMessage: errorMsg
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Sync player controls with YouTube player
|
|
348
|
+
*/
|
|
349
|
+
syncControls() {
|
|
350
|
+
// Override play/pause methods
|
|
351
|
+
const originalPlay = this.player.play;
|
|
352
|
+
const originalPause = this.player.pause;
|
|
353
|
+
|
|
354
|
+
this.player.play = () => {
|
|
355
|
+
if (this.ytPlayer && this.ytPlayer.playVideo) {
|
|
356
|
+
this.ytPlayer.playVideo();
|
|
357
|
+
} else {
|
|
358
|
+
originalPlay.call(this.player);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
this.player.pause = () => {
|
|
363
|
+
if (this.ytPlayer && this.ytPlayer.pauseVideo) {
|
|
364
|
+
this.ytPlayer.pauseVideo();
|
|
365
|
+
} else {
|
|
366
|
+
originalPause.call(this.player);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Sync time updates
|
|
371
|
+
if (this.ytPlayer) {
|
|
372
|
+
setInterval(() => {
|
|
373
|
+
if (this.ytPlayer && this.ytPlayer.getCurrentTime) {
|
|
374
|
+
const currentTime = this.ytPlayer.getCurrentTime();
|
|
375
|
+
const duration = this.ytPlayer.getDuration();
|
|
376
|
+
|
|
377
|
+
// Update progress bar if available
|
|
378
|
+
if (this.api.player.progressFilled && duration) {
|
|
379
|
+
const progress = (currentTime / duration) * 100;
|
|
380
|
+
this.api.player.progressFilled.style.width = progress + '%';
|
|
381
|
+
if (this.api.player.progressHandle) {
|
|
382
|
+
this.api.player.progressHandle.style.left = progress + '%';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}, 250);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Dispose plugin
|
|
392
|
+
*/
|
|
393
|
+
dispose() {
|
|
394
|
+
this.api.debug('YouTube plugin disposed');
|
|
395
|
+
|
|
396
|
+
// Destroy YouTube player
|
|
397
|
+
if (this.ytPlayer && this.ytPlayer.destroy) {
|
|
398
|
+
this.ytPlayer.destroy();
|
|
399
|
+
this.ytPlayer = null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Remove container
|
|
403
|
+
if (this.ytPlayerContainer) {
|
|
404
|
+
this.ytPlayerContainer.remove();
|
|
405
|
+
this.ytPlayerContainer = null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Show native video element again
|
|
409
|
+
if (this.api.video) {
|
|
410
|
+
this.api.video.style.display = '';
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Register plugin globally
|
|
416
|
+
window.registerMYETVPlugin('youtube', YouTubePlugin);
|
|
417
|
+
|
|
418
|
+
})();
|
package/scss/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.audio-player {
|
|
2
|
+
width: 320px;
|
|
3
|
+
height: 80px;
|
|
4
|
+
|
|
5
|
+
video {
|
|
6
|
+
display: none !important;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.controls-wrapper {
|
|
10
|
+
height: 60px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.audio-wave-canvas {
|
|
14
|
+
display: block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
height: 60px;
|
|
17
|
+
background-color: #222;
|
|
18
|
+
border-radius: 4px;
|
|
19
|
+
margin-top: 5px;
|
|
20
|
+
}
|
|
21
|
+
}
|
package/scss/_base.scss
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// BASE STYLES
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
/* Main theme colors */
|
|
7
|
+
--player-primary-color: goldenrod;
|
|
8
|
+
--player-primary-hover: #daa520;
|
|
9
|
+
--player-primary-dark: #b8860b;
|
|
10
|
+
/* Control colors */
|
|
11
|
+
--player-button-color: white;
|
|
12
|
+
--player-button-hover: rgba(255, 255, 255, 0.1);
|
|
13
|
+
--player-button-active: rgba(255, 255, 255, 0.2);
|
|
14
|
+
/* Text colors */
|
|
15
|
+
--player-text-color: white;
|
|
16
|
+
--player-text-secondary: rgba(255, 255, 255, 0.8);
|
|
17
|
+
/* Background and overlay */
|
|
18
|
+
--player-bg-primary: #000;
|
|
19
|
+
--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.8) 100%);
|
|
20
|
+
--player-bg-title-overlay: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, transparent 100%);
|
|
21
|
+
--player-bg-menu: rgba(20, 20, 20, 0.95);
|
|
22
|
+
--player-bg-loading: rgba(0, 0, 0, 0.7);
|
|
23
|
+
/* Dimensions */
|
|
24
|
+
--player-border-radius: 12px;
|
|
25
|
+
--player-controls-padding: 20px 15px 15px;
|
|
26
|
+
--player-title-overlay-padding: 15px 20px 25px;
|
|
27
|
+
--player-button-padding: 8px;
|
|
28
|
+
--player-icon-size: 20px;
|
|
29
|
+
/* Progress and Volume bars */
|
|
30
|
+
--player-progress-height: 6px;
|
|
31
|
+
--player-progress-bg: rgba(255, 255, 255, 0.2);
|
|
32
|
+
--player-progress-buffer: rgba(255, 255, 255, 0.3);
|
|
33
|
+
--player-progress-handle-size: 16px;
|
|
34
|
+
--player-volume-height: 4px;
|
|
35
|
+
--player-volume-bg: rgba(255, 255, 255, 0.2);
|
|
36
|
+
--player-volume-handle-size: 14px;
|
|
37
|
+
--player-volume-fill: 100%;
|
|
38
|
+
/* Transitions */
|
|
39
|
+
--player-transition-fast: 0.2s ease;
|
|
40
|
+
--player-transition-normal: 0.3s ease;
|
|
41
|
+
/* Shadows */
|
|
42
|
+
--player-shadow-main: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
43
|
+
--player-shadow-menu: 0 4px 16px rgba(0, 0, 0, 0.2);
|
|
44
|
+
--player-shadow-tooltip: 0 3px 12px rgba(0, 0, 0, 0.4);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
* {
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
body {
|
|
53
|
+
margin: 0;
|
|
54
|
+
padding: 20px;
|
|
55
|
+
background: #1a1a1a;
|
|
56
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.video-container {
|
|
60
|
+
max-width: 1200px;
|
|
61
|
+
margin: 0 auto;
|
|
62
|
+
background: var(--player-bg-primary);
|
|
63
|
+
border-radius: var(--player-border-radius);
|
|
64
|
+
box-shadow: var(--player-shadow-main);
|
|
65
|
+
position: relative;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.video-container:fullscreen,
|
|
69
|
+
.video-container:-webkit-full-screen,
|
|
70
|
+
.video-container:-moz-full-screen {
|
|
71
|
+
width: 100vw;
|
|
72
|
+
height: 100vh;
|
|
73
|
+
border-radius: 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.video-wrapper {
|
|
77
|
+
position: relative;
|
|
78
|
+
width: 100%;
|
|
79
|
+
background: var(--player-bg-primary);
|
|
80
|
+
overflow: visible !important;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.video-wrapper.player-initialized .video-player {
|
|
84
|
+
visibility: visible;
|
|
85
|
+
opacity: 1;
|
|
86
|
+
transition: opacity 0.3s ease;
|
|
87
|
+
pointer-events: auto;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.video-wrapper.player-initialized .initial-loading {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
visibility: hidden;
|
|
93
|
+
transition: opacity 0.3s ease, visibility 0.3s ease;
|
|
94
|
+
transition-delay: 0.2s;
|
|
95
|
+
display: none;
|
|
96
|
+
transition-delay: 0.5s;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.video-wrapper:not(.has-controls) .video-watermark.hide-on-autohide {
|
|
100
|
+
visibility: hidden;
|
|
101
|
+
opacity: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomleft:not(.hide-on-autohide),
|
|
105
|
+
.video-wrapper:not(.has-controls) .video-watermark.watermark-bottomright:not(.hide-on-autohide) {
|
|
106
|
+
bottom: 15px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.video-wrapper.has-controls .video-watermark {
|
|
110
|
+
visibility: visible;
|
|
111
|
+
opacity: 0.7;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.video-wrapper.has-controls .video-watermark.watermark-bottomleft,
|
|
115
|
+
.video-wrapper.has-controls .video-watermark.watermark-bottomright {
|
|
116
|
+
bottom: 90px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.hidden {
|
|
120
|
+
display: none !important;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
/* DARK MODE THEME */
|
|
125
|
+
|
|
126
|
+
.player-theme-dark {
|
|
127
|
+
--player-bg-primary: #1a1a1a;
|
|
128
|
+
--player-bg-controls: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.9) 100%);
|
|
129
|
+
--player-bg-title-overlay: linear-gradient(180deg, rgba(30, 30, 30, 0.9) 0%, transparent 100%);
|
|
130
|
+
--player-bg-menu: rgba(30, 30, 30, 0.95);
|
|
131
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ===================================
|
|
2
|
+
// CONTROLS
|
|
3
|
+
// ===================================
|
|
4
|
+
|
|
5
|
+
@use 'mixins' as *;
|
|
6
|
+
@use 'variables' as *;
|
|
7
|
+
|
|
8
|
+
/* CONTROLS - IMPROVED RESPONSIVE DESIGN */
|
|
9
|
+
.controls {
|
|
10
|
+
position: absolute;
|
|
11
|
+
bottom: 0;
|
|
12
|
+
left: 0;
|
|
13
|
+
right: 0;
|
|
14
|
+
background: var(--player-bg-controls);
|
|
15
|
+
padding: var(--player-controls-padding);
|
|
16
|
+
opacity: 0;
|
|
17
|
+
transform: translateY(100%);
|
|
18
|
+
transition: all var(--player-transition-normal);
|
|
19
|
+
z-index: 10;
|
|
20
|
+
min-height: 70px !important;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.controls.show {
|
|
25
|
+
opacity: 1;
|
|
26
|
+
transform: translateY(0);
|
|
27
|
+
position: absolute !important;
|
|
28
|
+
bottom: 0 !important;
|
|
29
|
+
z-index: 20 !important;
|
|
30
|
+
}
|