jerrrycans commited on
Commit
bf7a056
·
verified ·
1 Parent(s): 5bc24ba

Update bot/server/templates/player.html

Browse files
Files changed (1) hide show
  1. bot/server/templates/player.html +317 -381
bot/server/templates/player.html CHANGED
@@ -5,53 +5,75 @@
5
 
6
  <meta charset="UTF-8">
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
8
  <meta http-equiv="X-Frame-Options" content="deny">
9
 
10
  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
11
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
12
- <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600&display=swap" rel="stylesheet">
13
 
14
  <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
15
 
16
  <style>
17
  :root {
18
- --main-color: #3498db;
19
- --secondary-color: #2ecc71;
20
- --text-color: #ecf0f1;
21
- --dark-bg: #1a1a1a;
22
- --hover-color: #e74c3c;
23
- --gradient-start: #3498db;
24
- --gradient-end: #8e44ad;
25
  }
26
 
27
  * {
28
- box-sizing: border-box;
29
  margin: 0;
30
  padding: 0;
 
31
  }
32
 
33
  html, body {
34
  margin: 0;
35
  height: 100%;
36
- font-family: 'Montserrat', sans-serif;
37
- background-color: var(--dark-bg);
38
- color: var(--text-color);
39
  overflow: hidden;
40
  }
41
 
42
  .player-container {
 
43
  height: 100%;
44
  width: 100%;
45
- position: relative;
46
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
47
- overflow: hidden;
48
- border-radius: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
  #stream-media {
52
  height: 100%;
53
  width: 100%;
54
- background-color: #000;
 
55
  }
56
 
57
  #error-message {
@@ -59,228 +81,134 @@
59
  top: 50%;
60
  left: 50%;
61
  transform: translate(-50%, -50%);
62
- color: var(--hover-color);
63
- font-size: 1.5rem;
64
  text-align: center;
65
- background-color: rgba(0, 0, 0, 0.8);
66
- padding: 20px;
67
- border-radius: 10px;
68
- box-shadow: 0 0 20px rgba(231, 76, 60, 0.5);
69
- z-index: 100;
70
  max-width: 80%;
 
 
 
71
  }
72
 
73
- .custom-control {
74
  position: absolute;
75
- width: 50px;
76
- height: 50px;
77
- border-radius: 50%;
78
  display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- color: var(--text-color);
82
- z-index: 10;
83
- cursor: pointer;
84
- transition: all 0.3s ease;
85
- background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
86
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
87
- opacity: 0.9;
88
  }
89
 
90
- .custom-control:hover {
91
- transform: scale(1.1);
92
  opacity: 1;
93
  }
94
 
95
- .plyr-download-button {
96
- top: 20px;
97
- right: 20px;
98
- }
99
-
100
- .plyr-share-button {
101
- top: 20px;
102
- right: 90px;
103
- }
104
-
105
- .plyr-info-button {
106
- top: 20px;
107
- right: 160px;
 
108
  }
109
 
110
- .control-tooltip {
111
- position: absolute;
112
- background-color: rgba(0, 0, 0, 0.8);
113
- color: white;
114
- padding: 5px 10px;
115
- border-radius: 4px;
116
- font-size: 12px;
117
- bottom: -35px;
118
- left: 50%;
119
- transform: translateX(-50%);
120
- white-space: nowrap;
121
- opacity: 0;
122
- transition: opacity 0.2s ease;
123
- pointer-events: none;
124
  }
125
 
126
- .custom-control:hover .control-tooltip {
127
- opacity: 1;
128
  }
129
 
130
  .plyr--full-ui input[type=range] {
131
- color: var(--main-color);
132
  }
133
 
134
  .plyr__control--overlaid {
135
- background: linear-gradient(135deg, var(--gradient-start), var(--gradient-end));
136
- }
137
-
138
- .plyr--video .plyr__control:hover {
139
- background: var(--secondary-color);
140
  }
141
 
142
  .plyr--video .plyr__control.plyr__tab-focus,
143
- .plyr__control--overlaid:focus,
144
- .plyr__control--overlaid:hover {
145
- background: var(--hover-color);
146
  }
147
 
148
  .plyr__control.plyr__tab-focus {
149
- box-shadow: 0 0 0 5px rgba(52, 152, 219, 0.5);
150
- }
151
-
152
- .plyr__menu__container .plyr__control.plyr__tab-focus,
153
- .plyr__menu__container .plyr__control:hover,
154
- .plyr__menu__container .plyr__control[aria-expanded=true] {
155
- background: var(--main-color);
156
- }
157
-
158
- .plyr--audio .plyr__control.plyr__tab-focus,
159
- .plyr--audio .plyr__control:hover,
160
- .plyr--audio .plyr__control[aria-expanded=true] {
161
- background: var(--main-color);
162
  }
163
 
164
- .plyr__control--overlaid {
165
- padding: 25px;
166
- }
167
-
168
- .plyr--video .plyr__controls {
169
- background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.7));
170
- }
171
-
172
- .plyr--full-ui.plyr--video .plyr__control--overlaid {
173
- opacity: 0.9;
174
  }
175
 
176
- /* Loading animation */
177
  .loading-overlay {
178
  position: absolute;
179
  top: 0;
180
  left: 0;
181
- right: 0;
182
- bottom: 0;
183
- background: rgba(0, 0, 0, 0.8);
184
  display: flex;
185
  justify-content: center;
186
  align-items: center;
187
- z-index: 9;
188
  }
189
 
190
- .loader {
191
  width: 50px;
192
  height: 50px;
193
- border: 5px solid rgba(255, 255, 255, 0.3);
194
  border-radius: 50%;
195
- border-top-color: var(--main-color);
196
  animation: spin 1s ease-in-out infinite;
197
  }
198
 
199
  @keyframes spin {
200
- to { transform: rotate(360deg); }
201
- }
202
-
203
- /* Media info panel */
204
- .media-info-panel {
205
- position: absolute;
206
- top: 0;
207
- right: -350px;
208
- width: 350px;
209
- height: 100%;
210
- background-color: rgba(0, 0, 0, 0.85);
211
- transition: right 0.3s ease;
212
- z-index: 20;
213
- overflow-y: auto;
214
- padding: 20px;
215
- backdrop-filter: blur(10px);
216
- }
217
-
218
- .media-info-panel.active {
219
- right: 0;
220
- }
221
-
222
- .info-close {
223
- position: absolute;
224
- top: 15px;
225
- right: 15px;
226
- font-size: 20px;
227
- cursor: pointer;
228
- color: var(--text-color);
229
- }
230
-
231
- .info-heading {
232
- color: var(--main-color);
233
- margin-bottom: 20px;
234
- font-size: 1.5rem;
235
- border-bottom: 2px solid var(--main-color);
236
- padding-bottom: 10px;
237
- }
238
-
239
- .info-group {
240
- margin-bottom: 15px;
241
- }
242
-
243
- .info-label {
244
- color: var(--secondary-color);
245
- font-size: 0.9rem;
246
- margin-bottom: 5px;
247
- font-weight: 600;
248
- }
249
-
250
- .info-value {
251
- font-size: 0.85rem;
252
- line-height: 1.4;
253
- word-break: break-word;
254
- }
255
-
256
- .plyr, .plyr__video-wrapper, .plyr__video-embed iframe {
257
- height: 100%;
258
  }
259
 
260
- /* Mobile responsiveness */
261
  @media (max-width: 768px) {
262
- .custom-control {
263
- width: 40px;
264
- height: 40px;
265
  }
266
-
267
- .plyr-download-button {
268
- right: 15px;
269
- top: 15px;
270
  }
271
-
272
- .plyr-share-button {
273
- right: 70px;
274
- top: 15px;
 
 
275
  }
276
-
277
- .plyr-info-button {
278
- right: 125px;
279
- top: 15px;
280
  }
281
-
282
- .media-info-panel {
283
- width: 280px;
284
  }
285
  }
286
  </style>
@@ -288,234 +216,242 @@
288
 
289
  <body>
290
  <div class="player-container">
291
- <video id="stream-media" controls preload="auto">
 
 
292
  <source src="" type="">
293
  <p class="vjs-no-js">
294
  To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
295
  </p>
296
  </video>
297
-
298
- <div id="loading-overlay" class="loading-overlay">
299
- <div class="loader"></div>
300
- </div>
301
-
302
- <div id="media-info-panel" class="media-info-panel">
303
- <div class="info-close" id="info-close"><i class="fas fa-times"></i></div>
304
- <h3 class="info-heading">Media Information</h3>
305
- <div class="info-group">
306
- <div class="info-label">File Name</div>
307
- <div class="info-value" id="info-filename">Loading...</div>
308
- </div>
309
- <div class="info-group">
310
- <div class="info-label">Resolution</div>
311
- <div class="info-value" id="info-resolution">Loading...</div>
312
- </div>
313
- <div class="info-group">
314
- <div class="info-label">Duration</div>
315
- <div class="info-value" id="info-duration">Loading...</div>
316
- </div>
317
- <div class="info-group">
318
- <div class="info-label">Media URL</div>
319
- <div class="info-value" id="info-url">Loading...</div>
320
- </div>
321
- <div class="info-group">
322
- <div class="info-label">Playback Speed</div>
323
- <div class="info-value" id="info-speed">1x</div>
324
- </div>
325
  </div>
326
-
327
  <div id="error-message"></div>
 
 
 
 
328
  </div>
329
 
330
  <script>
331
- document.addEventListener('DOMContentLoaded', function() {
332
- const loadingOverlay = document.getElementById('loading-overlay');
333
-
334
- // Initialize player with more options
335
- const player = new Plyr('#stream-media', {
336
- controls: [
337
- 'play-large',
338
- 'rewind',
339
- 'play',
340
- 'fast-forward',
341
- 'progress',
342
- 'current-time',
343
- 'duration',
344
- 'mute',
345
- 'volume',
346
- 'captions',
347
- 'settings',
348
- 'pip',
349
- 'airplay',
350
- 'fullscreen'
351
- ],
352
- settings: ['captions', 'quality', 'speed', 'loop'],
353
- speed: { selected: 1, options: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 3] },
354
- seekTime: 10,
355
- keyboard: { focused: true, global: true },
356
- tooltips: { controls: true, seek: true },
357
- disableContextMenu: false,
358
- invertTime: false,
359
- toggleInvert: true,
360
- storage: { enabled: true, key: 'ultra-player-settings' }
361
- });
362
-
363
- // Extract the media link
364
- let mediaLink = "";
365
- try {
366
- // In a real implementation, this would be replaced by server-side templating
367
- const urlParams = new URLSearchParams(window.location.search);
368
- mediaLink = urlParams.get('mediaLink') || "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4";
369
- } catch (e) {
370
- mediaLink = "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4";
371
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
 
373
- // Setup the media source
 
374
  if (mediaLink) {
375
- document.querySelector('#stream-media source').setAttribute('src', mediaLink);
 
 
376
 
377
- // Create custom download button
378
- const downloadButton = document.createElement('div');
379
- downloadButton.className = 'custom-control plyr-download-button';
380
- downloadButton.innerHTML = '<i class="fas fa-download"></i><span class="control-tooltip">Download</span>';
381
- downloadButton.onclick = function(event) {
382
- event.stopPropagation();
383
- const link = document.createElement('a');
384
- link.href = mediaLink;
385
- link.download = getFileName(mediaLink);
386
- document.body.appendChild(link);
387
- link.click();
388
- document.body.removeChild(link);
389
- };
390
-
391
- // Create custom share button
392
- const shareButton = document.createElement('div');
393
- shareButton.className = 'custom-control plyr-share-button';
394
- shareButton.innerHTML = '<i class="fas fa-share-alt"></i><span class="control-tooltip">Share</span>';
395
- shareButton.onclick = function(event) {
396
- event.stopPropagation();
397
- if (navigator.share) {
398
- navigator.share({
399
- title: "Ultra Media Player",
400
- text: "Check out this media!",
401
- url: mediaLink
402
- });
403
- } else {
404
- // Fallback for browsers that don't support Web Share API
405
- prompt("Copy this link to share:", window.location.href);
406
- }
407
- };
408
-
409
- // Create info button
410
- const infoButton = document.createElement('div');
411
- infoButton.className = 'custom-control plyr-info-button';
412
- infoButton.innerHTML = '<i class="fas fa-info"></i><span class="control-tooltip">Info</span>';
413
- infoButton.onclick = function(event) {
414
- event.stopPropagation();
415
- document.getElementById('media-info-panel').classList.add('active');
416
- updateMediaInfo();
417
- };
418
-
419
- // Append custom controls
420
- const playerContainer = player.elements.container.querySelector('.plyr__video-wrapper');
421
- playerContainer.appendChild(downloadButton);
422
- playerContainer.appendChild(shareButton);
423
- playerContainer.appendChild(infoButton);
424
-
425
- // Close info panel
426
- document.getElementById('info-close').addEventListener('click', function() {
427
- document.getElementById('media-info-panel').classList.remove('active');
428
- });
429
-
430
- // Update player
431
  player.source = {
432
  type: 'video',
433
- sources: [{
434
- src: mediaLink,
435
- type: getMediaType(mediaLink)
436
- }]
 
 
437
  };
438
-
439
- // Handle player events
440
  player.on('ready', function() {
441
- loadingOverlay.style.display = 'none';
442
- updateMediaInfo();
443
- });
444
-
445
- player.on('error', function(error) {
446
- loadingOverlay.style.display = 'none';
447
- document.getElementById('error-message').textContent = 'Error loading media: ' + error.detail.message;
448
  });
449
-
450
- player.on('ratechange', function() {
451
- document.getElementById('info-speed').textContent = player.speed + 'x';
 
452
  });
453
-
 
 
 
 
 
 
454
  } else {
455
- loadingOverlay.style.display = 'none';
456
- document.getElementById('error-message').textContent = 'Error: Media URL not provided';
457
  }
 
458
 
459
- // Helper functions
460
- function getFileName(url) {
461
- const segments = url.split('/');
462
- return segments[segments.length - 1].split('?')[0];
 
 
 
 
 
 
 
 
463
  }
 
464
 
465
- function getMediaType(url) {
466
- const extension = url.split('.').pop().toLowerCase();
467
- const videoTypes = {
468
- 'mp4': 'video/mp4',
469
- 'webm': 'video/webm',
470
- 'mov': 'video/quicktime',
471
- 'avi': 'video/x-msvideo',
472
- 'mkv': 'video/x-matroska'
473
- };
474
-
475
- const audioTypes = {
476
- 'mp3': 'audio/mpeg',
477
- 'wav': 'audio/wav',
478
- 'ogg': 'audio/ogg',
479
- 'm4a': 'audio/mp4',
480
- 'flac': 'audio/flac'
481
- };
482
-
483
- return videoTypes[extension] || audioTypes[extension] || '';
484
  }
 
485
 
486
- function formatTime(seconds) {
487
- if (isNaN(seconds)) return '00:00';
 
 
 
 
 
 
 
 
 
488
 
489
- const hours = Math.floor(seconds / 3600);
490
- const minutes = Math.floor((seconds % 3600) / 60);
491
- const secs = Math.floor(seconds % 60);
 
 
 
 
 
 
 
 
 
 
 
 
 
492
 
493
- if (hours > 0) {
494
- return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
495
- } else {
496
- return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
 
 
 
497
  }
498
  }
 
499
 
500
- function updateMediaInfo() {
501
- document.getElementById('info-filename').textContent = getFileName(mediaLink);
502
- document.getElementById('info-url').textContent = mediaLink;
503
-
504
- // Get video dimensions when available
505
- if (player.media && player.media.videoWidth && player.media.videoHeight) {
506
- document.getElementById('info-resolution').textContent = `${player.media.videoWidth}x${player.media.videoHeight}`;
507
- } else {
508
- document.getElementById('info-resolution').textContent = 'Unknown';
509
- }
510
-
511
- // Get duration when available
512
- if (player.duration) {
513
- document.getElementById('info-duration').textContent = formatTime(player.duration);
514
- } else {
515
- document.getElementById('info-duration').textContent = 'Unknown';
516
- }
517
  }
518
  });
 
 
 
 
 
 
 
 
 
519
  </script>
520
  </body>
521
  </html>
 
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
  * {
 
29
  margin: 0;
30
  padding: 0;
31
+ box-sizing: border-box;
32
  }
33
 
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
 
43
  .player-container {
44
+ position: relative;
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
  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>
 
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>