jerrrycans commited on
Commit
14bb70c
·
verified ·
1 Parent(s): bf7a056

Update bot/server/templates/player.html

Browse files
Files changed (1) hide show
  1. bot/server/templates/player.html +385 -312
bot/server/templates/player.html CHANGED
@@ -1,28 +1,31 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <title>Ultra Media Player</title>
5
 
6
  <meta charset="UTF-8">
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
9
- <meta http-equiv="X-Frame-Options" content="deny">
10
 
11
- <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
 
 
 
 
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
13
- <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap" rel="stylesheet">
14
 
15
- <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
 
16
 
17
  <style>
18
  :root {
19
- --primary-color: #3a86ff;
20
- --secondary-color: #8338ec;
21
- --dark-color: #0d1117;
22
- --light-color: #f8f9fa;
23
- --success-color: #06d6a0;
24
- --warning-color: #ffbe0b;
25
- --danger-color: #ef476f;
26
  }
27
 
28
  * {
@@ -34,9 +37,8 @@
34
  html, body {
35
  margin: 0;
36
  height: 100%;
37
- font-family: 'Poppins', sans-serif;
38
- background-color: var(--dark-color);
39
- color: var(--light-color);
40
  overflow: hidden;
41
  }
42
 
@@ -45,35 +47,47 @@
45
  height: 100%;
46
  width: 100%;
47
  display: flex;
48
- flex-direction: column;
49
- justify-content: center;
50
  align-items: center;
 
51
  }
52
 
53
- .video-title {
 
 
 
 
 
 
54
  position: absolute;
55
  top: 0;
56
  left: 0;
57
  width: 100%;
58
- padding: 15px 20px;
59
- background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), transparent);
60
- color: white;
61
- z-index: 10;
62
- font-weight: 500;
63
- font-size: 18px;
64
- transition: opacity 0.3s ease;
65
- opacity: 0;
66
  }
67
 
68
- .player-container:hover .video-title {
69
- opacity: 1;
 
 
 
 
 
70
  }
71
 
72
- #stream-media {
73
- height: 100%;
74
- width: 100%;
75
- max-height: 100vh;
76
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
 
 
77
  }
78
 
79
  #error-message {
@@ -81,377 +95,436 @@
81
  top: 50%;
82
  left: 50%;
83
  transform: translate(-50%, -50%);
84
- color: var(--danger-color);
85
  font-size: 18px;
86
  text-align: center;
87
- background-color: rgba(13, 17, 23, 0.9);
88
- padding: 15px 25px;
89
  border-radius: 8px;
90
  max-width: 80%;
91
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
92
- z-index: 100;
93
  display: none;
94
  }
95
 
96
- .custom-controls {
97
- position: absolute;
98
- top: 10px;
99
- right: 10px;
100
- display: flex;
101
- flex-direction: column;
102
- gap: 10px;
103
- z-index: 20;
104
- transition: opacity 0.2s ease;
105
- opacity: 0;
106
  }
107
 
108
- .player-container:hover .custom-controls {
109
- opacity: 1;
 
 
 
 
 
 
 
 
110
  }
111
 
112
- .control-button {
 
 
 
 
 
 
 
 
 
113
  width: 40px;
114
  height: 40px;
115
  border-radius: 50%;
116
- background: rgba(0, 0, 0, 0.6);
117
- backdrop-filter: blur(5px);
118
- color: white;
119
- border: none;
120
  display: flex;
121
  align-items: center;
122
  justify-content: center;
 
123
  cursor: pointer;
124
- transition: all 0.2s ease;
125
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
 
126
  }
127
 
128
- .control-button:hover {
129
- background: var(--primary-color);
130
  transform: scale(1.1);
 
131
  }
132
 
133
- .control-button i {
134
  font-size: 16px;
135
  }
136
 
137
- .plyr--full-ui input[type=range] {
138
- color: var(--primary-color);
 
139
  }
140
 
141
- .plyr__control--overlaid {
142
- background: var(--primary-color);
 
143
  }
144
 
145
- .plyr--video .plyr__control.plyr__tab-focus,
146
- .plyr--video .plyr__control:hover,
147
- .plyr--video .plyr__control[aria-expanded=true] {
148
- background: var(--secondary-color);
149
  }
150
 
151
- .plyr__control.plyr__tab-focus {
152
- box-shadow: 0 0 0 3px rgba(58, 134, 255, 0.35);
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
 
155
- .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::before {
156
- background: var(--primary-color);
 
 
157
  }
158
 
159
- .loading-overlay {
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  position: absolute;
161
- top: 0;
162
- left: 0;
163
- width: 100%;
164
- height: 100%;
165
- background-color: rgba(13, 17, 23, 0.85);
166
- display: flex;
167
- justify-content: center;
168
- align-items: center;
169
- z-index: 30;
 
170
  }
171
 
172
- .spinner {
173
- width: 50px;
174
- height: 50px;
175
- border: 5px solid rgba(255, 255, 255, 0.1);
176
- border-radius: 50%;
177
- border-top-color: var(--primary-color);
178
- animation: spin 1s ease-in-out infinite;
179
  }
180
 
181
- @keyframes spin {
182
- to {
183
- transform: rotate(360deg);
184
- }
185
  }
186
 
187
  @media (max-width: 768px) {
188
- .video-title {
189
- font-size: 16px;
190
- padding: 10px 15px;
191
- }
192
-
193
- .control-button {
194
  width: 36px;
195
  height: 36px;
196
  }
197
- }
198
-
199
- @media (max-width: 480px) {
200
- .video-title {
201
- font-size: 14px;
202
- padding: 8px 12px;
 
 
 
203
  }
204
 
205
- .control-button {
 
 
 
 
 
 
 
 
 
 
 
 
206
  width: 32px;
207
  height: 32px;
208
  }
209
 
210
- .control-button i {
211
  font-size: 14px;
212
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
  </style>
215
  </head>
216
 
217
  <body>
218
  <div class="player-container">
219
- <div class="video-title">Your Media Title</div>
 
 
220
 
221
- <video id="stream-media" controls crossorigin playsinline>
222
  <source src="" type="">
 
223
  <p class="vjs-no-js">
224
  To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
225
  </p>
226
  </video>
 
 
227
 
228
- <div class="custom-controls">
229
- <button class="control-button download-button" title="Download">
230
- <i class="fas fa-download"></i>
231
- </button>
232
- <button class="control-button share-button" title="Share">
233
- <i class="fas fa-share-alt"></i>
234
- </button>
235
- <button class="control-button favorite-button" title="Add to favorites">
236
- <i class="far fa-heart"></i>
237
- </button>
238
  </div>
239
 
240
- <div id="error-message"></div>
241
-
242
- <div class="loading-overlay">
243
- <div class="spinner"></div>
244
  </div>
245
  </div>
246
 
247
  <script>
248
- // Initialize variables
249
- let mediaLink = "";
250
- let mediaTitle = "Your Media Title";
251
- let mediaType = "video/mp4";
252
- const urlParams = new URLSearchParams(window.location.search);
253
-
254
- // Get media link from URL parameter or set default
255
- if (urlParams.has('src')) {
256
- mediaLink = decodeURIComponent(urlParams.get('src'));
257
- }
258
-
259
- // Get optional media title
260
- if (urlParams.has('title')) {
261
- mediaTitle = decodeURIComponent(urlParams.get('title'));
262
- document.querySelector('.video-title').textContent = mediaTitle;
263
- document.title = mediaTitle + " - Ultra Media Player";
264
- }
265
-
266
- // Get optional media type
267
- if (urlParams.has('type')) {
268
- mediaType = decodeURIComponent(urlParams.get('type'));
269
- }
270
-
271
- // Initialize the player with enhanced options
272
- const player = new Plyr('#stream-media', {
273
- controls: [
274
- 'play-large',
275
- 'rewind',
276
- 'play',
277
- 'fast-forward',
278
- 'progress',
279
- 'current-time',
280
- 'duration',
281
- 'mute',
282
- 'volume',
283
- 'captions',
284
- 'settings',
285
- 'pip',
286
- 'airplay',
287
- 'fullscreen'
288
- ],
289
- settings: ['captions', 'quality', 'speed', 'loop'],
290
- speed: {selected: 1, options: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3]},
291
- seekTime: 10,
292
- volume: 1,
293
- muted: false,
294
- keyboard: { focused: true, global: true },
295
- tooltips: { controls: true, seek: true },
296
- resetOnEnd: false,
297
- displayDuration: true,
298
- invertTime: true,
299
- toggleInvert: true,
300
- ratio: '16:9',
301
- autoplay: false,
302
- clickToPlay: true,
303
- });
304
-
305
- // Function to show error message
306
- function showError(message) {
307
- const errorElement = document.getElementById('error-message');
308
- errorElement.textContent = message;
309
- errorElement.style.display = 'block';
310
- document.querySelector('.loading-overlay').style.display = 'none';
311
- }
312
-
313
- // Function to handle media loading
314
- function loadMedia() {
315
  if (mediaLink) {
316
- const sourceElement = document.querySelector('#stream-media source');
317
- sourceElement.setAttribute('src', mediaLink);
318
- sourceElement.setAttribute('type', mediaType);
 
 
 
 
 
 
319
 
320
- player.source = {
321
- type: 'video',
322
- sources: [
323
- {
324
- src: mediaLink,
325
- type: mediaType,
326
- },
327
- ],
 
 
 
 
 
 
328
  };
 
329
 
330
- // Hide loading overlay after the media loads
331
- player.on('ready', function() {
332
- document.querySelector('.loading-overlay').style.display = 'none';
333
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
- // Handle loading error
336
- player.on('error', function() {
337
- showError('Error: Unable to load media. Please check the URL and try again.');
 
 
338
  });
339
 
340
- // Set timeout for loading
341
- setTimeout(function() {
342
- if (player.loading) {
343
- showError('Error: Media loading timeout. Please check your connection and try again.');
 
 
 
 
 
 
 
 
 
 
344
  }
345
- }, 15000);
346
- } else {
347
- showError('Error: Media URL not provided. Please add a "src" parameter to the URL.');
348
- }
349
- }
350
-
351
- // Set up download button functionality
352
- document.querySelector('.download-button').addEventListener('click', function(event) {
353
- event.stopPropagation();
354
- if (mediaLink) {
355
- const link = document.createElement('a');
356
- link.href = mediaLink;
357
- link.download = mediaTitle || "download";
358
- document.body.appendChild(link);
359
- link.click();
360
- document.body.removeChild(link);
361
- } else {
362
- showError('Error: No media available to download');
363
- }
364
- });
365
-
366
- // Set up share button functionality
367
- document.querySelector('.share-button').addEventListener('click', function(event) {
368
- event.stopPropagation();
369
- if (navigator.share && mediaLink) {
370
- navigator.share({
371
- title: mediaTitle,
372
- url: window.location.href
373
- }).catch(err => {
374
- console.error('Share failed:', err);
375
  });
376
- } else {
377
- // Fallback for browsers that don't support Web Share API
378
- const currentUrl = window.location.href;
379
- navigator.clipboard.writeText(currentUrl).then(() => {
380
- alert('Link copied to clipboard!');
381
- }).catch(err => {
382
- console.error('Failed to copy link:', err);
383
  });
384
- }
385
- });
386
-
387
- // Set up favorite button functionality
388
- let isFavorite = false;
389
- document.querySelector('.favorite-button').addEventListener('click', function(event) {
390
- event.stopPropagation();
391
- isFavorite = !isFavorite;
392
-
393
- const icon = this.querySelector('i');
394
- if (isFavorite) {
395
- icon.classList.remove('far');
396
- icon.classList.add('fas');
397
- icon.style.color = '#ef476f';
398
 
399
- // Save to localStorage or your preferred storage method
400
- try {
401
- const favorites = JSON.parse(localStorage.getItem('mediaFavorites')) || [];
402
- favorites.push({
403
- url: mediaLink,
404
- title: mediaTitle,
405
- timestamp: new Date().toISOString()
406
- });
407
- localStorage.setItem('mediaFavorites', JSON.stringify(favorites));
408
- } catch (e) {
409
- console.error('Error saving favorite:', e);
410
- }
411
- } else {
412
- icon.classList.remove('fas');
413
- icon.classList.add('far');
414
- icon.style.color = '';
415
 
416
- // Remove from localStorage
417
- try {
418
- const favorites = JSON.parse(localStorage.getItem('mediaFavorites')) || [];
419
- const updatedFavorites = favorites.filter(fav => fav.url !== mediaLink);
420
- localStorage.setItem('mediaFavorites', JSON.stringify(updatedFavorites));
421
- } catch (e) {
422
- console.error('Error removing favorite:', e);
423
- }
 
 
 
 
 
 
 
 
 
424
  }
425
- });
426
-
427
- // Keyboard shortcuts
428
- document.addEventListener('keydown', function(event) {
429
- // Space already handled by Plyr for play/pause
430
 
431
- // D for download (when not typing in an input)
432
- if (event.key === 'd' && document.activeElement.tagName !== 'INPUT') {
433
- document.querySelector('.download-button').click();
 
 
434
  }
435
 
436
- // S for share (when not typing in an input)
437
- if (event.key === 's' && document.activeElement.tagName !== 'INPUT') {
438
- document.querySelector('.share-button').click();
439
- }
 
 
440
 
441
- // F for favorite (when not typing in an input)
442
- if (event.key === 'f' && document.activeElement.tagName !== 'INPUT') {
443
- document.querySelector('.favorite-button').click();
444
- }
 
 
 
 
 
 
445
  });
446
-
447
- // Check if we're in a mobile context
448
- const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
449
- if (isMobile) {
450
- document.documentElement.classList.add('mobile');
451
- }
452
-
453
- // Start loading the media
454
- loadMedia();
455
  </script>
456
  </body>
457
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <title>Premium Media Player</title>
5
 
6
  <meta charset="UTF-8">
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
9
+ <meta name="description" content="Advanced HTML5 Video Player">
10
 
11
+ <!-- Favicon -->
12
+ <link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.svg">
13
+
14
+ <!-- CSS Libraries -->
15
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.css">
16
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
17
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap">
18
 
19
+ <!-- Scripts -->
20
+ <script src="https://cdn.jsdelivr.net/npm/plyr@3.7.8/dist/plyr.polyfilled.js"></script>
21
 
22
  <style>
23
  :root {
24
+ --primary-color: #3498db;
25
+ --secondary-color: #2ecc71;
26
+ --background-color: #000;
27
+ --text-color: #fff;
28
+ --button-hover: rgba(52, 152, 219, 0.8);
 
 
29
  }
30
 
31
  * {
 
37
  html, body {
38
  margin: 0;
39
  height: 100%;
40
+ font-family: 'Roboto', Arial, sans-serif;
41
+ background-color: var(--background-color);
 
42
  overflow: hidden;
43
  }
44
 
 
47
  height: 100%;
48
  width: 100%;
49
  display: flex;
 
 
50
  align-items: center;
51
+ justify-content: center;
52
  }
53
 
54
+ #stream-media {
55
+ height: 100%;
56
+ width: 100%;
57
+ max-height: 100vh;
58
+ }
59
+
60
+ .loading-overlay {
61
  position: absolute;
62
  top: 0;
63
  left: 0;
64
  width: 100%;
65
+ height: 100%;
66
+ background-color: rgba(0, 0, 0, 0.7);
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ z-index: 100;
71
+ opacity: 1;
72
+ transition: opacity 0.5s ease;
73
  }
74
 
75
+ .loading-spinner {
76
+ width: 50px;
77
+ height: 50px;
78
+ border: 5px solid rgba(255, 255, 255, 0.3);
79
+ border-radius: 50%;
80
+ border-top-color: var(--primary-color);
81
+ animation: spin 1s ease-in-out infinite;
82
  }
83
 
84
+ @keyframes spin {
85
+ to { transform: rotate(360deg); }
86
+ }
87
+
88
+ .hidden {
89
+ opacity: 0;
90
+ pointer-events: none;
91
  }
92
 
93
  #error-message {
 
95
  top: 50%;
96
  left: 50%;
97
  transform: translate(-50%, -50%);
98
+ color: #e74c3c;
99
  font-size: 18px;
100
  text-align: center;
101
+ background-color: rgba(0, 0, 0, 0.8);
102
+ padding: 20px;
103
  border-radius: 8px;
104
  max-width: 80%;
105
+ z-index: 50;
 
106
  display: none;
107
  }
108
 
109
+ .plyr--full-ui input[type=range] {
110
+ color: var(--primary-color);
 
 
 
 
 
 
 
 
111
  }
112
 
113
+ .plyr__control--overlaid {
114
+ background: var(--primary-color);
115
+ }
116
+
117
+ .plyr--video .plyr__control:hover {
118
+ background: var(--primary-color);
119
+ }
120
+
121
+ .plyr--video .plyr__control.plyr__tab-focus {
122
+ background: var(--primary-color);
123
  }
124
 
125
+ .plyr__menu__container .plyr__control[role=menuitemradio][aria-checked=true]::before {
126
+ background: var(--primary-color);
127
+ }
128
+
129
+ .plyr__control.plyr__control--pressed {
130
+ background: var(--secondary-color);
131
+ }
132
+
133
+ .custom-button {
134
+ position: absolute;
135
  width: 40px;
136
  height: 40px;
137
  border-radius: 50%;
138
+ background-color: rgba(0, 0, 0, 0.6);
 
 
 
139
  display: flex;
140
  align-items: center;
141
  justify-content: center;
142
+ color: white;
143
  cursor: pointer;
144
+ z-index: 10;
145
+ transition: all 0.3s ease;
146
+ border: 2px solid rgba(255, 255, 255, 0.2);
147
  }
148
 
149
+ .custom-button:hover {
150
+ background-color: var(--button-hover);
151
  transform: scale(1.1);
152
+ border-color: rgba(255, 255, 255, 0.5);
153
  }
154
 
155
+ .custom-button i {
156
  font-size: 16px;
157
  }
158
 
159
+ .plyr__video-wrapper .download-button {
160
+ top: 15px;
161
+ right: 15px;
162
  }
163
 
164
+ .plyr__video-wrapper .share-button {
165
+ top: 15px;
166
+ right: 65px;
167
  }
168
 
169
+ .plyr__video-wrapper .info-button {
170
+ top: 15px;
171
+ right: 115px;
 
172
  }
173
 
174
+ .video-info-panel {
175
+ position: absolute;
176
+ top: 70px;
177
+ right: 15px;
178
+ background-color: rgba(0, 0, 0, 0.8);
179
+ border-radius: 8px;
180
+ padding: 15px;
181
+ color: white;
182
+ max-width: 300px;
183
+ transform: translateY(-20px);
184
+ opacity: 0;
185
+ transition: all 0.3s ease;
186
+ z-index: 9;
187
+ pointer-events: none;
188
+ border: 1px solid rgba(255, 255, 255, 0.1);
189
  }
190
 
191
+ .video-info-panel.active {
192
+ transform: translateY(0);
193
+ opacity: 1;
194
+ pointer-events: all;
195
  }
196
 
197
+ .video-info-panel h3 {
198
+ margin-bottom: 10px;
199
+ font-size: 16px;
200
+ font-weight: 500;
201
+ color: var(--primary-color);
202
+ }
203
+
204
+ .video-info-panel p {
205
+ margin-bottom: 8px;
206
+ font-size: 14px;
207
+ line-height: 1.4;
208
+ }
209
+
210
+ .keyboard-shortcuts {
211
  position: absolute;
212
+ bottom: 70px;
213
+ left: 20px;
214
+ background-color: rgba(0, 0, 0, 0.7);
215
+ border-radius: 4px;
216
+ padding: 8px 12px;
217
+ color: white;
218
+ font-size: 12px;
219
+ opacity: 0;
220
+ transition: opacity 0.3s ease;
221
+ pointer-events: none;
222
  }
223
 
224
+ .plyr--playing:hover .keyboard-shortcuts {
225
+ opacity: 1;
 
 
 
 
 
226
  }
227
 
228
+ .plyr__volume {
229
+ max-width: 120px;
 
 
230
  }
231
 
232
  @media (max-width: 768px) {
233
+ .custom-button {
 
 
 
 
 
234
  width: 36px;
235
  height: 36px;
236
  }
237
+
238
+ .plyr__video-wrapper .download-button {
239
+ top: 10px;
240
+ right: 10px;
241
+ }
242
+
243
+ .plyr__video-wrapper .share-button {
244
+ top: 10px;
245
+ right: 56px;
246
  }
247
 
248
+ .plyr__video-wrapper .info-button {
249
+ top: 10px;
250
+ right: 102px;
251
+ }
252
+
253
+ .video-info-panel {
254
+ max-width: 250px;
255
+ top: 60px;
256
+ }
257
+ }
258
+
259
+ @media (max-width: 480px) {
260
+ .custom-button {
261
  width: 32px;
262
  height: 32px;
263
  }
264
 
265
+ .custom-button i {
266
  font-size: 14px;
267
  }
268
+
269
+ .plyr__video-wrapper .share-button {
270
+ right: 52px;
271
+ }
272
+
273
+ .plyr__video-wrapper .info-button {
274
+ right: 94px;
275
+ }
276
+
277
+ .video-info-panel {
278
+ max-width: 200px;
279
+ }
280
+ }
281
+
282
+ .plyr {
283
+ --plyr-color-main: var(--primary-color);
284
+ --plyr-video-control-color: #fff;
285
+ --plyr-video-control-color-hover: #fff;
286
+ --plyr-video-control-background-hover: var(--primary-color);
287
+ height: 100%;
288
+ }
289
+
290
+ .plyr__progress__buffer {
291
+ color: rgba(255, 255, 255, 0.3);
292
+ }
293
+
294
+ .plyr--video .plyr__control.plyr__tab-focus,
295
+ .plyr--video .plyr__control:hover,
296
+ .plyr--video .plyr__control[aria-expanded=true] {
297
+ background: var(--primary-color);
298
+ }
299
+
300
+ .plyr--captions-enabled .plyr__captions {
301
+ font-size: 18px;
302
+ background-color: rgba(0, 0, 0, 0.7);
303
+ padding: 8px 16px;
304
+ border-radius: 4px;
305
  }
306
  </style>
307
  </head>
308
 
309
  <body>
310
  <div class="player-container">
311
+ <div class="loading-overlay" id="loading-overlay">
312
+ <div class="loading-spinner"></div>
313
+ </div>
314
 
315
+ <video id="stream-media" crossorigin="anonymous" playsinline>
316
  <source src="" type="">
317
+ <track kind="captions" label="English" src="" srclang="en" default>
318
  <p class="vjs-no-js">
319
  To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
320
  </p>
321
  </video>
322
+
323
+ <div id="error-message"></div>
324
 
325
+ <div class="video-info-panel" id="info-panel">
326
+ <h3>Media Information</h3>
327
+ <p><strong>Title:</strong> <span id="video-title">Video Title</span></p>
328
+ <p><strong>Duration:</strong> <span id="video-duration">00:00</span></p>
329
+ <p><strong>Quality:</strong> <span id="video-quality">HD</span></p>
 
 
 
 
 
330
  </div>
331
 
332
+ <div class="keyboard-shortcuts" id="shortcuts-info">
333
+ Press <strong>Space</strong> to play/pause, <strong>F</strong> for fullscreen
 
 
334
  </div>
335
  </div>
336
 
337
  <script>
338
+ document.addEventListener('DOMContentLoaded', function() {
339
+ // Create player instance with enhanced options
340
+ const player = new Plyr('#stream-media', {
341
+ controls: [
342
+ 'play-large',
343
+ 'restart',
344
+ 'rewind',
345
+ 'play',
346
+ 'fast-forward',
347
+ 'progress',
348
+ 'current-time',
349
+ 'duration',
350
+ 'mute',
351
+ 'volume',
352
+ 'captions',
353
+ 'settings',
354
+ 'pip',
355
+ 'airplay',
356
+ 'fullscreen'
357
+ ],
358
+ settings: ['captions', 'quality', 'speed', 'loop'],
359
+ speed: { selected: 1, options: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3] },
360
+ seekTime: 10,
361
+ volume: 0.8,
362
+ keyboard: { focused: true, global: true },
363
+ tooltips: { controls: true, seek: true },
364
+ captions: { active: false, language: 'en' },
365
+ quality: { default: 1080, options: [4320, 2880, 2160, 1440, 1080, 720, 576, 480, 360, 240] },
366
+ invertTime: true,
367
+ displayDuration: true,
368
+ blankVideo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=',
369
+ ratio: '16:9',
370
+ disableContextMenu: false,
371
+ autoplay: false,
372
+ clickToPlay: true,
373
+ hideControls: true,
374
+ resetOnEnd: false,
375
+ debug: false
376
+ });
377
+
378
+ // Define media source (replace with actual media URL in production)
379
+ const mediaLink = "YOUR_MEDIA_URL_HERE";
380
+ const mediaTitle = "Your Video Title";
381
+ const mediaDuration = "01:30:45";
382
+ const mediaQuality = "1080p";
383
+
384
+ // Initialize loading overlay
385
+ const loadingOverlay = document.getElementById('loading-overlay');
386
+
387
+ // Set up media and process loading
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  if (mediaLink) {
389
+ document.querySelector('#stream-media source').setAttribute('src', mediaLink);
390
+
391
+ // Update video info panel
392
+ document.getElementById('video-title').textContent = mediaTitle;
393
+ document.getElementById('video-duration').textContent = mediaDuration;
394
+ document.getElementById('video-quality').textContent = mediaQuality;
395
+
396
+ // Create and add custom buttons
397
+ const playerWrapper = player.elements.container.querySelector('.plyr__video-wrapper');
398
 
399
+ // Download Button
400
+ const downloadButton = document.createElement('div');
401
+ downloadButton.className = 'custom-button download-button';
402
+ downloadButton.innerHTML = '<i class="fas fa-download"></i>';
403
+ downloadButton.setAttribute('aria-label', 'Download video');
404
+ downloadButton.setAttribute('title', 'Download video');
405
+ downloadButton.onclick = function(event) {
406
+ event.stopPropagation();
407
+ const link = document.createElement('a');
408
+ link.href = mediaLink;
409
+ link.download = mediaTitle || 'video';
410
+ document.body.appendChild(link);
411
+ link.click();
412
+ document.body.removeChild(link);
413
  };
414
+ playerWrapper.appendChild(downloadButton);
415
 
416
+ // Share Button
417
+ const shareButton = document.createElement('div');
418
+ shareButton.className = 'custom-button share-button';
419
+ shareButton.innerHTML = '<i class="fas fa-share-alt"></i>';
420
+ shareButton.setAttribute('aria-label', 'Share video');
421
+ shareButton.setAttribute('title', 'Share video');
422
+ shareButton.onclick = function(event) {
423
+ event.stopPropagation();
424
+ if (navigator.share) {
425
+ navigator.share({
426
+ title: mediaTitle || 'Video',
427
+ url: window.location.href
428
+ }).catch(err => {
429
+ console.error('Share failed:', err);
430
+ });
431
+ } else {
432
+ // Fallback for browsers that don't support navigator.share
433
+ prompt('Copy this link to share:', window.location.href);
434
+ }
435
+ };
436
+ playerWrapper.appendChild(shareButton);
437
+
438
+ // Info Button
439
+ const infoButton = document.createElement('div');
440
+ infoButton.className = 'custom-button info-button';
441
+ infoButton.innerHTML = '<i class="fas fa-info"></i>';
442
+ infoButton.setAttribute('aria-label', 'Video information');
443
+ infoButton.setAttribute('title', 'Video information');
444
+
445
+ const infoPanel = document.getElementById('info-panel');
446
+ infoButton.onclick = function(event) {
447
+ event.stopPropagation();
448
+ infoPanel.classList.toggle('active');
449
+ };
450
+ playerWrapper.appendChild(infoButton);
451
 
452
+ // Hide info panel when clicking elsewhere
453
+ document.addEventListener('click', function(event) {
454
+ if (!infoButton.contains(event.target) && !infoPanel.contains(event.target)) {
455
+ infoPanel.classList.remove('active');
456
+ }
457
  });
458
 
459
+ // Handle player events
460
+ player.on('ready', event => {
461
+ console.log('Player is ready');
462
+ loadingOverlay.classList.add('hidden');
463
+
464
+ // Get actual duration if available
465
+ if (player.duration !== Infinity && !isNaN(player.duration)) {
466
+ const formatTime = seconds => {
467
+ const h = Math.floor(seconds / 3600);
468
+ const m = Math.floor((seconds % 3600) / 60);
469
+ const s = Math.floor(seconds % 60);
470
+ return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
471
+ };
472
+ document.getElementById('video-duration').textContent = formatTime(player.duration);
473
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  });
475
+
476
+ player.on('play', () => {
477
+ console.log('Video started playing');
 
 
 
 
478
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
 
480
+ player.on('pause', () => {
481
+ console.log('Video paused');
482
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
483
 
484
+ player.on('ended', () => {
485
+ console.log('Video ended');
486
+ });
487
+
488
+ player.on('error', error => {
489
+ console.error('Player error:', error);
490
+ loadingOverlay.classList.add('hidden');
491
+ showError('Error loading video. Please try again later.');
492
+ });
493
+
494
+ // Initialize the player
495
+ player.restart();
496
+
497
+ } else {
498
+ // Handle missing media
499
+ loadingOverlay.classList.add('hidden');
500
+ showError('Error: Media URL not provided');
501
  }
 
 
 
 
 
502
 
503
+ // Error display function
504
+ function showError(message) {
505
+ const errorElement = document.getElementById('error-message');
506
+ errorElement.textContent = message;
507
+ errorElement.style.display = 'block';
508
  }
509
 
510
+ // Add keyboard shortcut info on ctrl press
511
+ document.addEventListener('keydown', function(e) {
512
+ if (e.key === 'Control') {
513
+ document.getElementById('shortcuts-info').style.opacity = '1';
514
+ }
515
+ });
516
 
517
+ document.addEventListener('keyup', function(e) {
518
+ if (e.key === 'Control') {
519
+ document.getElementById('shortcuts-info').style.opacity = '0';
520
+ }
521
+ });
522
+
523
+ // Handle window resize events for responsive behavior
524
+ window.addEventListener('resize', function() {
525
+ player.ratio = '16:9';
526
+ });
527
  });
 
 
 
 
 
 
 
 
 
528
  </script>
529
  </body>
530
  </html>