unified-video-framework 1.4.446 → 1.4.447

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 +308 -6
  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 +389 -3
  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() {
@@ -4376,7 +4548,122 @@ export class WebPlayer extends BasePlayer {
4376
4548
  opacity: 1;
4377
4549
  transform: translateX(-50%) translateY(0);
4378
4550
  }
4379
-
4551
+
4552
+ /* Thumbnail Preview */
4553
+ .uvf-thumbnail-preview {
4554
+ position: absolute;
4555
+ bottom: 100%;
4556
+ left: 0;
4557
+ margin-bottom: 12px;
4558
+ display: flex;
4559
+ flex-direction: column;
4560
+ align-items: center;
4561
+ pointer-events: none;
4562
+ z-index: 25;
4563
+ opacity: 0;
4564
+ transform: translateX(-50%) translateY(8px);
4565
+ transition: opacity 0.15s ease, transform 0.15s ease;
4566
+ }
4567
+
4568
+ .uvf-thumbnail-preview.visible {
4569
+ opacity: 1;
4570
+ transform: translateX(-50%) translateY(0);
4571
+ }
4572
+
4573
+ .uvf-thumbnail-preview-container {
4574
+ position: relative;
4575
+ background: rgba(0, 0, 0, 0.9);
4576
+ border-radius: 8px;
4577
+ padding: 4px;
4578
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1);
4579
+ backdrop-filter: blur(12px);
4580
+ -webkit-backdrop-filter: blur(12px);
4581
+ }
4582
+
4583
+ .uvf-thumbnail-preview-image-wrapper {
4584
+ position: relative;
4585
+ width: 160px;
4586
+ height: 90px;
4587
+ border-radius: 6px;
4588
+ overflow: hidden;
4589
+ background: rgba(30, 30, 30, 0.95);
4590
+ }
4591
+
4592
+ .uvf-thumbnail-preview-image {
4593
+ width: 100%;
4594
+ height: 100%;
4595
+ object-fit: cover;
4596
+ display: block;
4597
+ opacity: 0;
4598
+ transition: opacity 0.2s ease;
4599
+ }
4600
+
4601
+ .uvf-thumbnail-preview-image.loaded {
4602
+ opacity: 1;
4603
+ }
4604
+
4605
+ .uvf-thumbnail-preview-loading {
4606
+ position: absolute;
4607
+ top: 50%;
4608
+ left: 50%;
4609
+ transform: translate(-50%, -50%);
4610
+ width: 24px;
4611
+ height: 24px;
4612
+ border: 2px solid rgba(255, 255, 255, 0.2);
4613
+ border-top-color: var(--uvf-accent-1, #ff5722);
4614
+ border-radius: 50%;
4615
+ animation: uvf-spin 0.8s linear infinite;
4616
+ }
4617
+
4618
+ .uvf-thumbnail-preview-image.loaded + .uvf-thumbnail-preview-loading {
4619
+ display: none;
4620
+ }
4621
+
4622
+ .uvf-thumbnail-preview-time {
4623
+ position: absolute;
4624
+ bottom: 6px;
4625
+ left: 50%;
4626
+ transform: translateX(-50%);
4627
+ background: rgba(0, 0, 0, 0.85);
4628
+ color: #fff;
4629
+ font-size: 12px;
4630
+ font-weight: 600;
4631
+ padding: 3px 8px;
4632
+ border-radius: 4px;
4633
+ white-space: nowrap;
4634
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
4635
+ backdrop-filter: blur(4px);
4636
+ -webkit-backdrop-filter: blur(4px);
4637
+ }
4638
+
4639
+ .uvf-thumbnail-preview-arrow {
4640
+ width: 0;
4641
+ height: 0;
4642
+ border-left: 8px solid transparent;
4643
+ border-right: 8px solid transparent;
4644
+ border-top: 8px solid rgba(0, 0, 0, 0.9);
4645
+ margin-top: -1px;
4646
+ }
4647
+
4648
+ /* Hide regular time tooltip when thumbnail preview is visible */
4649
+ .uvf-thumbnail-preview.visible ~ .uvf-time-tooltip {
4650
+ opacity: 0 !important;
4651
+ pointer-events: none;
4652
+ }
4653
+
4654
+ /* Responsive thumbnail sizes */
4655
+ @media (max-width: 768px) {
4656
+ .uvf-thumbnail-preview-image-wrapper {
4657
+ width: 120px;
4658
+ height: 68px;
4659
+ }
4660
+
4661
+ .uvf-thumbnail-preview-time {
4662
+ font-size: 11px;
4663
+ padding: 2px 6px;
4664
+ }
4665
+ }
4666
+
4380
4667
  /* Chapter Markers */
4381
4668
  .uvf-chapter-marker {
4382
4669
  position: absolute;
@@ -7390,6 +7677,16 @@ export class WebPlayer extends BasePlayer {
7390
7677
  <div class="uvf-progress-filled" id="uvf-progress-filled"></div>
7391
7678
  <div class="uvf-progress-handle" id="uvf-progress-handle"></div>
7392
7679
  </div>
7680
+ <div class="uvf-thumbnail-preview" id="uvf-thumbnail-preview">
7681
+ <div class="uvf-thumbnail-preview-container">
7682
+ <div class="uvf-thumbnail-preview-image-wrapper">
7683
+ <img class="uvf-thumbnail-preview-image" id="uvf-thumbnail-image" alt="Preview" />
7684
+ <div class="uvf-thumbnail-preview-loading"></div>
7685
+ </div>
7686
+ <div class="uvf-thumbnail-preview-time" id="uvf-thumbnail-time">00:00</div>
7687
+ </div>
7688
+ <div class="uvf-thumbnail-preview-arrow"></div>
7689
+ </div>
7393
7690
  <div class="uvf-time-tooltip" id="uvf-time-tooltip">00:00</div>
7394
7691
  `;
7395
7692
  progressSection.appendChild(progressBar);
@@ -7718,11 +8015,13 @@ export class WebPlayer extends BasePlayer {
7718
8015
  if (!this.isDragging) {
7719
8016
  this.showTimeTooltip = false;
7720
8017
  this.hideTimeTooltip();
8018
+ this.hideThumbnailPreview();
7721
8019
  }
7722
8020
  });
7723
8021
  progressBar?.addEventListener('mousemove', (e) => {
7724
8022
  if (this.showTimeTooltip) {
7725
8023
  this.updateTimeTooltip(e);
8024
+ this.updateThumbnailPreview(e);
7726
8025
  }
7727
8026
  });
7728
8027
  progressBar?.addEventListener('touchstart', (e) => {
@@ -7742,6 +8041,7 @@ export class WebPlayer extends BasePlayer {
7742
8041
  if (this.isDragging && progressBar) {
7743
8042
  this.seekToPosition(e);
7744
8043
  this.updateTimeTooltip(e);
8044
+ this.updateThumbnailPreview(e);
7745
8045
  }
7746
8046
  });
7747
8047
  document.addEventListener('touchmove', (e) => {
@@ -7771,6 +8071,7 @@ export class WebPlayer extends BasePlayer {
7771
8071
  if (progressBar && !progressBar.matches(':hover')) {
7772
8072
  this.showTimeTooltip = false;
7773
8073
  this.hideTimeTooltip();
8074
+ this.hideThumbnailPreview();
7774
8075
  }
7775
8076
  }
7776
8077
  });
@@ -7781,6 +8082,7 @@ export class WebPlayer extends BasePlayer {
7781
8082
  handle?.classList.remove('dragging');
7782
8083
  this.showTimeTooltip = false;
7783
8084
  this.hideTimeTooltip();
8085
+ this.hideThumbnailPreview();
7784
8086
  }
7785
8087
  });
7786
8088
  this.video.addEventListener('timeupdate', () => {