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

Update bot/server/templates/player.html

Browse files
Files changed (1) hide show
  1. bot/server/templates/player.html +341 -324
bot/server/templates/player.html CHANGED
@@ -1,502 +1,519 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <title>NetStream</title>
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.0/css/all.min.css">
12
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&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
- --primary-color: #e50914;
19
- --secondary-color: #141414;
20
- --text-color: #ffffff;
21
- --hover-color: #ff3d47;
22
- --overlay-color: rgba(20, 20, 20, 0.8);
23
- --progress-color: #e50914;
 
24
  }
25
 
26
  * {
 
27
  margin: 0;
28
  padding: 0;
29
- box-sizing: border-box;
30
- font-family: 'Roboto', sans-serif;
31
  }
32
 
33
  html, body {
34
  margin: 0;
35
  height: 100%;
36
- background-color: #000;
 
 
37
  overflow: hidden;
38
  }
39
 
40
- #player-container {
41
  height: 100%;
42
  width: 100%;
43
  position: relative;
44
- background-color: var(--secondary-color);
 
 
45
  }
46
 
47
  #stream-media {
48
  height: 100%;
49
  width: 100%;
 
50
  }
51
 
52
  #error-message {
53
- color: var(--primary-color);
54
- font-size: 1.5rem;
55
- text-align: center;
56
  position: absolute;
57
  top: 50%;
58
  left: 50%;
59
  transform: translate(-50%, -50%);
60
- background-color: var(--overlay-color);
61
- padding: 1.5rem;
62
- border-radius: 8px;
63
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
64
- width: 80%;
65
- max-width: 500px;
66
- }
67
-
68
- /* Custom player styling */
69
- .plyr {
70
- --plyr-color-main: var(--primary-color);
71
- --plyr-audio-control-color: var(--text-color);
72
- --plyr-audio-control-color-hover: var(--hover-color);
73
- --plyr-audio-control-background-hover: var(--overlay-color);
74
- --plyr-video-control-color: var(--text-color);
75
- --plyr-video-control-color-hover: var(--hover-color);
76
- --plyr-video-control-background-hover: var(--overlay-color);
77
- --plyr-menu-background: var(--overlay-color);
78
- --plyr-menu-color: var(--text-color);
79
- --plyr-menu-arrow-color: var(--primary-color);
80
- --plyr-menu-border-color: rgba(255, 255, 255, 0.15);
81
- --plyr-menu-radius: 6px;
82
- --plyr-video-controls-background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.7));
83
- --plyr-video-control-spacing: 15px;
84
- --plyr-range-thumb-background: var(--primary-color);
85
- --plyr-range-fill-background: var(--progress-color);
86
  }
87
 
88
- .plyr--full-ui input[type=range] {
89
- color: var(--primary-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
 
92
- .plyr__control--overlaid {
93
- background: rgba(229, 9, 20, 0.8);
94
- transition: background 0.3s ease;
95
  }
96
 
97
- .plyr__control--overlaid:hover {
98
- background: rgba(229, 9, 20, 1);
99
- transform: scale(1.1);
100
  }
101
 
102
- .plyr__controls {
103
- padding: 20px !important;
 
104
  }
105
 
106
- .plyr__volume {
107
- max-width: initial;
108
- min-width: initial;
109
- width: auto;
110
- position: relative;
111
  }
112
 
113
- .custom-control-button {
114
  position: absolute;
115
- width: 40px;
116
- height: 40px;
117
- background-color: rgba(0, 0, 0, 0.7);
118
- border-radius: 50%;
119
- display: flex;
120
- align-items: center;
121
- justify-content: center;
122
  color: white;
123
- z-index: 10;
124
- cursor: pointer;
125
- transition: all 0.3s ease;
 
 
 
 
126
  opacity: 0;
127
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
 
128
  }
129
 
130
- .plyr__video-wrapper:hover .custom-control-button {
131
  opacity: 1;
132
  }
133
 
134
- .custom-control-button:hover {
135
- background-color: var(--primary-color);
136
- transform: scale(1.1);
137
  }
138
 
139
- .plyr-download-button {
140
- top: 15px;
141
- right: 15px;
142
  }
143
 
144
- .plyr-share-button {
145
- top: 15px;
146
- right: 65px;
147
  }
148
 
149
- .plyr-info-button {
150
- top: 15px;
151
- right: 115px;
 
152
  }
153
 
154
- .loading-animation {
155
- position: absolute;
156
- top: 50%;
157
- left: 50%;
158
- transform: translate(-50%, -50%);
159
- width: 60px;
160
- height: 60px;
161
  }
162
 
163
- .loading-animation:before {
164
- content: '';
165
- box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  position: absolute;
167
  top: 0;
168
  left: 0;
169
- width: 60px;
170
- height: 60px;
171
- border-radius: 50%;
172
- border: 3px solid transparent;
173
- border-top-color: var(--primary-color);
174
- border-bottom-color: var(--primary-color);
175
- animation: spinner 1.2s linear infinite;
176
  }
177
 
178
- .loading-animation:after {
179
- content: '';
180
- box-sizing: border-box;
181
- position: absolute;
182
- top: 10px;
183
- left: 10px;
184
- width: 40px;
185
- height: 40px;
186
  border-radius: 50%;
187
- border: 3px solid transparent;
188
- border-left-color: var(--text-color);
189
- border-right-color: var(--text-color);
190
- animation: spinner 0.8s linear infinite reverse;
191
  }
192
 
193
- @keyframes spinner {
194
- 0% {
195
- transform: rotate(0deg);
196
- }
197
- 100% {
198
- transform: rotate(360deg);
199
- }
200
  }
201
 
202
- .info-panel {
203
- display: none;
204
  position: absolute;
205
- top: 50%;
206
- left: 50%;
207
- transform: translate(-50%, -50%);
208
- background-color: var(--overlay-color);
209
- color: var(--text-color);
210
- padding: 20px;
211
- border-radius: 8px;
212
  z-index: 20;
213
- width: 80%;
214
- max-width: 500px;
215
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
216
- }
217
-
218
- .info-panel h2 {
219
- color: var(--primary-color);
220
- margin-bottom: 15px;
221
- font-size: 1.5rem;
222
  }
223
 
224
- .info-panel p {
225
- margin-bottom: 10px;
226
- font-size: 1rem;
227
- line-height: 1.4;
228
  }
229
 
230
- .info-panel-close {
231
  position: absolute;
232
- top: 10px;
233
- right: 10px;
 
234
  cursor: pointer;
235
- font-size: 1.2rem;
236
  color: var(--text-color);
237
  }
238
 
239
- .brand-logo {
240
- position: absolute;
241
- top: 15px;
242
- left: 15px;
243
- color: var(--primary-color);
244
- font-size: 1.8rem;
245
- font-weight: bold;
246
- z-index: 10;
247
- opacity: 0.8;
248
- transition: opacity 0.3s ease;
249
  }
250
 
251
- .plyr__video-wrapper:hover .brand-logo {
252
- opacity: 0.4;
253
  }
254
 
255
- /* Touch device optimizations */
256
- @media (hover: none) {
257
- .custom-control-button {
258
- opacity: 1;
259
- background-color: rgba(0, 0, 0, 0.5);
260
- }
 
 
 
 
 
261
  }
262
 
263
- /* Mobile optimizations */
 
 
 
 
264
  @media (max-width: 768px) {
265
- .plyr__controls {
266
- padding: 12px !important;
 
267
  }
268
-
269
- .custom-control-button {
270
- width: 36px;
271
- height: 36px;
272
- }
273
-
274
  .plyr-download-button {
275
- right: 10px;
276
- top: 10px;
277
  }
278
-
279
  .plyr-share-button {
280
- right: 54px;
281
- top: 10px;
282
  }
283
-
284
  .plyr-info-button {
285
- right: 98px;
286
- top: 10px;
 
 
 
 
287
  }
288
- }
289
-
290
- /* Smooth buffering animation */
291
- .plyr__progress__buffer {
292
- background: rgba(255, 255, 255, 0.2);
293
  }
294
  </style>
295
  </head>
296
 
297
  <body>
298
- <div id="player-container">
299
- <div class="loading-animation"></div>
300
- <div class="brand-logo">NS</div>
301
-
302
- <video id="stream-media" playsinline>
303
  <source src="" type="">
304
- <p>Your browser doesn't support HTML5 video. Please update to a modern browser.</p>
 
 
305
  </video>
306
-
307
- <div id="error-message"></div>
308
-
309
- <div class="info-panel" id="info-panel">
310
- <div class="info-panel-close" id="info-panel-close"><i class="fas fa-times"></i></div>
311
- <h2>About This Media</h2>
312
- <p>You're watching premium content streamed through NetStream.</p>
313
- <p>This player supports high-quality streaming, adjustable playback speeds, and picture-in-picture mode.</p>
314
- <p>Use the control buttons for download and sharing options.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  </div>
 
 
316
  </div>
317
 
318
  <script>
319
  document.addEventListener('DOMContentLoaded', function() {
320
- // Initialize loading state
321
- const loadingAnimation = document.querySelector('.loading-animation');
322
- const playerContainer = document.getElementById('player-container');
323
- const errorMessage = document.getElementById('error-message');
324
- const infoPanel = document.getElementById('info-panel');
325
 
326
- // Initialize the player with advanced settings
327
  const player = new Plyr('#stream-media', {
328
  controls: [
329
- 'play-large',
330
- 'rewind',
331
- 'play',
332
- 'fast-forward',
333
- 'progress',
334
- 'current-time',
335
  'duration',
336
- 'mute',
337
  'volume',
338
  'captions',
339
- 'settings',
340
- 'pip',
341
  'airplay',
342
  'fullscreen'
343
  ],
344
  settings: ['captions', 'quality', 'speed', 'loop'],
345
- speed: {selected: 1, options: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3]},
346
  seekTime: 10,
347
  keyboard: { focused: true, global: true },
348
  tooltips: { controls: true, seek: true },
349
- captions: { active: true, update: true },
350
- previewThumbnails: { enabled: false },
351
- fullscreen: { enabled: true, fallback: true, iosNative: true },
352
- storage: { enabled: true, key: 'netstream-player' }
353
  });
354
 
355
- // Get media link and set up the player
356
- const mediaLink = "{{ mediaLink }}";
357
- const mediaTitle = mediaLink.split('/').pop() || "Media"; // Extract filename for title
358
- document.title = `NetStream - ${mediaTitle}`;
 
 
 
 
 
359
 
 
360
  if (mediaLink) {
361
  document.querySelector('#stream-media source').setAttribute('src', mediaLink);
362
 
363
- // Handle video loading
364
- player.on('ready', function() {
365
- loadingAnimation.style.display = 'none';
366
-
367
- // Create custom control buttons
368
- createCustomControls();
369
- });
370
-
371
- player.on('error', function() {
372
- loadingAnimation.style.display = 'none';
373
- errorMessage.textContent = 'Error loading media. Please try again later.';
374
- });
375
-
376
- // Load and play the media
377
- player.source = {
378
- type: 'video',
379
- sources: [{
380
- src: mediaLink,
381
- type: detectMimeType(mediaLink)
382
- }]
383
- };
384
- } else {
385
- loadingAnimation.style.display = 'none';
386
- errorMessage.textContent = 'Error: Media URL not provided';
387
- }
388
-
389
- // Create custom control buttons
390
- function createCustomControls() {
391
- const videoWrapper = player.elements.container.querySelector('.plyr__video-wrapper');
392
-
393
- // Download button
394
  const downloadButton = document.createElement('div');
395
- downloadButton.className = 'custom-control-button plyr-download-button';
396
- downloadButton.innerHTML = '<i class="fas fa-download"></i>';
397
- downloadButton.title = 'Download';
398
  downloadButton.onclick = function(event) {
399
  event.stopPropagation();
400
  const link = document.createElement('a');
401
  link.href = mediaLink;
402
- link.download = mediaTitle;
403
  document.body.appendChild(link);
404
  link.click();
405
  document.body.removeChild(link);
406
  };
407
-
408
- // Share button
409
  const shareButton = document.createElement('div');
410
- shareButton.className = 'custom-control-button plyr-share-button';
411
- shareButton.innerHTML = '<i class="fas fa-share-alt"></i>';
412
- shareButton.title = 'Share';
413
  shareButton.onclick = function(event) {
414
  event.stopPropagation();
415
  if (navigator.share) {
416
  navigator.share({
417
- title: "NetStream - " + mediaTitle,
418
- url: window.location.href
419
- }).catch(err => console.error('Error sharing:', err));
 
420
  } else {
421
  // Fallback for browsers that don't support Web Share API
422
  prompt("Copy this link to share:", window.location.href);
423
  }
424
  };
425
-
426
- // Info button
427
  const infoButton = document.createElement('div');
428
- infoButton.className = 'custom-control-button plyr-info-button';
429
- infoButton.innerHTML = '<i class="fas fa-info"></i>';
430
- infoButton.title = 'Info';
431
  infoButton.onclick = function(event) {
432
  event.stopPropagation();
433
- infoPanel.style.display = 'block';
434
- player.pause();
435
  };
436
-
437
- videoWrapper.appendChild(downloadButton);
438
- videoWrapper.appendChild(shareButton);
439
- videoWrapper.appendChild(infoButton);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  }
441
-
442
- // Close info panel
443
- document.getElementById('info-panel-close').addEventListener('click', function() {
444
- infoPanel.style.display = 'none';
445
- });
446
-
447
- // Detect MIME type from URL
448
- function detectMimeType(url) {
449
  const extension = url.split('.').pop().toLowerCase();
450
- const mimeTypes = {
451
  'mp4': 'video/mp4',
452
  'webm': 'video/webm',
453
- 'ogg': 'video/ogg',
454
  'mov': 'video/quicktime',
455
- 'mkv': 'video/x-matroska',
456
  'avi': 'video/x-msvideo',
457
- 'm3u8': 'application/x-mpegURL',
458
- 'mpd': 'application/dash+xml',
 
 
459
  'mp3': 'audio/mpeg',
460
  'wav': 'audio/wav',
 
 
461
  'flac': 'audio/flac'
462
  };
463
 
464
- return mimeTypes[extension] || 'video/mp4';
465
  }
466
-
467
- // Handle keyboard shortcuts for fast seeking
468
- document.addEventListener('keydown', function(e) {
469
- if (e.code === 'ArrowRight' && e.shiftKey) {
470
- player.forward(30); // Seek forward 30 seconds with Shift+Right
471
- } else if (e.code === 'ArrowLeft' && e.shiftKey) {
472
- player.rewind(30); // Seek backward 30 seconds with Shift+Left
 
 
 
 
 
473
  }
474
- });
475
-
476
- // Auto-hide controls after inactivity
477
- let mouseTimer;
478
- playerContainer.addEventListener('mousemove', function() {
479
- clearTimeout(mouseTimer);
480
- player.elements.container.classList.remove('plyr--hide-controls');
481
 
482
- mouseTimer = setTimeout(function() {
483
- if (player.playing) {
484
- player.elements.container.classList.add('plyr--hide-controls');
485
- }
486
- }, 3000);
487
- });
488
-
489
- // Quality switching based on network conditions
490
- if ('connection' in navigator) {
491
- navigator.connection.addEventListener('change', function() {
492
- if (navigator.connection.downlink < 1.5) {
493
- player.quality = 'low';
494
- } else if (navigator.connection.downlink < 5) {
495
- player.quality = 'medium';
496
- } else {
497
- player.quality = 'high';
498
- }
499
- });
500
  }
501
  });
502
  </script>
 
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-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 {
 
 
 
58
  position: absolute;
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>
287
  </head>
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>