unified-video-framework 1.4.446 → 1.4.448

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/interfaces.d.ts +14 -0
  3. package/packages/core/dist/interfaces.d.ts.map +1 -1
  4. package/packages/core/dist/version.d.ts +1 -1
  5. package/packages/core/dist/version.js +1 -1
  6. package/packages/core/src/interfaces.ts +24 -0
  7. package/packages/core/src/version.ts +1 -1
  8. package/packages/web/dist/WebPlayer.d.ts +364 -0
  9. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  10. package/packages/web/dist/WebPlayer.js +347 -7
  11. package/packages/web/dist/WebPlayer.js.map +1 -1
  12. package/packages/web/dist/chapters/ChapterManager.js +3 -3
  13. package/packages/web/dist/chapters/SkipButtonController.js +1 -1
  14. package/packages/web/dist/chapters/index.js +7 -7
  15. package/packages/web/dist/index.js +4 -4
  16. package/packages/web/dist/paywall/PaywallController.js +1 -1
  17. package/packages/web/dist/react/EPG.js +6 -6
  18. package/packages/web/dist/react/WebPlayerView.d.ts +399 -0
  19. package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
  20. package/packages/web/dist/react/WebPlayerView.js +19 -6
  21. package/packages/web/dist/react/WebPlayerView.js.map +1 -1
  22. package/packages/web/dist/react/WebPlayerViewWithEPG.js +2 -2
  23. package/packages/web/dist/react/components/ChapterProgress.js +1 -1
  24. package/packages/web/dist/react/components/EPGOverlay-improved-positioning.js +5 -5
  25. package/packages/web/dist/react/components/EPGOverlay.js +5 -5
  26. package/packages/web/dist/react/components/EPGProgramDetails.js +1 -1
  27. package/packages/web/dist/react/components/EPGProgramGrid.js +1 -1
  28. package/packages/web/dist/react/components/EPGTimelineHeader.js +1 -1
  29. package/packages/web/dist/react/components/SkipButton.js +1 -1
  30. package/packages/web/dist/react/examples/google-ads-example.js +1 -1
  31. package/packages/web/dist/react/types/ThumbnailPreviewTypes.d.ts +26 -0
  32. package/packages/web/dist/react/types/ThumbnailPreviewTypes.d.ts.map +1 -0
  33. package/packages/web/dist/react/types/ThumbnailPreviewTypes.js +2 -0
  34. package/packages/web/dist/react/types/ThumbnailPreviewTypes.js.map +1 -0
  35. package/packages/web/dist/test/epg-test.js +1 -1
  36. package/packages/web/src/WebPlayer.ts +432 -4
  37. package/packages/web/src/react/WebPlayerView.tsx +21 -2
  38. package/packages/web/src/react/types/ThumbnailPreviewTypes.ts +62 -0
@@ -1,8 +1,8 @@
1
- import { BasePlayer } from "../../core/dist/BasePlayer.js";
2
- import { ChapterManager as CoreChapterManager } from "../../core/dist/index.js";
3
- import { ChapterManager } from "./chapters/ChapterManager.js";
4
- import YouTubeExtractor from "./utils/YouTubeExtractor.js";
5
- import { DRMManager, DRMErrorHandler } from "./drm/index.js";
1
+ import { BasePlayer } from '../../core/dist/BasePlayer.js';
2
+ import { ChapterManager as CoreChapterManager } from '../../core/dist/index.js';
3
+ import { ChapterManager } from './chapters/ChapterManager.js';
4
+ import YouTubeExtractor from './utils/YouTubeExtractor.js';
5
+ import { DRMManager, DRMErrorHandler } from './drm/index.js';
6
6
  export class WebPlayer extends BasePlayer {
7
7
  constructor() {
8
8
  super(...arguments);
@@ -112,6 +112,11 @@ export class WebPlayer extends BasePlayer {
112
112
  this.MAX_HLS_ERROR_RETRIES = 3;
113
113
  this.lastDuration = 0;
114
114
  this.isDetectedAsLive = false;
115
+ this.thumbnailPreviewConfig = null;
116
+ this.thumbnailEntries = [];
117
+ this.preloadedThumbnails = new Map();
118
+ this.currentThumbnailUrl = null;
119
+ this.thumbnailPreviewEnabled = false;
115
120
  this.autoplayAttempted = false;
116
121
  this.youtubePlayer = null;
117
122
  this.youtubePlayerReady = false;
@@ -134,6 +139,169 @@ export class WebPlayer extends BasePlayer {
134
139
  console.warn(`[WebPlayer] ${message}`, ...args);
135
140
  }
136
141
  }
142
+ initializeThumbnailPreview(config) {
143
+ if (!config || !config.generationImage) {
144
+ this.thumbnailPreviewEnabled = false;
145
+ return;
146
+ }
147
+ this.thumbnailPreviewConfig = config;
148
+ this.thumbnailPreviewEnabled = config.enabled !== false;
149
+ this.thumbnailEntries = this.transformThumbnailData(config.generationImage);
150
+ this.debugLog('Thumbnail preview initialized:', {
151
+ enabled: this.thumbnailPreviewEnabled,
152
+ entries: this.thumbnailEntries.length
153
+ });
154
+ if (config.style) {
155
+ this.applyThumbnailStyles(config.style);
156
+ }
157
+ if (config.preloadImages !== false && this.thumbnailEntries.length > 0) {
158
+ this.preloadThumbnailImages();
159
+ }
160
+ }
161
+ transformThumbnailData(generationImage) {
162
+ const entries = [];
163
+ for (const [url, timeRange] of Object.entries(generationImage)) {
164
+ const parts = timeRange.split('-');
165
+ if (parts.length === 2) {
166
+ const startTime = parseFloat(parts[0]);
167
+ const endTime = parseFloat(parts[1]);
168
+ if (!isNaN(startTime) && !isNaN(endTime)) {
169
+ entries.push({ url, startTime, endTime });
170
+ }
171
+ }
172
+ }
173
+ entries.sort((a, b) => a.startTime - b.startTime);
174
+ return entries;
175
+ }
176
+ findThumbnailForTime(time) {
177
+ if (this.thumbnailEntries.length === 0)
178
+ return null;
179
+ let left = 0;
180
+ let right = this.thumbnailEntries.length - 1;
181
+ let result = null;
182
+ while (left <= right) {
183
+ const mid = Math.floor((left + right) / 2);
184
+ const entry = this.thumbnailEntries[mid];
185
+ if (time >= entry.startTime && time < entry.endTime) {
186
+ return entry;
187
+ }
188
+ else if (time < entry.startTime) {
189
+ right = mid - 1;
190
+ }
191
+ else {
192
+ left = mid + 1;
193
+ }
194
+ }
195
+ if (left > 0 && left <= this.thumbnailEntries.length) {
196
+ const prevEntry = this.thumbnailEntries[left - 1];
197
+ if (time >= prevEntry.startTime && time < prevEntry.endTime) {
198
+ return prevEntry;
199
+ }
200
+ }
201
+ return result;
202
+ }
203
+ preloadThumbnailImages() {
204
+ this.debugLog('Preloading', this.thumbnailEntries.length, 'thumbnail images');
205
+ for (const entry of this.thumbnailEntries) {
206
+ if (this.preloadedThumbnails.has(entry.url))
207
+ continue;
208
+ const img = new Image();
209
+ img.onload = () => {
210
+ this.preloadedThumbnails.set(entry.url, img);
211
+ this.debugLog('Preloaded thumbnail:', entry.url);
212
+ };
213
+ img.onerror = () => {
214
+ this.debugWarn('Failed to preload thumbnail:', entry.url);
215
+ };
216
+ img.src = entry.url;
217
+ }
218
+ }
219
+ applyThumbnailStyles(style) {
220
+ if (!style)
221
+ return;
222
+ const wrapper = document.getElementById('uvf-thumbnail-preview');
223
+ const imageWrapper = wrapper?.querySelector('.uvf-thumbnail-preview-image-wrapper');
224
+ if (imageWrapper) {
225
+ if (style.width) {
226
+ imageWrapper.style.width = `${style.width}px`;
227
+ }
228
+ if (style.height) {
229
+ imageWrapper.style.height = `${style.height}px`;
230
+ }
231
+ if (style.borderRadius !== undefined) {
232
+ imageWrapper.style.borderRadius = `${style.borderRadius}px`;
233
+ }
234
+ }
235
+ }
236
+ updateThumbnailPreview(e) {
237
+ if (!this.thumbnailPreviewEnabled || this.thumbnailEntries.length === 0) {
238
+ return;
239
+ }
240
+ const progressBar = document.getElementById('uvf-progress-bar');
241
+ const thumbnailPreview = document.getElementById('uvf-thumbnail-preview');
242
+ const thumbnailImage = document.getElementById('uvf-thumbnail-image');
243
+ const thumbnailTime = document.getElementById('uvf-thumbnail-time');
244
+ if (!progressBar || !thumbnailPreview || !thumbnailImage || !this.video) {
245
+ return;
246
+ }
247
+ const rect = progressBar.getBoundingClientRect();
248
+ const x = e.clientX - rect.left;
249
+ const percent = Math.max(0, Math.min(1, x / rect.width));
250
+ const time = percent * this.video.duration;
251
+ const entry = this.findThumbnailForTime(time);
252
+ if (entry) {
253
+ thumbnailPreview.classList.add('visible');
254
+ const thumbnailWidth = 160;
255
+ const halfWidth = thumbnailWidth / 2;
256
+ const minX = halfWidth;
257
+ const maxX = rect.width - halfWidth;
258
+ const clampedX = Math.max(minX, Math.min(maxX, x));
259
+ thumbnailPreview.style.left = `${clampedX}px`;
260
+ if (this.currentThumbnailUrl !== entry.url) {
261
+ this.currentThumbnailUrl = entry.url;
262
+ thumbnailImage.classList.remove('loaded');
263
+ const preloaded = this.preloadedThumbnails.get(entry.url);
264
+ if (preloaded) {
265
+ thumbnailImage.src = preloaded.src;
266
+ thumbnailImage.classList.add('loaded');
267
+ }
268
+ else {
269
+ thumbnailImage.onload = () => {
270
+ thumbnailImage.classList.add('loaded');
271
+ };
272
+ thumbnailImage.src = entry.url;
273
+ }
274
+ }
275
+ if (thumbnailTime && this.thumbnailPreviewConfig?.showTimeInThumbnail !== false) {
276
+ thumbnailTime.textContent = this.formatTime(time);
277
+ thumbnailTime.style.display = 'block';
278
+ }
279
+ else if (thumbnailTime) {
280
+ thumbnailTime.style.display = 'none';
281
+ }
282
+ }
283
+ else {
284
+ this.hideThumbnailPreview();
285
+ }
286
+ }
287
+ hideThumbnailPreview() {
288
+ const thumbnailPreview = document.getElementById('uvf-thumbnail-preview');
289
+ if (thumbnailPreview) {
290
+ thumbnailPreview.classList.remove('visible');
291
+ }
292
+ this.currentThumbnailUrl = null;
293
+ }
294
+ setThumbnailPreview(config) {
295
+ if (!config) {
296
+ this.thumbnailPreviewEnabled = false;
297
+ this.thumbnailPreviewConfig = null;
298
+ this.thumbnailEntries = [];
299
+ this.preloadedThumbnails.clear();
300
+ this.hideThumbnailPreview();
301
+ return;
302
+ }
303
+ this.initializeThumbnailPreview(config);
304
+ }
137
305
  async initialize(container, config) {
138
306
  console.log('WebPlayer.initialize called with config:', config);
139
307
  if (config) {
@@ -196,6 +364,10 @@ export class WebPlayer extends BasePlayer {
196
364
  console.log('YouTube native controls config found:', config.youtubeNativeControls);
197
365
  this.youtubeNativeControls = config.youtubeNativeControls;
198
366
  }
367
+ if (config && config.thumbnailPreview) {
368
+ console.log('Thumbnail preview config found:', config.thumbnailPreview);
369
+ this.initializeThumbnailPreview(config.thumbnailPreview);
370
+ }
199
371
  await super.initialize(container, config);
200
372
  }
201
373
  async setupPlayer() {
@@ -4321,7 +4493,37 @@ export class WebPlayer extends BasePlayer {
4321
4493
  top: 2px; /* Center on the 4px hover progress bar (2px from top) */
4322
4494
  transform: translate(-50%, -50%) scale(1);
4323
4495
  }
4324
-
4496
+
4497
+ /* Prevent text selection during seekbar drag */
4498
+ .uvf-player-wrapper.seeking {
4499
+ user-select: none;
4500
+ -webkit-user-select: none;
4501
+ -moz-user-select: none;
4502
+ -ms-user-select: none;
4503
+ }
4504
+
4505
+ /* Maintain expanded seekbar state during drag (same as hover) */
4506
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-bar {
4507
+ height: 4px;
4508
+ background: rgba(255, 255, 255, 0.2);
4509
+ border-radius: 6px;
4510
+ transform: scaleY(1.1);
4511
+ }
4512
+
4513
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-handle {
4514
+ opacity: 1;
4515
+ top: 2px;
4516
+ transform: translate(-50%, -50%) scale(1);
4517
+ }
4518
+
4519
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-buffered {
4520
+ border-radius: 6px;
4521
+ }
4522
+
4523
+ .uvf-progress-bar-wrapper.dragging .uvf-progress-filled {
4524
+ border-radius: 6px;
4525
+ }
4526
+
4325
4527
  .uvf-progress-handle:hover {
4326
4528
  transform: translate(-50%, -50%) scale(1.2);
4327
4529
  box-shadow: 0 3px 12px rgba(0, 0, 0, 0.4);
@@ -4376,7 +4578,122 @@ export class WebPlayer extends BasePlayer {
4376
4578
  opacity: 1;
4377
4579
  transform: translateX(-50%) translateY(0);
4378
4580
  }
4379
-
4581
+
4582
+ /* Thumbnail Preview */
4583
+ .uvf-thumbnail-preview {
4584
+ position: absolute;
4585
+ bottom: 100%;
4586
+ left: 0;
4587
+ margin-bottom: 12px;
4588
+ display: flex;
4589
+ flex-direction: column;
4590
+ align-items: center;
4591
+ pointer-events: none;
4592
+ z-index: 25;
4593
+ opacity: 0;
4594
+ transform: translateX(-50%) translateY(8px);
4595
+ transition: opacity 0.15s ease, transform 0.15s ease;
4596
+ }
4597
+
4598
+ .uvf-thumbnail-preview.visible {
4599
+ opacity: 1;
4600
+ transform: translateX(-50%) translateY(0);
4601
+ }
4602
+
4603
+ .uvf-thumbnail-preview-container {
4604
+ position: relative;
4605
+ background: rgba(0, 0, 0, 0.9);
4606
+ border-radius: 8px;
4607
+ padding: 4px;
4608
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
4609
+ backdrop-filter: blur(12px);
4610
+ -webkit-backdrop-filter: blur(12px);
4611
+ }
4612
+
4613
+ .uvf-thumbnail-preview-image-wrapper {
4614
+ position: relative;
4615
+ width: 160px;
4616
+ height: 90px;
4617
+ border-radius: 6px;
4618
+ overflow: hidden;
4619
+ background: rgba(30, 30, 30, 0.95);
4620
+ }
4621
+
4622
+ .uvf-thumbnail-preview-image {
4623
+ width: 100%;
4624
+ height: 100%;
4625
+ object-fit: cover;
4626
+ display: block;
4627
+ opacity: 0;
4628
+ transition: opacity 0.2s ease;
4629
+ }
4630
+
4631
+ .uvf-thumbnail-preview-image.loaded {
4632
+ opacity: 1;
4633
+ }
4634
+
4635
+ .uvf-thumbnail-preview-loading {
4636
+ position: absolute;
4637
+ top: 50%;
4638
+ left: 50%;
4639
+ transform: translate(-50%, -50%);
4640
+ width: 24px;
4641
+ height: 24px;
4642
+ border: 2px solid rgba(255, 255, 255, 0.2);
4643
+ border-top-color: var(--uvf-accent-1, #ff5722);
4644
+ border-radius: 50%;
4645
+ animation: uvf-spin 0.8s linear infinite;
4646
+ }
4647
+
4648
+ .uvf-thumbnail-preview-image.loaded + .uvf-thumbnail-preview-loading {
4649
+ display: none;
4650
+ }
4651
+
4652
+ .uvf-thumbnail-preview-time {
4653
+ position: absolute;
4654
+ bottom: 6px;
4655
+ left: 50%;
4656
+ transform: translateX(-50%);
4657
+ background: rgba(0, 0, 0, 0.85);
4658
+ color: #fff;
4659
+ font-size: 12px;
4660
+ font-weight: 600;
4661
+ padding: 3px 8px;
4662
+ border-radius: 4px;
4663
+ white-space: nowrap;
4664
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
4665
+ backdrop-filter: blur(4px);
4666
+ -webkit-backdrop-filter: blur(4px);
4667
+ }
4668
+
4669
+ .uvf-thumbnail-preview-arrow {
4670
+ width: 0;
4671
+ height: 0;
4672
+ border-left: 8px solid transparent;
4673
+ border-right: 8px solid transparent;
4674
+ border-top: 8px solid rgba(0, 0, 0, 0.9);
4675
+ margin-top: -1px;
4676
+ }
4677
+
4678
+ /* Hide regular time tooltip when thumbnail preview is visible */
4679
+ .uvf-thumbnail-preview.visible ~ .uvf-time-tooltip {
4680
+ opacity: 0 !important;
4681
+ pointer-events: none;
4682
+ }
4683
+
4684
+ /* Responsive thumbnail sizes */
4685
+ @media (max-width: 768px) {
4686
+ .uvf-thumbnail-preview-image-wrapper {
4687
+ width: 120px;
4688
+ height: 68px;
4689
+ }
4690
+
4691
+ .uvf-thumbnail-preview-time {
4692
+ font-size: 11px;
4693
+ padding: 2px 6px;
4694
+ }
4695
+ }
4696
+
4380
4697
  /* Chapter Markers */
4381
4698
  .uvf-chapter-marker {
4382
4699
  position: absolute;
@@ -7390,6 +7707,16 @@ export class WebPlayer extends BasePlayer {
7390
7707
  <div class="uvf-progress-filled" id="uvf-progress-filled"></div>
7391
7708
  <div class="uvf-progress-handle" id="uvf-progress-handle"></div>
7392
7709
  </div>
7710
+ <div class="uvf-thumbnail-preview" id="uvf-thumbnail-preview">
7711
+ <div class="uvf-thumbnail-preview-container">
7712
+ <div class="uvf-thumbnail-preview-image-wrapper">
7713
+ <img class="uvf-thumbnail-preview-image" id="uvf-thumbnail-image" alt="Preview" />
7714
+ <div class="uvf-thumbnail-preview-loading"></div>
7715
+ </div>
7716
+ <div class="uvf-thumbnail-preview-time" id="uvf-thumbnail-time">00:00</div>
7717
+ </div>
7718
+ <div class="uvf-thumbnail-preview-arrow"></div>
7719
+ </div>
7393
7720
  <div class="uvf-time-tooltip" id="uvf-time-tooltip">00:00</div>
7394
7721
  `;
7395
7722
  progressSection.appendChild(progressBar);
@@ -7708,6 +8035,8 @@ export class WebPlayer extends BasePlayer {
7708
8035
  progressBar?.addEventListener('mousedown', (e) => {
7709
8036
  this.isDragging = true;
7710
8037
  this.showTimeTooltip = true;
8038
+ progressBar.classList.add('dragging');
8039
+ this.playerWrapper?.classList.add('seeking');
7711
8040
  this.seekToPosition(e);
7712
8041
  this.updateTimeTooltip(e);
7713
8042
  });
@@ -7718,16 +8047,20 @@ export class WebPlayer extends BasePlayer {
7718
8047
  if (!this.isDragging) {
7719
8048
  this.showTimeTooltip = false;
7720
8049
  this.hideTimeTooltip();
8050
+ this.hideThumbnailPreview();
7721
8051
  }
7722
8052
  });
7723
8053
  progressBar?.addEventListener('mousemove', (e) => {
7724
8054
  if (this.showTimeTooltip) {
7725
8055
  this.updateTimeTooltip(e);
8056
+ this.updateThumbnailPreview(e);
7726
8057
  }
7727
8058
  });
7728
8059
  progressBar?.addEventListener('touchstart', (e) => {
7729
8060
  e.preventDefault();
7730
8061
  this.isDragging = true;
8062
+ progressBar.classList.add('dragging');
8063
+ this.playerWrapper?.classList.add('seeking');
7731
8064
  const touch = e.touches[0];
7732
8065
  const mouseEvent = new MouseEvent('mousedown', {
7733
8066
  clientX: touch.clientX,
@@ -7742,6 +8075,7 @@ export class WebPlayer extends BasePlayer {
7742
8075
  if (this.isDragging && progressBar) {
7743
8076
  this.seekToPosition(e);
7744
8077
  this.updateTimeTooltip(e);
8078
+ this.updateThumbnailPreview(e);
7745
8079
  }
7746
8080
  });
7747
8081
  document.addEventListener('touchmove', (e) => {
@@ -7766,21 +8100,27 @@ export class WebPlayer extends BasePlayer {
7766
8100
  }
7767
8101
  if (this.isDragging) {
7768
8102
  this.isDragging = false;
8103
+ progressBar?.classList.remove('dragging');
8104
+ this.playerWrapper?.classList.remove('seeking');
7769
8105
  const handle = document.getElementById('uvf-progress-handle');
7770
8106
  handle?.classList.remove('dragging');
7771
8107
  if (progressBar && !progressBar.matches(':hover')) {
7772
8108
  this.showTimeTooltip = false;
7773
8109
  this.hideTimeTooltip();
8110
+ this.hideThumbnailPreview();
7774
8111
  }
7775
8112
  }
7776
8113
  });
7777
8114
  document.addEventListener('touchend', () => {
7778
8115
  if (this.isDragging) {
7779
8116
  this.isDragging = false;
8117
+ progressBar?.classList.remove('dragging');
8118
+ this.playerWrapper?.classList.remove('seeking');
7780
8119
  const handle = document.getElementById('uvf-progress-handle');
7781
8120
  handle?.classList.remove('dragging');
7782
8121
  this.showTimeTooltip = false;
7783
8122
  this.hideTimeTooltip();
8123
+ this.hideThumbnailPreview();
7784
8124
  }
7785
8125
  });
7786
8126
  this.video.addEventListener('timeupdate', () => {