unified-video-framework 1.4.436 → 1.4.438
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.
- package/package.json +1 -1
- package/packages/web/dist/WebPlayer.d.ts +8 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +306 -10
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/drm/systems/WidevineDRM.js +70 -6
- package/packages/web/dist/index.d.ts +1 -0
- package/packages/web/dist/index.d.ts.map +1 -1
- package/packages/web/dist/index.js.map +1 -1
- package/packages/web/dist/react/types/FlashNewsTickerTypes.d.ts +20 -0
- package/packages/web/dist/react/types/FlashNewsTickerTypes.d.ts.map +1 -1
- package/packages/web/src/WebPlayer.ts +368 -12
- package/packages/web/src/index.ts +11 -0
- package/packages/web/src/react/types/FlashNewsTickerTypes.ts +77 -2
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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;
|
|
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":"
|
|
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';
|
|
@@ -172,6 +172,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
172
172
|
private currentRetryAttempt: number = 0;
|
|
173
173
|
private lastFailedUrl: string = ''; // Track last failed URL to avoid duplicate error handling
|
|
174
174
|
private isFallbackPosterMode: boolean = false; // True when showing fallback poster (no playable sources)
|
|
175
|
+
private hlsErrorRetryCount: number = 0; // Track HLS error recovery attempts
|
|
176
|
+
private readonly MAX_HLS_ERROR_RETRIES: number = 3; // Max HLS recovery attempts before fallback
|
|
175
177
|
|
|
176
178
|
// Live stream detection
|
|
177
179
|
private lastDuration: number = 0; // Track duration changes to detect live streams
|
|
@@ -727,6 +729,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
727
729
|
this.currentRetryAttempt = 0;
|
|
728
730
|
this.lastFailedUrl = '';
|
|
729
731
|
this.isFallbackPosterMode = false; // Reset fallback poster mode
|
|
732
|
+
this.hlsErrorRetryCount = 0; // Reset HLS error retry count
|
|
730
733
|
|
|
731
734
|
// Clean up previous instances
|
|
732
735
|
await this.cleanup();
|
|
@@ -1122,6 +1125,9 @@ export class WebPlayer extends BasePlayer {
|
|
|
1122
1125
|
this.hls.attachMedia(this.video);
|
|
1123
1126
|
|
|
1124
1127
|
this.hls.on(window.Hls.Events.MANIFEST_PARSED, (event: any, data: any) => {
|
|
1128
|
+
// Reset HLS error retry count on successful manifest load
|
|
1129
|
+
this.hlsErrorRetryCount = 0;
|
|
1130
|
+
|
|
1125
1131
|
// Extract quality levels
|
|
1126
1132
|
this.qualities = data.levels.map((level: any, index: number) => ({
|
|
1127
1133
|
height: level.height,
|
|
@@ -1164,27 +1170,101 @@ export class WebPlayer extends BasePlayer {
|
|
|
1164
1170
|
}
|
|
1165
1171
|
}
|
|
1166
1172
|
|
|
1167
|
-
private handleHLSError(data: any): void {
|
|
1173
|
+
private async handleHLSError(data: any): Promise<void> {
|
|
1168
1174
|
const Hls = window.Hls;
|
|
1175
|
+
|
|
1176
|
+
this.debugLog(`🔴 HLS Error: type=${data.type}, details=${data.details}, fatal=${data.fatal}`);
|
|
1177
|
+
|
|
1178
|
+
// Check if we've exceeded max retry attempts
|
|
1179
|
+
if (this.hlsErrorRetryCount >= this.MAX_HLS_ERROR_RETRIES) {
|
|
1180
|
+
this.debugLog(`🔴 HLS max retries (${this.MAX_HLS_ERROR_RETRIES}) exceeded, triggering fallback`);
|
|
1181
|
+
this.hls?.destroy();
|
|
1182
|
+
this.hls = null;
|
|
1183
|
+
|
|
1184
|
+
// Try fallback sources
|
|
1185
|
+
const fallbackLoaded = await this.tryFallbackSource({
|
|
1186
|
+
code: 'HLS_ERROR',
|
|
1187
|
+
message: data.details,
|
|
1188
|
+
type: data.type,
|
|
1189
|
+
fatal: true,
|
|
1190
|
+
details: data
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
if (!fallbackLoaded) {
|
|
1194
|
+
this.handleError({
|
|
1195
|
+
code: 'HLS_ERROR',
|
|
1196
|
+
message: `HLS stream failed after ${this.MAX_HLS_ERROR_RETRIES} retries: ${data.details}`,
|
|
1197
|
+
type: 'media',
|
|
1198
|
+
fatal: true,
|
|
1199
|
+
details: data
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1169
1205
|
switch (data.type) {
|
|
1170
1206
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
1171
|
-
|
|
1172
|
-
this.
|
|
1207
|
+
this.hlsErrorRetryCount++;
|
|
1208
|
+
this.debugLog(`🔴 Fatal network error (attempt ${this.hlsErrorRetryCount}/${this.MAX_HLS_ERROR_RETRIES}), trying to recover`);
|
|
1209
|
+
|
|
1210
|
+
// For manifest errors (like 404), recovery won't help - go straight to fallback
|
|
1211
|
+
if (data.details === 'manifestLoadError' || data.details === 'manifestParsingError') {
|
|
1212
|
+
this.debugLog(`🔴 Manifest error detected (${data.details}), skipping recovery - triggering fallback`);
|
|
1213
|
+
this.hls?.destroy();
|
|
1214
|
+
this.hls = null;
|
|
1215
|
+
|
|
1216
|
+
const fallbackLoaded = await this.tryFallbackSource({
|
|
1217
|
+
code: 'HLS_MANIFEST_ERROR',
|
|
1218
|
+
message: data.details,
|
|
1219
|
+
type: 'network',
|
|
1220
|
+
fatal: true,
|
|
1221
|
+
details: data
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
if (!fallbackLoaded) {
|
|
1225
|
+
this.handleError({
|
|
1226
|
+
code: 'HLS_MANIFEST_ERROR',
|
|
1227
|
+
message: `Failed to load HLS manifest: ${data.details}`,
|
|
1228
|
+
type: 'media',
|
|
1229
|
+
fatal: true,
|
|
1230
|
+
details: data
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
// For other network errors, try recovery
|
|
1235
|
+
this.hls?.startLoad();
|
|
1236
|
+
}
|
|
1173
1237
|
break;
|
|
1238
|
+
|
|
1174
1239
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
|
1175
|
-
|
|
1176
|
-
this.
|
|
1240
|
+
this.hlsErrorRetryCount++;
|
|
1241
|
+
this.debugLog(`🔴 Fatal media error (attempt ${this.hlsErrorRetryCount}/${this.MAX_HLS_ERROR_RETRIES}), trying to recover`);
|
|
1242
|
+
this.hls?.recoverMediaError();
|
|
1177
1243
|
break;
|
|
1244
|
+
|
|
1178
1245
|
default:
|
|
1179
|
-
|
|
1180
|
-
this.
|
|
1246
|
+
this.debugLog(`🔴 Fatal unrecoverable HLS error: ${data.details}`);
|
|
1247
|
+
this.hls?.destroy();
|
|
1248
|
+
this.hls = null;
|
|
1249
|
+
|
|
1250
|
+
// Try fallback sources for unrecoverable errors
|
|
1251
|
+
const fallbackLoaded = await this.tryFallbackSource({
|
|
1181
1252
|
code: 'HLS_ERROR',
|
|
1182
1253
|
message: data.details,
|
|
1183
1254
|
type: 'media',
|
|
1184
1255
|
fatal: true,
|
|
1185
1256
|
details: data
|
|
1186
1257
|
});
|
|
1187
|
-
|
|
1258
|
+
|
|
1259
|
+
if (!fallbackLoaded) {
|
|
1260
|
+
this.handleError({
|
|
1261
|
+
code: 'HLS_ERROR',
|
|
1262
|
+
message: data.details,
|
|
1263
|
+
type: 'media',
|
|
1264
|
+
fatal: true,
|
|
1265
|
+
details: data
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1188
1268
|
break;
|
|
1189
1269
|
}
|
|
1190
1270
|
}
|
|
@@ -2468,8 +2548,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
2468
2548
|
}
|
|
2469
2549
|
|
|
2470
2550
|
private createTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
|
|
2551
|
+
// Route to broadcast style if configured
|
|
2552
|
+
if (config.styleVariant === 'broadcast') {
|
|
2553
|
+
return this.createBroadcastTickerElement(config, position);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// Simple style (original implementation)
|
|
2557
|
+
return this.createSimpleTickerElement(config, position);
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
private createSimpleTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
|
|
2471
2561
|
const ticker = document.createElement('div');
|
|
2472
|
-
ticker.className = `uvf-flash-ticker ticker-${position}`;
|
|
2562
|
+
ticker.className = `uvf-flash-ticker ticker-${position} ticker-simple`;
|
|
2473
2563
|
|
|
2474
2564
|
const bottomOffset = config.bottomOffset || 0;
|
|
2475
2565
|
const topOffset = config.topOffset || 0;
|
|
@@ -2567,6 +2657,262 @@ export class WebPlayer extends BasePlayer {
|
|
|
2567
2657
|
ticker.appendChild(track);
|
|
2568
2658
|
|
|
2569
2659
|
// Add animation keyframes to document if not exists
|
|
2660
|
+
this.ensureTickerAnimations();
|
|
2661
|
+
|
|
2662
|
+
return ticker;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
private createBroadcastTickerElement(config: FlashNewsTickerConfig, position: 'top' | 'bottom'): HTMLDivElement {
|
|
2666
|
+
const broadcastStyle = config.broadcastStyle || {};
|
|
2667
|
+
const theme = broadcastStyle.theme || 'breaking-red';
|
|
2668
|
+
|
|
2669
|
+
// Get theme colors
|
|
2670
|
+
const themeColors = this.getBroadcastThemeColors(theme, broadcastStyle);
|
|
2671
|
+
|
|
2672
|
+
const ticker = document.createElement('div');
|
|
2673
|
+
ticker.className = `uvf-flash-ticker ticker-${position} ticker-broadcast`;
|
|
2674
|
+
|
|
2675
|
+
const bottomOffset = config.bottomOffset || 0;
|
|
2676
|
+
const topOffset = config.topOffset || 0;
|
|
2677
|
+
const headerHeight = broadcastStyle.headerHeight || 28;
|
|
2678
|
+
const bodyHeight = config.height || 36;
|
|
2679
|
+
const totalHeight = headerHeight + bodyHeight;
|
|
2680
|
+
|
|
2681
|
+
ticker.style.cssText = `
|
|
2682
|
+
position: absolute;
|
|
2683
|
+
left: 0;
|
|
2684
|
+
right: 0;
|
|
2685
|
+
height: ${totalHeight}px;
|
|
2686
|
+
${position === 'top' ? `top: ${topOffset}px;` : `bottom: ${bottomOffset}px;`}
|
|
2687
|
+
overflow: hidden;
|
|
2688
|
+
pointer-events: none;
|
|
2689
|
+
display: flex;
|
|
2690
|
+
flex-direction: column;
|
|
2691
|
+
`;
|
|
2692
|
+
|
|
2693
|
+
// Create header row (BREAKING NEWS with globe and LIVE badge)
|
|
2694
|
+
const header = document.createElement('div');
|
|
2695
|
+
header.className = 'uvf-ticker-header';
|
|
2696
|
+
header.style.cssText = `
|
|
2697
|
+
display: flex;
|
|
2698
|
+
align-items: center;
|
|
2699
|
+
height: ${headerHeight}px;
|
|
2700
|
+
background: ${themeColors.headerBg};
|
|
2701
|
+
padding: 0 12px;
|
|
2702
|
+
position: relative;
|
|
2703
|
+
`;
|
|
2704
|
+
|
|
2705
|
+
// Add globe graphic if enabled
|
|
2706
|
+
if (broadcastStyle.showGlobe !== false) {
|
|
2707
|
+
const globe = this.createGlobeElement(broadcastStyle.animateGlobe !== false);
|
|
2708
|
+
header.appendChild(globe);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
// Add header text (BREAKING NEWS)
|
|
2712
|
+
const headerText = document.createElement('span');
|
|
2713
|
+
headerText.className = 'uvf-ticker-header-text';
|
|
2714
|
+
headerText.textContent = broadcastStyle.headerText || 'BREAKING NEWS';
|
|
2715
|
+
headerText.style.cssText = `
|
|
2716
|
+
color: ${broadcastStyle.headerTextColor || '#ffffff'};
|
|
2717
|
+
font-size: ${broadcastStyle.headerFontSize || 16}px;
|
|
2718
|
+
font-weight: 800;
|
|
2719
|
+
text-transform: uppercase;
|
|
2720
|
+
letter-spacing: 1px;
|
|
2721
|
+
margin-left: ${broadcastStyle.showGlobe !== false ? '8px' : '0'};
|
|
2722
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
2723
|
+
`;
|
|
2724
|
+
header.appendChild(headerText);
|
|
2725
|
+
|
|
2726
|
+
// Add LIVE badge if enabled
|
|
2727
|
+
if (broadcastStyle.showLiveBadge !== false) {
|
|
2728
|
+
const liveBadge = this.createLiveBadgeElement(broadcastStyle.pulseLiveBadge !== false);
|
|
2729
|
+
header.appendChild(liveBadge);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// Create body row (scrolling ticker)
|
|
2733
|
+
const body = document.createElement('div');
|
|
2734
|
+
body.className = 'uvf-ticker-body';
|
|
2735
|
+
body.style.cssText = `
|
|
2736
|
+
display: flex;
|
|
2737
|
+
align-items: center;
|
|
2738
|
+
height: ${bodyHeight}px;
|
|
2739
|
+
background: ${themeColors.bodyBg};
|
|
2740
|
+
overflow: hidden;
|
|
2741
|
+
position: relative;
|
|
2742
|
+
`;
|
|
2743
|
+
|
|
2744
|
+
// Create scrolling track
|
|
2745
|
+
const track = document.createElement('div');
|
|
2746
|
+
track.className = 'uvf-ticker-track';
|
|
2747
|
+
|
|
2748
|
+
const duration = this.calculateTickerDuration(config);
|
|
2749
|
+
track.style.cssText = `
|
|
2750
|
+
display: flex;
|
|
2751
|
+
white-space: nowrap;
|
|
2752
|
+
animation: ticker-scroll ${duration}s linear infinite;
|
|
2753
|
+
will-change: transform;
|
|
2754
|
+
padding-left: 100%;
|
|
2755
|
+
`;
|
|
2756
|
+
|
|
2757
|
+
// Calculate responsive font size
|
|
2758
|
+
const containerWidth = this.container?.offsetWidth || 1920;
|
|
2759
|
+
const baseFontSize = config.fontSize || 14;
|
|
2760
|
+
let responsiveFontSize = baseFontSize;
|
|
2761
|
+
if (containerWidth < 768) {
|
|
2762
|
+
responsiveFontSize = Math.max(baseFontSize * 0.8, 12);
|
|
2763
|
+
} else if (containerWidth < 1280) {
|
|
2764
|
+
responsiveFontSize = Math.max(baseFontSize * 0.9, 13);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// Render items
|
|
2768
|
+
const renderItems = () => {
|
|
2769
|
+
if (!config.items) return;
|
|
2770
|
+
config.items.forEach((item) => {
|
|
2771
|
+
const span = document.createElement('span');
|
|
2772
|
+
if (item.html) {
|
|
2773
|
+
span.innerHTML = item.html;
|
|
2774
|
+
} else {
|
|
2775
|
+
span.textContent = item.text;
|
|
2776
|
+
}
|
|
2777
|
+
span.style.cssText = `
|
|
2778
|
+
color: ${config.textColor || '#ffffff'};
|
|
2779
|
+
font-size: ${responsiveFontSize}px;
|
|
2780
|
+
font-weight: ${config.fontWeight || 600};
|
|
2781
|
+
margin-right: ${config.gap || 100}px;
|
|
2782
|
+
display: inline-flex;
|
|
2783
|
+
align-items: center;
|
|
2784
|
+
`;
|
|
2785
|
+
track.appendChild(span);
|
|
2786
|
+
|
|
2787
|
+
if (config.separator) {
|
|
2788
|
+
const sep = document.createElement('span');
|
|
2789
|
+
sep.textContent = config.separator;
|
|
2790
|
+
sep.style.cssText = `
|
|
2791
|
+
color: ${config.textColor || '#ffffff'};
|
|
2792
|
+
font-size: ${responsiveFontSize}px;
|
|
2793
|
+
opacity: 0.5;
|
|
2794
|
+
margin: 0 8px;
|
|
2795
|
+
`;
|
|
2796
|
+
track.appendChild(sep);
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
};
|
|
2800
|
+
|
|
2801
|
+
for (let i = 0; i < 10; i++) {
|
|
2802
|
+
renderItems();
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
body.appendChild(track);
|
|
2806
|
+
ticker.appendChild(header);
|
|
2807
|
+
ticker.appendChild(body);
|
|
2808
|
+
|
|
2809
|
+
// Add all animation keyframes
|
|
2810
|
+
this.ensureTickerAnimations();
|
|
2811
|
+
|
|
2812
|
+
return ticker;
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
private getBroadcastThemeColors(theme: BroadcastTheme, broadcastStyle: BroadcastStyleConfig): { headerBg: string; bodyBg: string } {
|
|
2816
|
+
switch (theme) {
|
|
2817
|
+
case 'breaking-red':
|
|
2818
|
+
return {
|
|
2819
|
+
headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
|
|
2820
|
+
bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
|
|
2821
|
+
};
|
|
2822
|
+
case 'breaking-blue':
|
|
2823
|
+
return {
|
|
2824
|
+
headerBg: 'linear-gradient(90deg, #0d47a1 0%, #1565c0 50%, #0d47a1 100%)',
|
|
2825
|
+
bodyBg: 'linear-gradient(90deg, #1a1a2e 0%, #16213e 50%, #1a1a2e 100%)'
|
|
2826
|
+
};
|
|
2827
|
+
case 'alert-red':
|
|
2828
|
+
return {
|
|
2829
|
+
headerBg: 'linear-gradient(90deg, #b71c1c 0%, #d32f2f 50%, #b71c1c 100%)',
|
|
2830
|
+
bodyBg: 'linear-gradient(90deg, #7f0000 0%, #9a0000 50%, #7f0000 100%)'
|
|
2831
|
+
};
|
|
2832
|
+
case 'news-blue':
|
|
2833
|
+
return {
|
|
2834
|
+
headerBg: 'linear-gradient(90deg, #01579b 0%, #0277bd 50%, #01579b 100%)',
|
|
2835
|
+
bodyBg: 'linear-gradient(90deg, #002171 0%, #003c8f 50%, #002171 100%)'
|
|
2836
|
+
};
|
|
2837
|
+
case 'custom':
|
|
2838
|
+
return {
|
|
2839
|
+
headerBg: broadcastStyle.headerColor || '#cc0000',
|
|
2840
|
+
bodyBg: broadcastStyle.bodyColor || '#1a237e'
|
|
2841
|
+
};
|
|
2842
|
+
default:
|
|
2843
|
+
return {
|
|
2844
|
+
headerBg: 'linear-gradient(90deg, #cc0000 0%, #ff0000 50%, #cc0000 100%)',
|
|
2845
|
+
bodyBg: 'linear-gradient(90deg, #1a237e 0%, #283593 50%, #1a237e 100%)'
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
private createGlobeElement(animate: boolean): HTMLDivElement {
|
|
2851
|
+
const globeContainer = document.createElement('div');
|
|
2852
|
+
globeContainer.className = 'uvf-ticker-globe';
|
|
2853
|
+
globeContainer.style.cssText = `
|
|
2854
|
+
width: 24px;
|
|
2855
|
+
height: 24px;
|
|
2856
|
+
position: relative;
|
|
2857
|
+
flex-shrink: 0;
|
|
2858
|
+
`;
|
|
2859
|
+
|
|
2860
|
+
// SVG Globe icon
|
|
2861
|
+
globeContainer.innerHTML = `
|
|
2862
|
+
<svg viewBox="0 0 24 24" fill="none" style="width: 100%; height: 100%; ${animate ? 'animation: globe-rotate 8s linear infinite;' : ''}">
|
|
2863
|
+
<circle cx="12" cy="12" r="10" stroke="#ffffff" stroke-width="1.5" fill="none"/>
|
|
2864
|
+
<ellipse cx="12" cy="12" rx="10" ry="4" stroke="#ffffff" stroke-width="1" fill="none"/>
|
|
2865
|
+
<ellipse cx="12" cy="12" rx="4" ry="10" stroke="#ffffff" stroke-width="1" fill="none"/>
|
|
2866
|
+
<line x1="2" y1="12" x2="22" y2="12" stroke="#ffffff" stroke-width="0.5"/>
|
|
2867
|
+
<line x1="12" y1="2" x2="12" y2="22" stroke="#ffffff" stroke-width="0.5"/>
|
|
2868
|
+
<path d="M4 8 Q12 6 20 8" stroke="#ffffff" stroke-width="0.5" fill="none"/>
|
|
2869
|
+
<path d="M4 16 Q12 18 20 16" stroke="#ffffff" stroke-width="0.5" fill="none"/>
|
|
2870
|
+
</svg>
|
|
2871
|
+
`;
|
|
2872
|
+
|
|
2873
|
+
return globeContainer;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
private createLiveBadgeElement(pulse: boolean): HTMLDivElement {
|
|
2877
|
+
const badge = document.createElement('div');
|
|
2878
|
+
badge.className = 'uvf-ticker-live-badge';
|
|
2879
|
+
badge.style.cssText = `
|
|
2880
|
+
display: flex;
|
|
2881
|
+
align-items: center;
|
|
2882
|
+
margin-left: auto;
|
|
2883
|
+
padding: 2px 8px;
|
|
2884
|
+
background: #ff0000;
|
|
2885
|
+
border-radius: 3px;
|
|
2886
|
+
${pulse ? 'animation: live-pulse 1.5s ease-in-out infinite;' : ''}
|
|
2887
|
+
`;
|
|
2888
|
+
|
|
2889
|
+
// Red dot
|
|
2890
|
+
const dot = document.createElement('span');
|
|
2891
|
+
dot.style.cssText = `
|
|
2892
|
+
width: 8px;
|
|
2893
|
+
height: 8px;
|
|
2894
|
+
background: #ffffff;
|
|
2895
|
+
border-radius: 50%;
|
|
2896
|
+
margin-right: 4px;
|
|
2897
|
+
${pulse ? 'animation: dot-blink 1s ease-in-out infinite;' : ''}
|
|
2898
|
+
`;
|
|
2899
|
+
badge.appendChild(dot);
|
|
2900
|
+
|
|
2901
|
+
// LIVE text
|
|
2902
|
+
const text = document.createElement('span');
|
|
2903
|
+
text.textContent = 'LIVE';
|
|
2904
|
+
text.style.cssText = `
|
|
2905
|
+
color: #ffffff;
|
|
2906
|
+
font-size: 11px;
|
|
2907
|
+
font-weight: 800;
|
|
2908
|
+
letter-spacing: 0.5px;
|
|
2909
|
+
`;
|
|
2910
|
+
badge.appendChild(text);
|
|
2911
|
+
|
|
2912
|
+
return badge;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
private ensureTickerAnimations(): void {
|
|
2570
2916
|
if (!document.querySelector('#uvf-ticker-animation')) {
|
|
2571
2917
|
const style = document.createElement('style');
|
|
2572
2918
|
style.id = 'uvf-ticker-animation';
|
|
@@ -2575,11 +2921,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
2575
2921
|
0% { transform: translateX(0%); }
|
|
2576
2922
|
100% { transform: translateX(-100%); }
|
|
2577
2923
|
}
|
|
2924
|
+
@keyframes globe-rotate {
|
|
2925
|
+
0% { transform: rotate(0deg); }
|
|
2926
|
+
100% { transform: rotate(360deg); }
|
|
2927
|
+
}
|
|
2928
|
+
@keyframes live-pulse {
|
|
2929
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
2930
|
+
50% { opacity: 0.85; transform: scale(1.02); }
|
|
2931
|
+
}
|
|
2932
|
+
@keyframes dot-blink {
|
|
2933
|
+
0%, 100% { opacity: 1; }
|
|
2934
|
+
50% { opacity: 0.3; }
|
|
2935
|
+
}
|
|
2578
2936
|
`;
|
|
2579
2937
|
document.head.appendChild(style);
|
|
2580
2938
|
}
|
|
2581
|
-
|
|
2582
|
-
return ticker;
|
|
2583
2939
|
}
|
|
2584
2940
|
|
|
2585
2941
|
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';
|