unified-video-framework 1.4.435 → 1.4.437

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.
@@ -5,9 +5,33 @@ import { DRMType as DRMTypeEnum } from "../../../../core/dist/index.js";
5
5
  export class WidevineDRM extends BaseDRM {
6
6
  async initialize() {
7
7
  this.log('Initializing Widevine DRM...');
8
+ this.log('Requested robustness - Video:', this.config.videoRobustness || 'SW_SECURE_CRYPTO', 'Audio:', this.config.audioRobustness || 'SW_SECURE_CRYPTO');
8
9
  try {
9
10
  const keySystemAccess = await navigator.requestMediaKeySystemAccess(this.getKeySystem(), this.getKeySystemConfiguration());
10
11
  this.log('Widevine key system access granted');
12
+
13
+ // Log the actual configuration that was accepted
14
+ const actualConfig = keySystemAccess.getConfiguration();
15
+ this.log('✅ GRANTED Configuration:', {
16
+ videoRobustness: actualConfig.videoCapabilities?.[0]?.robustness || 'unknown',
17
+ audioRobustness: actualConfig.audioCapabilities?.[0]?.robustness || 'unknown',
18
+ distinctiveIdentifier: actualConfig.distinctiveIdentifier,
19
+ persistentState: actualConfig.persistentState
20
+ });
21
+
22
+ // Check if fallback happened
23
+ const requestedVideoRobustness = this.config.videoRobustness || 'SW_SECURE_CRYPTO';
24
+ const grantedVideoRobustness = actualConfig.videoCapabilities?.[0]?.robustness;
25
+
26
+ if (requestedVideoRobustness !== grantedVideoRobustness) {
27
+ this.log('⚠️ WARNING: Browser fell back from', requestedVideoRobustness, 'to', grantedVideoRobustness);
28
+ this.log('⚠️ Screenshot blocking may not work - system does not support hardware overlay');
29
+ } else if (requestedVideoRobustness === 'HW_SECURE_DECODE' && grantedVideoRobustness === 'HW_SECURE_DECODE') {
30
+ this.log('✅ SUCCESS: Hardware overlay protection active - screenshots should show black screen');
31
+ } else {
32
+ this.log('ℹ️ Using software DRM - screenshots will be visible');
33
+ }
34
+
11
35
  this.mediaKeys = await keySystemAccess.createMediaKeys();
12
36
  }
13
37
  catch (error) {
@@ -22,12 +46,15 @@ export class WidevineDRM extends BaseDRM {
22
46
  getHLSConfig() {
23
47
  this.log('Generating HLS.js config for Widevine');
24
48
  const licenseUrl = this.config.licenseUrl || this.config.widevineOptions?.licenseUrl;
49
+ // Use videoRobustness from config if provided, otherwise fallback to SW_SECURE_CRYPTO
50
+ const robustness = this.config.videoRobustness || 'SW_SECURE_CRYPTO';
51
+ this.log(`Using video robustness level: ${robustness}`);
25
52
  return {
26
53
  emeEnabled: true,
27
54
  drmSystems: {
28
55
  [this.getKeySystem()]: {
29
56
  licenseUrl: licenseUrl,
30
- robustness: 'SW_SECURE_CRYPTO',
57
+ robustness: robustness,
31
58
  },
32
59
  },
33
60
  requestMediaKeySystemAccessFunc: navigator.requestMediaKeySystemAccess.bind(navigator),
@@ -70,12 +97,46 @@ export class WidevineDRM extends BaseDRM {
70
97
  return protectionData;
71
98
  }
72
99
  getKeySystemConfiguration() {
73
- return [{
100
+ // Use robustness from config or fallback to SW_SECURE_CRYPTO (L3)
101
+ const videoRobustness = this.config.videoRobustness || 'SW_SECURE_CRYPTO';
102
+ const audioRobustness = this.config.audioRobustness || 'SW_SECURE_CRYPTO';
103
+
104
+ this.log(`Key system config - Video: ${videoRobustness}, Audio: ${audioRobustness}`);
105
+
106
+ // Create configuration with multiple robustness fallbacks
107
+ const configs = [];
108
+
109
+ // Try requested robustness first
110
+ configs.push({
111
+ initDataTypes: ['cenc'],
112
+ audioCapabilities: [{
113
+ contentType: 'audio/mp4; codecs="mp4a.40.2"',
114
+ robustness: audioRobustness,
115
+ }],
116
+ videoCapabilities: [
117
+ {
118
+ contentType: 'video/mp4; codecs="avc1.42E01E"',
119
+ robustness: videoRobustness,
120
+ },
121
+ {
122
+ contentType: 'video/mp4; codecs="avc1.4d401f"',
123
+ robustness: videoRobustness,
124
+ },
125
+ ],
126
+ distinctiveIdentifier: 'optional',
127
+ persistentState: 'optional',
128
+ sessionTypes: ['temporary'],
129
+ });
130
+
131
+ // Fallback to SW_SECURE_CRYPTO if hardware not available
132
+ if (videoRobustness !== 'SW_SECURE_CRYPTO') {
133
+ this.log('Adding fallback configuration with SW_SECURE_CRYPTO');
134
+ configs.push({
74
135
  initDataTypes: ['cenc'],
75
136
  audioCapabilities: [{
76
- contentType: 'audio/mp4; codecs="mp4a.40.2"',
77
- robustness: 'SW_SECURE_CRYPTO',
78
- }],
137
+ contentType: 'audio/mp4; codecs="mp4a.40.2"',
138
+ robustness: 'SW_SECURE_CRYPTO',
139
+ }],
79
140
  videoCapabilities: [
80
141
  {
81
142
  contentType: 'video/mp4; codecs="avc1.42E01E"',
@@ -89,7 +150,10 @@ export class WidevineDRM extends BaseDRM {
89
150
  distinctiveIdentifier: 'not-allowed',
90
151
  persistentState: 'not-allowed',
91
152
  sessionTypes: ['temporary'],
92
- }];
153
+ });
154
+ }
155
+
156
+ return configs;
93
157
  }
94
158
  }
95
159
  //# sourceMappingURL=WidevineDRM.js.map
@@ -2,5 +2,6 @@ export * from '../../core/dist/index';
2
2
  export { WebPlayer } from './WebPlayer';
3
3
  export { WebPlayerView } from './react/WebPlayerView';
4
4
  export * from './react/EPG';
5
+ export type { FlashNewsTickerConfig, FlashNewsTickerItem, FlashNewsTickerAPI, TickerDisplayConfig, TickerStyleVariant, BroadcastTheme, BroadcastStyleConfig } from './react/types/FlashNewsTickerTypes';
5
6
  export declare const VERSION = "1.0.0";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,cAAc,aAAa,CAAC;AAG5B,eAAO,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,cAAc,aAAa,CAAC;AAG5B,YAAY,EACV,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACrB,MAAM,oCAAoC,CAAC;AAG5C,eAAO,MAAM,OAAO,UAAU,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,cAAc,aAAa,CAAC;AAG5B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,cAAc,aAAa,CAAC;AAc5B,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
@@ -1,3 +1,19 @@
1
+ export type TickerStyleVariant = 'simple' | 'broadcast';
2
+ export type BroadcastTheme = 'breaking-red' | 'breaking-blue' | 'alert-red' | 'news-blue' | 'custom';
3
+ export interface BroadcastStyleConfig {
4
+ theme?: BroadcastTheme;
5
+ headerText?: string;
6
+ showGlobe?: boolean;
7
+ showLiveBadge?: boolean;
8
+ headerColor?: string;
9
+ headerTextColor?: string;
10
+ bodyColor?: string;
11
+ headerHeight?: number;
12
+ headerFontSize?: number;
13
+ subHeaderText?: string;
14
+ animateGlobe?: boolean;
15
+ pulseLiveBadge?: boolean;
16
+ }
1
17
  export interface FlashNewsTickerItem {
2
18
  id: string;
3
19
  text: string;
@@ -18,11 +34,15 @@ export interface TickerDisplayConfig {
18
34
  gap?: number;
19
35
  separator?: string;
20
36
  offset?: number;
37
+ styleVariant?: TickerStyleVariant;
38
+ broadcastStyle?: BroadcastStyleConfig;
21
39
  }
22
40
  export interface FlashNewsTickerConfig {
23
41
  enabled?: boolean;
24
42
  items?: FlashNewsTickerItem[];
25
43
  position?: 'top' | 'bottom' | 'both';
44
+ styleVariant?: TickerStyleVariant;
45
+ broadcastStyle?: BroadcastStyleConfig;
26
46
  topConfig?: TickerDisplayConfig;
27
47
  bottomConfig?: TickerDisplayConfig;
28
48
  height?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"FlashNewsTickerTypes.d.ts","sourceRoot":"","sources":["../../../src/react/types/FlashNewsTickerTypes.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,mBAAmB;IAElC,EAAE,EAAE,MAAM,CAAC;IAGX,IAAI,EAAE,MAAM,CAAC;IAOb,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAKD,MAAM,WAAW,mBAAmB;IAElC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAK7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAK7B,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,GAAG,CAAC,EAAE,MAAM,CAAC;IAGb,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,MAAM,WAAW,qBAAqB;IAEpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAGlB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAG9B,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAGrC,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAGhC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAKnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,eAAe,CAAC,EAAE,MAAM,CAAC;IAGzB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAK7B,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,GAAG,CAAC,EAAE,MAAM,CAAC;IAGb,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,SAAS,CAAC,EAAE,MAAM,CAAC;IAKnB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAKD,MAAM,WAAW,kBAAkB;IAEjC,IAAI,EAAE,MAAM,IAAI,CAAC;IAGjB,IAAI,EAAE,MAAM,IAAI,CAAC;IAGjB,SAAS,EAAE,MAAM,OAAO,CAAC;IAGzB,WAAW,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,IAAI,CAAC;IAGpD,OAAO,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAG7C,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAGrC,UAAU,EAAE,MAAM,IAAI,CAAC;IAGvB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;IAG/D,KAAK,EAAE,MAAM,IAAI,CAAC;IAGlB,MAAM,EAAE,MAAM,IAAI,CAAC;IAGnB,QAAQ,EAAE,MAAM,OAAO,CAAC;CACzB"}
1
+ {"version":3,"file":"FlashNewsTickerTypes.d.ts","sourceRoot":"","sources":["../../../src/react/types/FlashNewsTickerTypes.ts"],"names":[],"mappings":"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"}
@@ -20,7 +20,7 @@ import {
20
20
  VideoSegment,
21
21
  ChapterEvents
22
22
  } from './chapters/types/ChapterTypes';
23
- import type { FlashNewsTickerConfig } from './react/types/FlashNewsTickerTypes';
23
+ import type { FlashNewsTickerConfig, BroadcastStyleConfig, BroadcastTheme, TickerStyleVariant } from './react/types/FlashNewsTickerTypes';
24
24
  import YouTubeExtractor from './utils/YouTubeExtractor';
25
25
  import { DRMManager, DRMErrorHandler } from './drm';
26
26
  import type { DRMInitResult, DRMError } from './drm';
@@ -2468,8 +2468,18 @@ export class WebPlayer extends BasePlayer {
2468
2468
  }
2469
2469
 
2470
2470
  private createTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
2471
+ // Route to broadcast style if configured
2472
+ if (config.styleVariant === 'broadcast') {
2473
+ return this.createBroadcastTickerElement(config, position);
2474
+ }
2475
+
2476
+ // Simple style (original implementation)
2477
+ return this.createSimpleTickerElement(config, position);
2478
+ }
2479
+
2480
+ private createSimpleTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
2471
2481
  const ticker = document.createElement('div');
2472
- ticker.className = `uvf-flash-ticker ticker-${position}`;
2482
+ ticker.className = `uvf-flash-ticker ticker-${position} ticker-simple`;
2473
2483
 
2474
2484
  const bottomOffset = config.bottomOffset || 0;
2475
2485
  const topOffset = config.topOffset || 0;
@@ -2567,6 +2577,262 @@ export class WebPlayer extends BasePlayer {
2567
2577
  ticker.appendChild(track);
2568
2578
 
2569
2579
  // Add animation keyframes to document if not exists
2580
+ this.ensureTickerAnimations();
2581
+
2582
+ return ticker;
2583
+ }
2584
+
2585
+ private createBroadcastTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
2586
+ const broadcastStyle = config.broadcastStyle || {};
2587
+ const theme = broadcastStyle.theme || 'breaking-red';
2588
+
2589
+ // Get theme colors
2590
+ const themeColors = this.getBroadcastThemeColors(theme, broadcastStyle);
2591
+
2592
+ const ticker = document.createElement('div');
2593
+ ticker.className = `uvf-flash-ticker ticker-${position} ticker-broadcast`;
2594
+
2595
+ const bottomOffset = config.bottomOffset || 0;
2596
+ const topOffset = config.topOffset || 0;
2597
+ const headerHeight = broadcastStyle.headerHeight || 28;
2598
+ const bodyHeight = config.height || 36;
2599
+ const totalHeight = headerHeight + bodyHeight;
2600
+
2601
+ ticker.style.cssText = `
2602
+ position: absolute;
2603
+ left: 0;
2604
+ right: 0;
2605
+ height: ${totalHeight}px;
2606
+ ${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
2607
+ overflow: hidden;
2608
+ pointer-events: none;
2609
+ display: flex;
2610
+ flex-direction: column;
2611
+ `;
2612
+
2613
+ // Create header row (BREAKING NEWS with globe and LIVE badge)
2614
+ const header = document.createElement('div');
2615
+ header.className = 'uvf-ticker-header';
2616
+ header.style.cssText = `
2617
+ display: flex;
2618
+ align-items: center;
2619
+ height: ${headerHeight}px;
2620
+ background: ${themeColors.headerBg};
2621
+ padding: 0 12px;
2622
+ position: relative;
2623
+ `;
2624
+
2625
+ // Add globe graphic if enabled
2626
+ if (broadcastStyle.showGlobe !== false) {
2627
+ const globe = this.createGlobeElement(broadcastStyle.animateGlobe !== false);
2628
+ header.appendChild(globe);
2629
+ }
2630
+
2631
+ // Add header text (BREAKING NEWS)
2632
+ const headerText = document.createElement('span');
2633
+ headerText.className = 'uvf-ticker-header-text';
2634
+ headerText.textContent = broadcastStyle.headerText || 'BREAKING NEWS';
2635
+ headerText.style.cssText = `
2636
+ color: ${broadcastStyle.headerTextColor || '#ffffff'};
2637
+ font-size: ${broadcastStyle.headerFontSize || 16}px;
2638
+ font-weight: 800;
2639
+ text-transform: uppercase;
2640
+ letter-spacing: 1px;
2641
+ margin-left: ${broadcastStyle.showGlobe !== false ? '8px' : '0'};
2642
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
2643
+ `;
2644
+ header.appendChild(headerText);
2645
+
2646
+ // Add LIVE badge if enabled
2647
+ if (broadcastStyle.showLiveBadge !== false) {
2648
+ const liveBadge = this.createLiveBadgeElement(broadcastStyle.pulseLiveBadge !== false);
2649
+ header.appendChild(liveBadge);
2650
+ }
2651
+
2652
+ // Create body row (scrolling ticker)
2653
+ const body = document.createElement('div');
2654
+ body.className = 'uvf-ticker-body';
2655
+ body.style.cssText = `
2656
+ display: flex;
2657
+ align-items: center;
2658
+ height: ${bodyHeight}px;
2659
+ background: ${themeColors.bodyBg};
2660
+ overflow: hidden;
2661
+ position: relative;
2662
+ `;
2663
+
2664
+ // Create scrolling track
2665
+ const track = document.createElement('div');
2666
+ track.className = 'uvf-ticker-track';
2667
+
2668
+ const duration = this.calculateTickerDuration(config);
2669
+ track.style.cssText = `
2670
+ display: flex;
2671
+ white-space: nowrap;
2672
+ animation: ticker-scroll ${duration}s linear infinite;
2673
+ will-change: transform;
2674
+ padding-left: 100%;
2675
+ `;
2676
+
2677
+ // Calculate responsive font size
2678
+ const containerWidth = this.container?.offsetWidth || 1920;
2679
+ const baseFontSize = config.fontSize || 14;
2680
+ let responsiveFontSize = baseFontSize;
2681
+ if (containerWidth < 768) {
2682
+ responsiveFontSize = Math.max(baseFontSize * 0.8, 12);
2683
+ } else if (containerWidth < 1280) {
2684
+ responsiveFontSize = Math.max(baseFontSize * 0.9, 13);
2685
+ }
2686
+
2687
+ // Render items
2688
+ const renderItems = () => {
2689
+ if (!config.items) return;
2690
+ config.items.forEach((item) => {
2691
+ const span = document.createElement('span');
2692
+ if (item.html) {
2693
+ span.innerHTML = item.html;
2694
+ } else {
2695
+ span.textContent = item.text;
2696
+ }
2697
+ span.style.cssText = `
2698
+ color: ${config.textColor || '#ffffff'};
2699
+ font-size: ${responsiveFontSize}px;
2700
+ font-weight: ${config.fontWeight || 600};
2701
+ margin-right: ${config.gap || 100}px;
2702
+ display: inline-flex;
2703
+ align-items: center;
2704
+ `;
2705
+ track.appendChild(span);
2706
+
2707
+ if (config.separator) {
2708
+ const sep = document.createElement('span');
2709
+ sep.textContent = config.separator;
2710
+ sep.style.cssText = `
2711
+ color: ${config.textColor || '#ffffff'};
2712
+ font-size: ${responsiveFontSize}px;
2713
+ opacity: 0.5;
2714
+ margin: 0 8px;
2715
+ `;
2716
+ track.appendChild(sep);
2717
+ }
2718
+ });
2719
+ };
2720
+
2721
+ for (let i = 0; i < 10; i++) {
2722
+ renderItems();
2723
+ }
2724
+
2725
+ body.appendChild(track);
2726
+ ticker.appendChild(header);
2727
+ ticker.appendChild(body);
2728
+
2729
+ // Add all animation keyframes
2730
+ this.ensureTickerAnimations();
2731
+
2732
+ return ticker;
2733
+ }
2734
+
2735
+ private getBroadcastThemeColors(theme: BroadcastTheme, broadcastStyle: BroadcastStyleConfig): { headerBg: string; bodyBg: string } {
2736
+ switch (theme) {
2737
+ case 'breaking-red':
2738
+ return {
2739
+ headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
2740
+ bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
2741
+ };
2742
+ case 'breaking-blue':
2743
+ return {
2744
+ headerBg: 'linear-gradient(90deg, #0d47a1 0%, #1565c0 50%, #0d47a1 100%)',
2745
+ bodyBg: 'linear-gradient(90deg, #1a1a2e 0%, #16213e 50%, #1a1a2e 100%)'
2746
+ };
2747
+ case 'alert-red':
2748
+ return {
2749
+ headerBg: 'linear-gradient(90deg, #b71c1c 0%, #d32f2f 50%, #b71c1c 100%)',
2750
+ bodyBg: 'linear-gradient(90deg, #7f0000 0%, #9a0000 50%, #7f0000 100%)'
2751
+ };
2752
+ case 'news-blue':
2753
+ return {
2754
+ headerBg: 'linear-gradient(90deg, #01579b 0%, #0277bd 50%, #01579b 100%)',
2755
+ bodyBg: 'linear-gradient(90deg, #002171 0%, #003c8f 50%, #002171 100%)'
2756
+ };
2757
+ case 'custom':
2758
+ return {
2759
+ headerBg: broadcastStyle.headerColor || '#cc0000',
2760
+ bodyBg: broadcastStyle.bodyColor || '#1a237e'
2761
+ };
2762
+ default:
2763
+ return {
2764
+ headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
2765
+ bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
2766
+ };
2767
+ }
2768
+ }
2769
+
2770
+ private createGlobeElement(animate: boolean): HTMLDivElement {
2771
+ const globeContainer = document.createElement('div');
2772
+ globeContainer.className = 'uvf-ticker-globe';
2773
+ globeContainer.style.cssText = `
2774
+ width: 24px;
2775
+ height: 24px;
2776
+ position: relative;
2777
+ flex-shrink: 0;
2778
+ `;
2779
+
2780
+ // SVG Globe icon
2781
+ globeContainer.innerHTML = `
2782
+ <svg viewBox="0 0 24 24" fill="none" style="width: 100%; height: 100%; ${animate ? 'animation: globe-rotate 8s linear infinite;' : ''}">
2783
+ <circle cx="12" cy="12" r="10" stroke="#ffffff" stroke-width="1.5" fill="none"/>
2784
+ <ellipse cx="12" cy="12" rx="10" ry="4" stroke="#ffffff" stroke-width="1" fill="none"/>
2785
+ <ellipse cx="12" cy="12" rx="4" ry="10" stroke="#ffffff" stroke-width="1" fill="none"/>
2786
+ <line x1="2" y1="12" x2="22" y2="12" stroke="#ffffff" stroke-width="0.5"/>
2787
+ <line x1="12" y1="2" x2="12" y2="22" stroke="#ffffff" stroke-width="0.5"/>
2788
+ <path d="M4 8 Q12 6 20 8" stroke="#ffffff" stroke-width="0.5" fill="none"/>
2789
+ <path d="M4 16 Q12 18 20 16" stroke="#ffffff" stroke-width="0.5" fill="none"/>
2790
+ </svg>
2791
+ `;
2792
+
2793
+ return globeContainer;
2794
+ }
2795
+
2796
+ private createLiveBadgeElement(pulse: boolean): HTMLDivElement {
2797
+ const badge = document.createElement('div');
2798
+ badge.className = 'uvf-ticker-live-badge';
2799
+ badge.style.cssText = `
2800
+ display: flex;
2801
+ align-items: center;
2802
+ margin-left: auto;
2803
+ padding: 2px 8px;
2804
+ background: #ff0000;
2805
+ border-radius: 3px;
2806
+ ${pulse ? 'animation: live-pulse 1.5s ease-in-out infinite;' : ''}
2807
+ `;
2808
+
2809
+ // Red dot
2810
+ const dot = document.createElement('span');
2811
+ dot.style.cssText = `
2812
+ width: 8px;
2813
+ height: 8px;
2814
+ background: #ffffff;
2815
+ border-radius: 50%;
2816
+ margin-right: 4px;
2817
+ ${pulse ? 'animation: dot-blink 1s ease-in-out infinite;' : ''}
2818
+ `;
2819
+ badge.appendChild(dot);
2820
+
2821
+ // LIVE text
2822
+ const text = document.createElement('span');
2823
+ text.textContent = 'LIVE';
2824
+ text.style.cssText = `
2825
+ color: #ffffff;
2826
+ font-size: 11px;
2827
+ font-weight: 800;
2828
+ letter-spacing: 0.5px;
2829
+ `;
2830
+ badge.appendChild(text);
2831
+
2832
+ return badge;
2833
+ }
2834
+
2835
+ private ensureTickerAnimations(): void {
2570
2836
  if (!document.querySelector('#uvf-ticker-animation')) {
2571
2837
  const style = document.createElement('style');
2572
2838
  style.id = 'uvf-ticker-animation';
@@ -2575,11 +2841,21 @@ export class WebPlayer extends BasePlayer {
2575
2841
  0% { transform: translateX(0%); }
2576
2842
  100% { transform: translateX(-100%); }
2577
2843
  }
2844
+ @keyframes globe-rotate {
2845
+ 0% { transform: rotate(0deg); }
2846
+ 100% { transform: rotate(360deg); }
2847
+ }
2848
+ @keyframes live-pulse {
2849
+ 0%, 100% { opacity: 1; transform: scale(1); }
2850
+ 50% { opacity: 0.85; transform: scale(1.02); }
2851
+ }
2852
+ @keyframes dot-blink {
2853
+ 0%, 100% { opacity: 1; }
2854
+ 50% { opacity: 0.3; }
2855
+ }
2578
2856
  `;
2579
2857
  document.head.appendChild(style);
2580
2858
  }
2581
-
2582
- return ticker;
2583
2859
  }
2584
2860
 
2585
2861
  private calculateTickerDuration(config: FlashNewsTickerConfig): number {
@@ -13,5 +13,16 @@ export { WebPlayerView } from './react/WebPlayerView';
13
13
  // Export EPG (Electronic Program Guide) components
14
14
  export * from './react/EPG';
15
15
 
16
+ // Export Flash News Ticker types
17
+ export type {
18
+ FlashNewsTickerConfig,
19
+ FlashNewsTickerItem,
20
+ FlashNewsTickerAPI,
21
+ TickerDisplayConfig,
22
+ TickerStyleVariant,
23
+ BroadcastTheme,
24
+ BroadcastStyleConfig
25
+ } from './react/types/FlashNewsTickerTypes';
26
+
16
27
  // Version
17
28
  export const VERSION = '1.0.0';
@@ -2,9 +2,68 @@
2
2
  * Flash News Ticker Types
3
3
  *
4
4
  * Type definitions for the flash news ticker feature that displays
5
- * scrolling text overlays on the video player.
5
+ * scrolling text overlays on the video player. Supports both simple
6
+ * tickers and broadcast-style breaking news banners.
6
7
  */
7
8
 
9
+ /**
10
+ * Style variant for the ticker
11
+ * - 'simple': Basic scrolling ticker with single background
12
+ * - 'broadcast': Professional broadcast-style with header, body, globe, and LIVE badge
13
+ */
14
+ export type TickerStyleVariant = 'simple' | 'broadcast';
15
+
16
+ /**
17
+ * Broadcast style theme presets
18
+ */
19
+ export type BroadcastTheme =
20
+ | 'breaking-red' // Red header with blue body (classic breaking news)
21
+ | 'breaking-blue' // Blue header with dark body
22
+ | 'alert-red' // Full red theme for urgent alerts
23
+ | 'news-blue' // Professional blue theme
24
+ | 'custom'; // Custom colors via headerColor/bodyColor
25
+
26
+ /**
27
+ * Configuration for broadcast-style banner appearance
28
+ */
29
+ export interface BroadcastStyleConfig {
30
+ /** Theme preset. Default: 'breaking-red' */
31
+ theme?: BroadcastTheme;
32
+
33
+ /** Header text (e.g., "BREAKING NEWS", "LIVE", "ALERT"). Default: 'BREAKING NEWS' */
34
+ headerText?: string;
35
+
36
+ /** Show the globe/world graphic on the left. Default: true */
37
+ showGlobe?: boolean;
38
+
39
+ /** Show LIVE badge indicator. Default: true */
40
+ showLiveBadge?: boolean;
41
+
42
+ /** Custom header background color (only used when theme is 'custom') */
43
+ headerColor?: string;
44
+
45
+ /** Custom header text color. Default: '#ffffff' */
46
+ headerTextColor?: string;
47
+
48
+ /** Custom body/ticker background color (only used when theme is 'custom') */
49
+ bodyColor?: string;
50
+
51
+ /** Header height in pixels. Default: 28 */
52
+ headerHeight?: number;
53
+
54
+ /** Header font size in pixels. Default: 16 */
55
+ headerFontSize?: number;
56
+
57
+ /** Secondary text shown below header (optional) */
58
+ subHeaderText?: string;
59
+
60
+ /** Globe animation enabled. Default: true */
61
+ animateGlobe?: boolean;
62
+
63
+ /** LIVE badge pulse animation. Default: true */
64
+ pulseLiveBadge?: boolean;
65
+ }
66
+
8
67
  /**
9
68
  * Individual news item to display in the ticker
10
69
  */
@@ -44,7 +103,7 @@ export interface TickerDisplayConfig {
44
103
 
45
104
  // Visual styling
46
105
 
47
- /** Height of ticker in pixels. Default: 40 */
106
+ /** Height of ticker in pixels. Default: 40 for simple, 60 for broadcast */
48
107
  height?: number;
49
108
 
50
109
  /** Background color with alpha. Default: 'rgba(0,0,0,0.7)' */
@@ -72,6 +131,12 @@ export interface TickerDisplayConfig {
72
131
 
73
132
  /** Offset in pixels for safe area. Default: 0 for bottom, 10 for top */
74
133
  offset?: number;
134
+
135
+ /** Style variant for this specific ticker. Overrides parent styleVariant */
136
+ styleVariant?: TickerStyleVariant;
137
+
138
+ /** Broadcast style config for this specific ticker. Overrides parent broadcastStyle */
139
+ broadcastStyle?: BroadcastStyleConfig;
75
140
  }
76
141
 
77
142
  /**
@@ -87,6 +152,16 @@ export interface FlashNewsTickerConfig {
87
152
  /** Position on screen. Default: 'bottom' */
88
153
  position?: 'top' | 'bottom' | 'both';
89
154
 
155
+ /**
156
+ * Style variant for the ticker. Default: 'simple'
157
+ * - 'simple': Basic scrolling ticker (original style)
158
+ * - 'broadcast': Professional broadcast-style breaking news banner
159
+ */
160
+ styleVariant?: TickerStyleVariant;
161
+
162
+ /** Configuration for broadcast-style appearance (only used when styleVariant is 'broadcast') */
163
+ broadcastStyle?: BroadcastStyleConfig;
164
+
90
165
  /** Configuration for top ticker (only used when position is 'both') */
91
166
  topConfig?: TickerDisplayConfig;
92
167