unified-video-framework 1.4.151 → 1.4.154

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 (86) hide show
  1. package/package.json +1 -1
  2. package/packages/core/dist/chapter-manager.d.ts +39 -0
  3. package/packages/core/dist/chapter-manager.d.ts.map +1 -0
  4. package/packages/core/dist/chapter-manager.js +173 -0
  5. package/packages/core/dist/chapter-manager.js.map +1 -0
  6. package/packages/core/dist/index.d.ts +2 -0
  7. package/packages/core/dist/index.d.ts.map +1 -1
  8. package/packages/core/dist/index.js +1 -0
  9. package/packages/core/dist/index.js.map +1 -1
  10. package/packages/core/dist/interfaces/IVideoPlayer.d.ts +10 -0
  11. package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
  12. package/packages/core/dist/interfaces.d.ts +33 -1
  13. package/packages/core/dist/interfaces.d.ts.map +1 -1
  14. package/packages/core/src/chapter-manager.ts +290 -0
  15. package/packages/core/src/index.ts +4 -0
  16. package/packages/core/src/interfaces/IVideoPlayer.ts +11 -0
  17. package/packages/core/src/interfaces.ts +47 -1
  18. package/packages/web/dist/WebPlayer.d.ts +24 -1
  19. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  20. package/packages/web/dist/WebPlayer.js +472 -1
  21. package/packages/web/dist/WebPlayer.js.map +1 -1
  22. package/packages/web/dist/chapters/ChapterManager.d.ts +38 -0
  23. package/packages/web/dist/chapters/ChapterManager.d.ts.map +1 -0
  24. package/packages/web/dist/chapters/ChapterManager.js +291 -0
  25. package/packages/web/dist/chapters/ChapterManager.js.map +1 -0
  26. package/packages/web/dist/chapters/SkipButtonController.d.ts +31 -0
  27. package/packages/web/dist/chapters/SkipButtonController.d.ts.map +1 -0
  28. package/packages/web/dist/chapters/SkipButtonController.js +213 -0
  29. package/packages/web/dist/chapters/SkipButtonController.js.map +1 -0
  30. package/packages/web/dist/chapters/UserPreferencesManager.d.ts +25 -0
  31. package/packages/web/dist/chapters/UserPreferencesManager.d.ts.map +1 -0
  32. package/packages/web/dist/chapters/UserPreferencesManager.js +232 -0
  33. package/packages/web/dist/chapters/UserPreferencesManager.js.map +1 -0
  34. package/packages/web/dist/chapters/index.d.ts +12 -0
  35. package/packages/web/dist/chapters/index.d.ts.map +1 -0
  36. package/packages/web/dist/chapters/index.js +8 -0
  37. package/packages/web/dist/chapters/index.js.map +1 -0
  38. package/packages/web/dist/chapters/types/ChapterTypes.d.ts +98 -0
  39. package/packages/web/dist/chapters/types/ChapterTypes.d.ts.map +1 -0
  40. package/packages/web/dist/chapters/types/ChapterTypes.js +31 -0
  41. package/packages/web/dist/chapters/types/ChapterTypes.js.map +1 -0
  42. package/packages/web/dist/index.d.ts +1 -1
  43. package/packages/web/dist/index.d.ts.map +1 -1
  44. package/packages/web/dist/index.js +1 -1
  45. package/packages/web/dist/index.js.map +1 -1
  46. package/packages/web/dist/react/WebPlayerView.d.ts +2 -2
  47. package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
  48. package/packages/web/dist/react/WebPlayerViewWithEPG.d.ts +2 -2
  49. package/packages/web/dist/react/WebPlayerViewWithEPG.d.ts.map +1 -1
  50. package/packages/web/dist/react/components/ChapterProgress.d.ts +22 -0
  51. package/packages/web/dist/react/components/ChapterProgress.d.ts.map +1 -0
  52. package/packages/web/dist/react/components/ChapterProgress.js +101 -0
  53. package/packages/web/dist/react/components/ChapterProgress.js.map +1 -0
  54. package/packages/web/dist/react/components/SkipButton.d.ts +18 -0
  55. package/packages/web/dist/react/components/SkipButton.d.ts.map +1 -0
  56. package/packages/web/dist/react/components/SkipButton.js +156 -0
  57. package/packages/web/dist/react/components/SkipButton.js.map +1 -0
  58. package/packages/web/dist/react/hooks/useChapters.d.ts +29 -0
  59. package/packages/web/dist/react/hooks/useChapters.d.ts.map +1 -0
  60. package/packages/web/dist/react/hooks/useChapters.js +158 -0
  61. package/packages/web/dist/react/hooks/useChapters.js.map +1 -0
  62. package/packages/web/package.json +0 -3
  63. package/packages/web/src/SecureVideoPlayer.ts +1 -1
  64. package/packages/web/src/WebPlayer.ts +587 -3
  65. package/packages/web/src/__tests__/WebPlayer.test.ts +1 -1
  66. package/packages/web/src/__tests__/epg-integration.test.ts +1 -1
  67. package/packages/web/src/chapters/ChapterManager.ts +464 -0
  68. package/packages/web/src/chapters/SkipButtonController.ts +353 -0
  69. package/packages/web/src/chapters/UserPreferencesManager.ts +324 -0
  70. package/packages/web/src/chapters/index.ts +34 -0
  71. package/packages/web/src/chapters/types/ChapterTypes.ts +236 -0
  72. package/packages/web/src/index.ts +1 -1
  73. package/packages/web/src/react/EPG.ts +1 -1
  74. package/packages/web/src/react/WebPlayerView.tsx +2 -2
  75. package/packages/web/src/react/WebPlayerViewWithEPG.tsx +3 -3
  76. package/packages/web/src/react/components/ChapterProgress.tsx +207 -0
  77. package/packages/web/src/react/components/EPGNavigationControls.tsx +1 -1
  78. package/packages/web/src/react/components/EPGOverlay-improved-positioning.tsx +1 -1
  79. package/packages/web/src/react/components/EPGOverlay.tsx +1 -1
  80. package/packages/web/src/react/components/EPGProgramGrid.tsx +1 -1
  81. package/packages/web/src/react/components/EPGTimelineHeader.tsx +1 -1
  82. package/packages/web/src/react/components/SkipButton.tsx +278 -0
  83. package/packages/web/src/react/hooks/useChapters.ts +308 -0
  84. package/packages/web/src/react/types/EPGTypes.ts +1 -1
  85. package/packages/web/src/react/utils/EPGUtils.ts +1 -1
  86. package/packages/web/src/test/epg-test.ts +1 -1
@@ -2,14 +2,24 @@
2
2
  * Web implementation of the video player with HLS and DASH support
3
3
  */
4
4
 
5
- import { BasePlayer } from '@unified-video/core';
5
+ import { BasePlayer } from '../../core/src/BasePlayer';
6
6
  import {
7
7
  VideoSource,
8
8
  PlayerConfig,
9
9
  Quality,
10
10
  SubtitleTrack,
11
- PlayerError
12
- } from '@unified-video/core';
11
+ PlayerError,
12
+ ChapterManager as CoreChapterManager,
13
+ Chapter,
14
+ ChapterSegment
15
+ } from '../../core/src/index';
16
+ import { ChapterManager } from './chapters/ChapterManager';
17
+ import {
18
+ ChapterConfig,
19
+ VideoChapters,
20
+ VideoSegment,
21
+ ChapterEvents
22
+ } from './chapters/types/ChapterTypes';
13
23
 
14
24
  // Dynamic imports for streaming libraries
15
25
  declare global {
@@ -86,6 +96,11 @@ export class WebPlayer extends BasePlayer {
86
96
 
87
97
  // Progress bar tooltip state
88
98
  private showTimeTooltip: boolean = false;
99
+
100
+ // Chapter management
101
+ private chapterManager: ChapterManager | null = null;
102
+ private coreChapterManager: CoreChapterManager | null = null;
103
+ private chapterConfig: ChapterConfig = { enabled: false };
89
104
 
90
105
  // Debug logging helper
91
106
  private debugLog(message: string, ...args: any[]): void {
@@ -131,6 +146,32 @@ export class WebPlayer extends BasePlayer {
131
146
  console.log('No settings config found, using defaults:', this.settingsConfig);
132
147
  }
133
148
 
149
+ // Configure chapters if provided
150
+ if (config && config.chapters) {
151
+ console.log('Chapter config found:', config.chapters);
152
+ this.chapterConfig = {
153
+ enabled: config.chapters.enabled || false,
154
+ data: config.chapters.data,
155
+ dataUrl: config.chapters.dataUrl,
156
+ autoHide: config.chapters.autoHide !== undefined ? config.chapters.autoHide : true,
157
+ autoHideDelay: config.chapters.autoHideDelay || 5000,
158
+ showChapterMarkers: config.chapters.showChapterMarkers !== undefined ? config.chapters.showChapterMarkers : true,
159
+ skipButtonPosition: config.chapters.skipButtonPosition || 'bottom-right',
160
+ customStyles: config.chapters.customStyles || {},
161
+ userPreferences: config.chapters.userPreferences || {
162
+ autoSkipIntro: false,
163
+ autoSkipRecap: false,
164
+ autoSkipCredits: false,
165
+ showSkipButtons: true,
166
+ skipButtonTimeout: 5000,
167
+ rememberChoices: true
168
+ }
169
+ };
170
+ console.log('Chapter config applied:', this.chapterConfig);
171
+ } else {
172
+ console.log('No chapter config found, chapters disabled');
173
+ }
174
+
134
175
  // Call parent initialize
135
176
  await super.initialize(container, config);
136
177
  }
@@ -198,6 +239,11 @@ export class WebPlayer extends BasePlayer {
198
239
  this.setupWatermark();
199
240
  this.setupFullscreenListeners();
200
241
  this.setupUserInteractionTracking();
242
+
243
+ // Initialize chapter manager if enabled
244
+ if (this.chapterConfig.enabled && this.video) {
245
+ this.setupChapterManager();
246
+ }
201
247
 
202
248
  // Initialize paywall controller if provided
203
249
  try {
@@ -326,6 +372,10 @@ export class WebPlayer extends BasePlayer {
326
372
  this.updateTime(t);
327
373
  // Enforce free preview gate on local playback
328
374
  this.enforceFreePreviewGate(t);
375
+ // Process chapter time updates
376
+ if (this.coreChapterManager) {
377
+ this.coreChapterManager.processTimeUpdate(t);
378
+ }
329
379
  });
330
380
 
331
381
  this.video.addEventListener('progress', () => {
@@ -2439,6 +2489,201 @@ export class WebPlayer extends BasePlayer {
2439
2489
  transform: translateX(-50%) translateY(0);
2440
2490
  }
2441
2491
 
2492
+ /* Chapter Markers */
2493
+ .uvf-chapter-marker {
2494
+ position: absolute;
2495
+ top: 0;
2496
+ width: 2px;
2497
+ height: 100%;
2498
+ background: rgba(255, 255, 255, 0.6);
2499
+ z-index: 4;
2500
+ cursor: pointer;
2501
+ transition: all 0.2s ease;
2502
+ }
2503
+
2504
+ .uvf-chapter-marker:hover {
2505
+ width: 3px;
2506
+ box-shadow: 0 0 8px rgba(255, 255, 255, 0.8);
2507
+ }
2508
+
2509
+ .uvf-chapter-marker-intro {
2510
+ background: var(--uvf-accent-1, #ff5722);
2511
+ }
2512
+
2513
+ .uvf-chapter-marker-recap {
2514
+ background: #ffc107;
2515
+ }
2516
+
2517
+ .uvf-chapter-marker-credits {
2518
+ background: #9c27b0;
2519
+ }
2520
+
2521
+ .uvf-chapter-marker-ad {
2522
+ background: #f44336;
2523
+ }
2524
+
2525
+ /* Skip Button Styles */
2526
+ .uvf-skip-button {
2527
+ position: absolute;
2528
+ background: rgba(0, 0, 0, 0.8);
2529
+ color: white;
2530
+ border: 2px solid rgba(255, 255, 255, 0.3);
2531
+ border-radius: 8px;
2532
+ padding: 12px 24px;
2533
+ font-size: 16px;
2534
+ font-weight: 600;
2535
+ cursor: pointer;
2536
+ backdrop-filter: blur(10px);
2537
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
2538
+ z-index: 1000;
2539
+ user-select: none;
2540
+
2541
+ /* Default hidden state */
2542
+ opacity: 0;
2543
+ transform: translateX(100px) scale(0.9);
2544
+ pointer-events: none;
2545
+ }
2546
+
2547
+ .uvf-skip-button.visible {
2548
+ opacity: 1;
2549
+ transform: translateX(0) scale(1);
2550
+ pointer-events: auto;
2551
+ }
2552
+
2553
+ .uvf-skip-button:hover {
2554
+ background: var(--uvf-accent-1, #ff5722);
2555
+ border-color: var(--uvf-accent-1, #ff5722);
2556
+ transform: translateX(0) scale(1.05);
2557
+ box-shadow: 0 4px 20px rgba(255, 87, 34, 0.4);
2558
+ }
2559
+
2560
+ .uvf-skip-button:active {
2561
+ transform: translateX(0) scale(0.95);
2562
+ transition: all 0.1s ease;
2563
+ }
2564
+
2565
+ /* Skip button positioning */
2566
+ .uvf-skip-button-bottom-right {
2567
+ bottom: 100px;
2568
+ right: 30px;
2569
+ }
2570
+
2571
+ .uvf-skip-button-bottom-left {
2572
+ bottom: 100px;
2573
+ left: 30px;
2574
+ transform: translateX(-100px) scale(0.9);
2575
+ }
2576
+
2577
+ .uvf-skip-button-bottom-left.visible {
2578
+ transform: translateX(0) scale(1);
2579
+ }
2580
+
2581
+ .uvf-skip-button-bottom-left:hover {
2582
+ transform: translateX(0) scale(1.05);
2583
+ }
2584
+
2585
+ .uvf-skip-button-bottom-left:active {
2586
+ transform: translateX(0) scale(0.95);
2587
+ }
2588
+
2589
+ .uvf-skip-button-top-right {
2590
+ top: 30px;
2591
+ right: 30px;
2592
+ }
2593
+
2594
+ .uvf-skip-button-top-left {
2595
+ top: 30px;
2596
+ left: 30px;
2597
+ transform: translateX(-100px) scale(0.9);
2598
+ }
2599
+
2600
+ .uvf-skip-button-top-left.visible {
2601
+ transform: translateX(0) scale(1);
2602
+ }
2603
+
2604
+ .uvf-skip-button-top-left:hover {
2605
+ transform: translateX(0) scale(1.05);
2606
+ }
2607
+
2608
+ .uvf-skip-button-top-left:active {
2609
+ transform: translateX(0) scale(0.95);
2610
+ }
2611
+
2612
+ /* Skip button segment type styling */
2613
+ .uvf-skip-intro {
2614
+ border-color: var(--uvf-accent-1, #ff5722);
2615
+ }
2616
+
2617
+ .uvf-skip-intro:hover {
2618
+ background: var(--uvf-accent-1, #ff5722);
2619
+ border-color: var(--uvf-accent-1, #ff5722);
2620
+ }
2621
+
2622
+ .uvf-skip-recap {
2623
+ border-color: #ffc107;
2624
+ }
2625
+
2626
+ .uvf-skip-recap:hover {
2627
+ background: #ffc107;
2628
+ border-color: #ffc107;
2629
+ color: #000;
2630
+ }
2631
+
2632
+ .uvf-skip-credits {
2633
+ border-color: #9c27b0;
2634
+ }
2635
+
2636
+ .uvf-skip-credits:hover {
2637
+ background: #9c27b0;
2638
+ border-color: #9c27b0;
2639
+ }
2640
+
2641
+ .uvf-skip-ad {
2642
+ border-color: #f44336;
2643
+ }
2644
+
2645
+ .uvf-skip-ad:hover {
2646
+ background: #f44336;
2647
+ border-color: #f44336;
2648
+ }
2649
+
2650
+ /* Auto-skip countdown styling */
2651
+ .uvf-skip-button.auto-skip {
2652
+ position: relative;
2653
+ overflow: hidden;
2654
+ border-color: var(--uvf-accent-1, #ff5722);
2655
+ animation: uvf-skip-pulse 2s infinite;
2656
+ }
2657
+
2658
+ .uvf-skip-button.auto-skip::before {
2659
+ content: '';
2660
+ position: absolute;
2661
+ bottom: 0;
2662
+ left: 0;
2663
+ height: 3px;
2664
+ background: var(--uvf-accent-1, #ff5722);
2665
+ width: 0%;
2666
+ transition: none;
2667
+ z-index: -1;
2668
+ }
2669
+
2670
+ .uvf-skip-button.auto-skip.countdown::before {
2671
+ width: 100%;
2672
+ transition: width linear;
2673
+ }
2674
+
2675
+ @keyframes uvf-skip-pulse {
2676
+ 0% {
2677
+ box-shadow: 0 0 0 0 rgba(255, 87, 34, 0.4);
2678
+ }
2679
+ 50% {
2680
+ box-shadow: 0 0 0 8px rgba(255, 87, 34, 0.1);
2681
+ }
2682
+ 100% {
2683
+ box-shadow: 0 0 0 0 rgba(255, 87, 34, 0);
2684
+ }
2685
+ }
2686
+
2442
2687
 
2443
2688
 
2444
2689
  /* Mobile responsive design with enhanced touch targets */
@@ -2463,6 +2708,42 @@ export class WebPlayer extends BasePlayer {
2463
2708
  top: 2.5px; /* Center on the 5px mobile hover progress bar */
2464
2709
  }
2465
2710
 
2711
+ /* Mobile skip button adjustments */
2712
+ .uvf-skip-button {
2713
+ padding: 10px 20px;
2714
+ font-size: 14px;
2715
+ border-radius: 6px;
2716
+ }
2717
+
2718
+ .uvf-skip-button-bottom-right {
2719
+ bottom: 80px;
2720
+ right: 20px;
2721
+ }
2722
+
2723
+ .uvf-skip-button-bottom-left {
2724
+ bottom: 80px;
2725
+ left: 20px;
2726
+ }
2727
+
2728
+ .uvf-skip-button-top-right {
2729
+ top: 20px;
2730
+ right: 20px;
2731
+ }
2732
+
2733
+ .uvf-skip-button-top-left {
2734
+ top: 20px;
2735
+ left: 20px;
2736
+ }
2737
+
2738
+ /* Mobile chapter markers */
2739
+ .uvf-chapter-marker {
2740
+ width: 3px; /* Thicker on mobile for better touch */
2741
+ }
2742
+
2743
+ .uvf-chapter-marker:hover {
2744
+ width: 4px;
2745
+ }
2746
+
2466
2747
  }
2467
2748
 
2468
2749
  /* Controls Row */
@@ -6619,6 +6900,299 @@ export class WebPlayer extends BasePlayer {
6619
6900
  }
6620
6901
  }
6621
6902
 
6903
+ /**
6904
+ * Setup chapter manager for skip functionality
6905
+ */
6906
+ private setupChapterManager(): void {
6907
+ if (!this.video || !this.playerWrapper) {
6908
+ this.debugWarn('Cannot setup chapter manager: video or wrapper not available');
6909
+ return;
6910
+ }
6911
+
6912
+ try {
6913
+ // Initialize the web-specific chapter manager (for UI controls)
6914
+ this.chapterManager = new ChapterManager(
6915
+ this.playerWrapper,
6916
+ this.video,
6917
+ this.chapterConfig
6918
+ );
6919
+
6920
+ // Initialize the core chapter manager (for basic chapter functionality)
6921
+ const coreChapterConfig = {
6922
+ enabled: this.chapterConfig.enabled,
6923
+ chapters: this.convertToChapters(this.chapterConfig.data),
6924
+ segments: this.convertToChapterSegments(this.chapterConfig.data),
6925
+ dataUrl: this.chapterConfig.dataUrl,
6926
+ autoSkip: this.chapterConfig.userPreferences?.autoSkipIntro || false,
6927
+ onChapterChange: (chapter: Chapter | null) => {
6928
+ this.debugLog('Core chapter changed:', chapter?.title || 'none');
6929
+ this.emit('chapterchange', chapter);
6930
+ },
6931
+ onSegmentEntered: (segment: ChapterSegment) => {
6932
+ this.debugLog('Core segment entered:', segment.title);
6933
+ this.emit('segmententered', segment);
6934
+ },
6935
+ onSegmentExited: (segment: ChapterSegment) => {
6936
+ this.debugLog('Core segment exited:', segment.title);
6937
+ this.emit('segmentexited', segment);
6938
+ },
6939
+ onSegmentSkipped: (segment: ChapterSegment) => {
6940
+ this.debugLog('Core segment skipped:', segment.title);
6941
+ this.emit('segmentskipped', segment);
6942
+ }
6943
+ };
6944
+
6945
+ this.coreChapterManager = new CoreChapterManager(coreChapterConfig);
6946
+
6947
+ // Initialize the core chapter manager
6948
+ this.coreChapterManager.initialize();
6949
+
6950
+ // Set up event listeners for web chapter events
6951
+ this.chapterManager.on('segmentEntered', (data) => {
6952
+ this.debugLog('Entered segment:', data.segment.type, data.segment.title);
6953
+ this.emit('chapterSegmentEntered', data);
6954
+ });
6955
+
6956
+ this.chapterManager.on('segmentSkipped', (data) => {
6957
+ this.debugLog('Skipped segment:', data.fromSegment.type, 'to', data.toSegment?.type || 'end');
6958
+ this.emit('chapterSegmentSkipped', data);
6959
+ });
6960
+
6961
+ this.chapterManager.on('skipButtonShown', (data) => {
6962
+ this.debugLog('Skip button shown for:', data.segment.type);
6963
+ this.emit('chapterSkipButtonShown', data);
6964
+ });
6965
+
6966
+ this.chapterManager.on('skipButtonHidden', (data) => {
6967
+ this.debugLog('Skip button hidden for:', data.segment.type, 'reason:', data.reason);
6968
+ this.emit('chapterSkipButtonHidden', data);
6969
+ });
6970
+
6971
+ this.chapterManager.on('chaptersLoaded', (data) => {
6972
+ this.debugLog('Chapters loaded:', data.segmentCount, 'segments');
6973
+ this.emit('chaptersLoaded', data);
6974
+ });
6975
+
6976
+ this.chapterManager.on('chaptersLoadError', (data) => {
6977
+ this.debugError('Failed to load chapters:', data.error.message);
6978
+ this.emit('chaptersLoadError', data);
6979
+ });
6980
+
6981
+ this.debugLog('Chapter managers initialized successfully');
6982
+ } catch (error) {
6983
+ this.debugError('Failed to initialize chapter managers:', error);
6984
+ }
6985
+ }
6986
+
6987
+ /**
6988
+ * Convert web chapter data to core Chapter format
6989
+ */
6990
+ private convertToChapters(webChapterData: any): Chapter[] {
6991
+ if (!webChapterData || !webChapterData.segments) {
6992
+ return [];
6993
+ }
6994
+
6995
+ return webChapterData.segments
6996
+ .filter((segment: any) => segment.type === 'content')
6997
+ .map((segment: any, index: number) => ({
6998
+ id: segment.id || `chapter-${index}`,
6999
+ title: segment.title || `Chapter ${index + 1}`,
7000
+ startTime: segment.startTime,
7001
+ endTime: segment.endTime,
7002
+ thumbnail: segment.thumbnail,
7003
+ description: segment.description,
7004
+ metadata: segment.metadata || {}
7005
+ }));
7006
+ }
7007
+
7008
+ /**
7009
+ * Convert web chapter data to core ChapterSegment format
7010
+ */
7011
+ private convertToChapterSegments(webChapterData: any): ChapterSegment[] {
7012
+ if (!webChapterData || !webChapterData.segments) {
7013
+ return [];
7014
+ }
7015
+
7016
+ return webChapterData.segments
7017
+ .filter((segment: any) => segment.type !== 'content')
7018
+ .map((segment: any) => ({
7019
+ id: segment.id,
7020
+ startTime: segment.startTime,
7021
+ endTime: segment.endTime,
7022
+ category: segment.type,
7023
+ action: this.mapSegmentAction(segment.type),
7024
+ title: segment.title,
7025
+ description: segment.description
7026
+ }));
7027
+ }
7028
+
7029
+ /**
7030
+ * Map web segment types to core segment actions
7031
+ */
7032
+ private mapSegmentAction(segmentType: string): 'skip' | 'mute' | 'warn' {
7033
+ switch (segmentType) {
7034
+ case 'intro':
7035
+ case 'recap':
7036
+ case 'credits':
7037
+ case 'sponsor':
7038
+ return 'skip';
7039
+ case 'offensive':
7040
+ return 'mute';
7041
+ default:
7042
+ return 'warn';
7043
+ }
7044
+ }
7045
+
7046
+ /**
7047
+ * Load chapters data
7048
+ */
7049
+ public async loadChapters(chapters: VideoChapters): Promise<void> {
7050
+ if (!this.chapterManager) {
7051
+ throw new Error('Chapter manager not initialized. Enable chapters in config first.');
7052
+ }
7053
+
7054
+ try {
7055
+ await this.chapterManager.loadChapters(chapters);
7056
+ this.debugLog('Chapters loaded successfully');
7057
+ } catch (error) {
7058
+ this.debugError('Failed to load chapters:', error);
7059
+ throw error;
7060
+ }
7061
+ }
7062
+
7063
+ /**
7064
+ * Load chapters from URL
7065
+ */
7066
+ public async loadChaptersFromUrl(url: string): Promise<void> {
7067
+ if (!this.chapterManager) {
7068
+ throw new Error('Chapter manager not initialized. Enable chapters in config first.');
7069
+ }
7070
+
7071
+ try {
7072
+ await this.chapterManager.loadChaptersFromUrl(url);
7073
+ this.debugLog('Chapters loaded from URL successfully');
7074
+ } catch (error) {
7075
+ this.debugError('Failed to load chapters from URL:', error);
7076
+ throw error;
7077
+ }
7078
+ }
7079
+
7080
+ /**
7081
+ * Get current video segment
7082
+ */
7083
+ public getCurrentSegment(): VideoSegment | null {
7084
+ if (!this.chapterManager || !this.video) {
7085
+ return null;
7086
+ }
7087
+
7088
+ return this.chapterManager.getCurrentSegment(this.video.currentTime);
7089
+ }
7090
+
7091
+ /**
7092
+ * Skip to specific segment by ID
7093
+ */
7094
+ public skipToSegment(segmentId: string): void {
7095
+ if (!this.chapterManager) {
7096
+ this.debugWarn('Cannot skip segment: chapter manager not initialized');
7097
+ return;
7098
+ }
7099
+
7100
+ this.chapterManager.skipToSegment(segmentId);
7101
+ }
7102
+
7103
+ /**
7104
+ * Get all video segments
7105
+ */
7106
+ public getSegments(): VideoSegment[] {
7107
+ if (!this.chapterManager) {
7108
+ return [];
7109
+ }
7110
+
7111
+ return this.chapterManager.getSegments();
7112
+ }
7113
+
7114
+ /**
7115
+ * Update chapter configuration
7116
+ */
7117
+ public updateChapterConfig(newConfig: Partial<ChapterConfig>): void {
7118
+ this.chapterConfig = { ...this.chapterConfig, ...newConfig };
7119
+
7120
+ if (this.chapterManager) {
7121
+ this.chapterManager.updateConfig(this.chapterConfig);
7122
+ }
7123
+ }
7124
+
7125
+ /**
7126
+ * Check if chapters are loaded
7127
+ */
7128
+ public hasChapters(): boolean {
7129
+ return this.chapterManager?.hasChapters() || false;
7130
+ }
7131
+
7132
+ /**
7133
+ * Get chapter data
7134
+ */
7135
+ public getChapters(): VideoChapters | null {
7136
+ return this.chapterManager?.getChapters() || null;
7137
+ }
7138
+
7139
+ /**
7140
+ * Get core chapters (Chapter format)
7141
+ */
7142
+ public getCoreChapters(): Chapter[] {
7143
+ return this.coreChapterManager?.getChapters() || [];
7144
+ }
7145
+
7146
+ /**
7147
+ * Get core chapter segments (ChapterSegment format)
7148
+ */
7149
+ public getCoreSegments(): ChapterSegment[] {
7150
+ return this.coreChapterManager?.getSegments() || [];
7151
+ }
7152
+
7153
+ /**
7154
+ * Get current chapter info from core manager
7155
+ */
7156
+ public getCurrentChapterInfo(): Chapter | null {
7157
+ return this.coreChapterManager?.getCurrentChapterInfo() || null;
7158
+ }
7159
+
7160
+ /**
7161
+ * Seek to chapter by ID (core chapter functionality)
7162
+ */
7163
+ public seekToChapter(chapterId: string): void {
7164
+ if (!this.coreChapterManager || !this.video) {
7165
+ this.debugWarn('Cannot seek to chapter: core chapter manager or video not available');
7166
+ return;
7167
+ }
7168
+
7169
+ const chapter = this.coreChapterManager.seekToChapter(chapterId);
7170
+ if (chapter) {
7171
+ this.video.currentTime = chapter.startTime;
7172
+ this.debugLog('Seeked to chapter:', chapter.title);
7173
+ }
7174
+ }
7175
+
7176
+ /**
7177
+ * Get next chapter from current time
7178
+ */
7179
+ public getNextChapter(): Chapter | null {
7180
+ if (!this.coreChapterManager || !this.video) {
7181
+ return null;
7182
+ }
7183
+ return this.coreChapterManager.getNextChapter(this.video.currentTime);
7184
+ }
7185
+
7186
+ /**
7187
+ * Get previous chapter from current time
7188
+ */
7189
+ public getPreviousChapter(): Chapter | null {
7190
+ if (!this.coreChapterManager || !this.video) {
7191
+ return null;
7192
+ }
7193
+ return this.coreChapterManager.getPreviousChapter(this.video.currentTime);
7194
+ }
7195
+
6622
7196
  // Theme API: set CSS variables on the wrapper to apply dynamic colors
6623
7197
  public setTheme(theme: any): void {
6624
7198
  const wrapper = this.playerWrapper;
@@ -7973,6 +8547,16 @@ export class WebPlayer extends BasePlayer {
7973
8547
  this.paywallController = null;
7974
8548
  }
7975
8549
 
8550
+ // Destroy chapter managers
8551
+ if (this.chapterManager && typeof this.chapterManager.destroy === 'function') {
8552
+ this.chapterManager.destroy();
8553
+ this.chapterManager = null;
8554
+ }
8555
+ if (this.coreChapterManager) {
8556
+ this.coreChapterManager.destroy();
8557
+ this.coreChapterManager = null;
8558
+ }
8559
+
7976
8560
  if (this.video) {
7977
8561
  this.video.pause();
7978
8562
  this.video.removeAttribute('src');
@@ -1,5 +1,5 @@
1
1
  import { WebPlayer } from '../WebPlayer';
2
- import { VideoSource } from '@unified-video/core';
2
+ import { VideoSource } from '../../core/src';
3
3
 
4
4
  describe('WebPlayer', () => {
5
5
  let player: WebPlayer;
@@ -256,4 +256,4 @@ describe('EPG Integration Tests', () => {
256
256
  expect(player.isEPGButtonVisible()).toBe(false);
257
257
  });
258
258
  });
259
- });
259
+ });