unified-video-framework 1.4.157 → 1.4.159
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 +12 -2
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts +18 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts.map +1 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js +117 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js.map +1 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts +18 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.js +99 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.js.map +1 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts +20 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js +161 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js.map +1 -0
- package/packages/core/dist/analytics/core/EventBatcher.d.ts +32 -0
- package/packages/core/dist/analytics/core/EventBatcher.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/EventBatcher.js +98 -0
- package/packages/core/dist/analytics/core/EventBatcher.js.map +1 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts +19 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.js +80 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.js.map +1 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts +32 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts.map +1 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js +220 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js.map +1 -0
- package/packages/core/dist/analytics/index.d.ts +13 -0
- package/packages/core/dist/analytics/index.d.ts.map +1 -0
- package/packages/core/dist/analytics/index.js +13 -0
- package/packages/core/dist/analytics/index.js.map +1 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts +239 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts.map +1 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.js +8 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.js.map +1 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.d.ts +27 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.d.ts.map +1 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.js +184 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.js.map +1 -0
- package/packages/core/dist/chapter-manager.d.ts +39 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/src/analytics/README.md +902 -0
- package/packages/core/src/analytics/adapters/PlayerAnalyticsAdapter.ts +156 -0
- package/packages/core/src/analytics/core/AnalyticsProvider.ts +169 -0
- package/packages/core/src/analytics/core/DynamicAnalyticsManager.ts +199 -0
- package/packages/core/src/analytics/core/EventBatcher.ts +160 -0
- package/packages/core/src/analytics/core/PlayerAnalytics.ts +147 -0
- package/packages/core/src/analytics/index.ts +51 -0
- package/packages/core/src/analytics/types/AnalyticsTypes.ts +315 -0
- package/packages/core/src/analytics/utils/DeviceDetection.ts +220 -0
- package/packages/core/src/index.ts +3 -0
- package/packages/ios/README.md +84 -0
- package/packages/web/dist/WebPlayer.d.ts +6 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +273 -67
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/epg/EPGController.d.ts +78 -0
- package/packages/web/dist/epg/EPGController.d.ts.map +1 -0
- package/packages/web/dist/epg/EPGController.js +476 -0
- package/packages/web/dist/epg/EPGController.js.map +1 -0
- package/packages/web/src/WebPlayer.ts +336 -85
- package/src/analytics/README.md +902 -0
- package/src/analytics/adapters/PlayerAnalyticsAdapter.ts +572 -0
- package/src/analytics/core/DynamicAnalyticsManager.ts +526 -0
- package/src/analytics/examples/DynamicAnalyticsExample.ts +324 -0
- package/src/analytics/index.ts +60 -0
|
@@ -215,8 +215,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
215
215
|
this.video.controls = false; // We'll use custom controls
|
|
216
216
|
// Don't set autoplay attribute - we'll handle it programmatically with intelligent detection
|
|
217
217
|
this.video.autoplay = false;
|
|
218
|
-
//
|
|
219
|
-
this.video.muted = this.config.
|
|
218
|
+
// Respect user's muted preference, intelligent autoplay will handle browser policies
|
|
219
|
+
this.video.muted = this.config.muted ?? false;
|
|
220
220
|
this.video.loop = this.config.loop ?? false;
|
|
221
221
|
this.video.playsInline = this.config.playsInline ?? true;
|
|
222
222
|
this.video.preload = this.config.preload ?? 'metadata';
|
|
@@ -862,6 +862,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
862
862
|
}
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
+
/**
|
|
866
|
+
* Check if page has user activation (from navigation or interaction)
|
|
867
|
+
*/
|
|
868
|
+
private hasUserActivation(): boolean {
|
|
869
|
+
// Check if browser supports userActivation API
|
|
870
|
+
if (typeof navigator !== 'undefined' && (navigator as any).userActivation) {
|
|
871
|
+
const hasActivation = (navigator as any).userActivation.hasBeenActive;
|
|
872
|
+
this.debugLog(`🎯 User activation detected: ${hasActivation}`);
|
|
873
|
+
return hasActivation;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// Fallback: Check if user has interacted with the page
|
|
877
|
+
const hasInteracted = this.lastUserInteraction > 0 &&
|
|
878
|
+
(Date.now() - this.lastUserInteraction) < 5000;
|
|
879
|
+
|
|
880
|
+
this.debugLog(`🎯 Recent user interaction: ${hasInteracted}`);
|
|
881
|
+
return hasInteracted;
|
|
882
|
+
}
|
|
883
|
+
|
|
865
884
|
/**
|
|
866
885
|
* Attempt intelligent autoplay based on detected capabilities
|
|
867
886
|
*/
|
|
@@ -871,11 +890,19 @@ export class WebPlayer extends BasePlayer {
|
|
|
871
890
|
// Detect capabilities first
|
|
872
891
|
await this.detectAutoplayCapabilities();
|
|
873
892
|
|
|
874
|
-
//
|
|
875
|
-
|
|
893
|
+
// Check if user has activated the page (navigation counts as activation)
|
|
894
|
+
const hasActivation = this.hasUserActivation();
|
|
895
|
+
|
|
896
|
+
// Try unmuted autoplay if:
|
|
897
|
+
// 1. Browser supports unmuted autoplay OR user has activated the page
|
|
898
|
+
// 2. User hasn't explicitly set muted=true
|
|
899
|
+
const shouldTryUnmuted = (this.autoplayCapabilities.canAutoplayUnmuted || hasActivation)
|
|
900
|
+
&& this.config.muted !== true;
|
|
901
|
+
|
|
902
|
+
if (shouldTryUnmuted) {
|
|
876
903
|
this.video.muted = false;
|
|
877
904
|
this.video.volume = this.config.volume ?? 1.0;
|
|
878
|
-
this.debugLog(
|
|
905
|
+
this.debugLog(`🔊 Attempting unmuted autoplay (activation: ${hasActivation})`);
|
|
879
906
|
|
|
880
907
|
try {
|
|
881
908
|
await this.play();
|
|
@@ -887,7 +914,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
887
914
|
}
|
|
888
915
|
|
|
889
916
|
// Fall back to muted autoplay
|
|
890
|
-
if (this.autoplayCapabilities.canAutoplayMuted) {
|
|
917
|
+
if (this.autoplayCapabilities.canAutoplayMuted || hasActivation) {
|
|
891
918
|
this.video.muted = true;
|
|
892
919
|
this.debugLog('🔇 Attempting muted autoplay');
|
|
893
920
|
|
|
@@ -1314,20 +1341,41 @@ export class WebPlayer extends BasePlayer {
|
|
|
1314
1341
|
if (!this.playerWrapper) return;
|
|
1315
1342
|
|
|
1316
1343
|
try {
|
|
1317
|
-
//
|
|
1318
|
-
if (
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1344
|
+
// iOS Safari special handling - use video element fullscreen
|
|
1345
|
+
if (this.isIOSDevice() && this.video) {
|
|
1346
|
+
this.debugLog('iOS device detected - using video element fullscreen');
|
|
1347
|
+
|
|
1348
|
+
try {
|
|
1349
|
+
// iOS Safari supports video fullscreen but not element fullscreen
|
|
1350
|
+
if ((this.video as any).webkitEnterFullscreen) {
|
|
1351
|
+
await (this.video as any).webkitEnterFullscreen();
|
|
1352
|
+
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1353
|
+
this.emit('onFullscreenChanged', true);
|
|
1354
|
+
return;
|
|
1355
|
+
} else if ((this.video as any).webkitRequestFullscreen) {
|
|
1356
|
+
await (this.video as any).webkitRequestFullscreen();
|
|
1357
|
+
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1358
|
+
this.emit('onFullscreenChanged', true);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
} catch (iosError) {
|
|
1362
|
+
this.debugWarn('iOS video fullscreen failed:', (iosError as Error).message);
|
|
1363
|
+
// Fall through to try standard fullscreen
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Check if fullscreen is supported for non-iOS devices
|
|
1368
|
+
if (!this.isFullscreenSupported()) {
|
|
1322
1369
|
this.debugWarn('Fullscreen not supported by browser');
|
|
1370
|
+
// On mobile devices that don't support fullscreen, show a helpful message
|
|
1371
|
+
if (this.isMobileDevice()) {
|
|
1372
|
+
this.showShortcutIndicator('Rotate device for fullscreen experience');
|
|
1373
|
+
}
|
|
1323
1374
|
return;
|
|
1324
1375
|
}
|
|
1325
1376
|
|
|
1326
1377
|
// Check if already in fullscreen
|
|
1327
|
-
if (
|
|
1328
|
-
(document as any).webkitFullscreenElement ||
|
|
1329
|
-
(document as any).mozFullScreenElement ||
|
|
1330
|
-
(document as any).msFullscreenElement) {
|
|
1378
|
+
if (this.isFullscreen()) {
|
|
1331
1379
|
this.debugLog('Already in fullscreen mode');
|
|
1332
1380
|
return;
|
|
1333
1381
|
}
|
|
@@ -1335,31 +1383,63 @@ export class WebPlayer extends BasePlayer {
|
|
|
1335
1383
|
// Target the player wrapper to maintain custom controls
|
|
1336
1384
|
const element = this.playerWrapper;
|
|
1337
1385
|
|
|
1386
|
+
// Try different fullscreen APIs with better error handling
|
|
1387
|
+
let fullscreenSuccess = false;
|
|
1388
|
+
|
|
1338
1389
|
if (element.requestFullscreen) {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
})
|
|
1390
|
+
try {
|
|
1391
|
+
await element.requestFullscreen();
|
|
1392
|
+
fullscreenSuccess = true;
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
this.debugWarn('Standard fullscreen request failed:', (err as Error).message);
|
|
1395
|
+
}
|
|
1343
1396
|
} else if ((element as any).webkitRequestFullscreen) {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1397
|
+
try {
|
|
1398
|
+
await (element as any).webkitRequestFullscreen();
|
|
1399
|
+
fullscreenSuccess = true;
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
this.debugWarn('WebKit fullscreen request failed:', (err as Error).message);
|
|
1402
|
+
}
|
|
1347
1403
|
} else if ((element as any).mozRequestFullScreen) {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1404
|
+
try {
|
|
1405
|
+
await (element as any).mozRequestFullScreen();
|
|
1406
|
+
fullscreenSuccess = true;
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
this.debugWarn('Mozilla fullscreen request failed:', (err as Error).message);
|
|
1409
|
+
}
|
|
1351
1410
|
} else if ((element as any).msRequestFullscreen) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1411
|
+
try {
|
|
1412
|
+
await (element as any).msRequestFullscreen();
|
|
1413
|
+
fullscreenSuccess = true;
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
this.debugWarn('MS fullscreen request failed:', (err as Error).message);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if (fullscreenSuccess) {
|
|
1420
|
+
// Add fullscreen class for styling
|
|
1421
|
+
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1422
|
+
this.emit('onFullscreenChanged', true);
|
|
1423
|
+
|
|
1424
|
+
// On Android, suggest orientation for better experience
|
|
1425
|
+
if (this.isAndroidDevice()) {
|
|
1426
|
+
setTimeout(() => {
|
|
1427
|
+
this.showShortcutIndicator('Rotate device to landscape for best experience');
|
|
1428
|
+
}, 1000);
|
|
1429
|
+
}
|
|
1355
1430
|
} else {
|
|
1356
|
-
this.debugWarn('
|
|
1357
|
-
|
|
1431
|
+
this.debugWarn('All fullscreen methods failed');
|
|
1432
|
+
|
|
1433
|
+
// Provide helpful feedback based on device
|
|
1434
|
+
if (this.isIOSDevice()) {
|
|
1435
|
+
this.showShortcutIndicator('Fullscreen not available - use device controls');
|
|
1436
|
+
} else if (this.isAndroidDevice()) {
|
|
1437
|
+
this.showShortcutIndicator('Try rotating device to landscape');
|
|
1438
|
+
} else {
|
|
1439
|
+
this.showShortcutIndicator('Fullscreen not supported in this browser');
|
|
1440
|
+
}
|
|
1358
1441
|
}
|
|
1359
1442
|
|
|
1360
|
-
// Add fullscreen class for styling
|
|
1361
|
-
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1362
|
-
this.emit('onFullscreenChanged', true);
|
|
1363
1443
|
} catch (error) {
|
|
1364
1444
|
this.debugWarn('Failed to enter fullscreen:', (error as Error).message);
|
|
1365
1445
|
// Don't re-throw the error to prevent breaking the user experience
|
|
@@ -1368,38 +1448,76 @@ export class WebPlayer extends BasePlayer {
|
|
|
1368
1448
|
|
|
1369
1449
|
async exitFullscreen(): Promise<void> {
|
|
1370
1450
|
try {
|
|
1451
|
+
// iOS Safari special handling
|
|
1452
|
+
if (this.isIOSDevice() && this.video) {
|
|
1453
|
+
try {
|
|
1454
|
+
if ((this.video as any).webkitExitFullscreen) {
|
|
1455
|
+
await (this.video as any).webkitExitFullscreen();
|
|
1456
|
+
if (this.playerWrapper) {
|
|
1457
|
+
this.playerWrapper.classList.remove('uvf-fullscreen');
|
|
1458
|
+
}
|
|
1459
|
+
this.emit('onFullscreenChanged', false);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
} catch (iosError) {
|
|
1463
|
+
this.debugWarn('iOS video exit fullscreen failed:', (iosError as Error).message);
|
|
1464
|
+
// Fall through to try standard methods
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1371
1468
|
// Check if we're actually in fullscreen
|
|
1372
|
-
if (!
|
|
1373
|
-
!(document as any).webkitFullscreenElement &&
|
|
1374
|
-
!(document as any).mozFullScreenElement &&
|
|
1375
|
-
!(document as any).msFullscreenElement) {
|
|
1469
|
+
if (!this.isFullscreen()) {
|
|
1376
1470
|
this.debugLog('Not in fullscreen mode');
|
|
1377
1471
|
return;
|
|
1378
1472
|
}
|
|
1379
1473
|
|
|
1474
|
+
// Try different exit fullscreen methods
|
|
1475
|
+
let exitSuccess = false;
|
|
1476
|
+
|
|
1380
1477
|
if (document.exitFullscreen) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1478
|
+
try {
|
|
1479
|
+
await document.exitFullscreen();
|
|
1480
|
+
exitSuccess = true;
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
this.debugWarn('Standard exit fullscreen failed:', (err as Error).message);
|
|
1483
|
+
}
|
|
1384
1484
|
} else if ((document as any).webkitExitFullscreen) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1485
|
+
try {
|
|
1486
|
+
await (document as any).webkitExitFullscreen();
|
|
1487
|
+
exitSuccess = true;
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
this.debugWarn('WebKit exit fullscreen failed:', (err as Error).message);
|
|
1490
|
+
}
|
|
1388
1491
|
} else if ((document as any).mozCancelFullScreen) {
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1492
|
+
try {
|
|
1493
|
+
await (document as any).mozCancelFullScreen();
|
|
1494
|
+
exitSuccess = true;
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
this.debugWarn('Mozilla exit fullscreen failed:', (err as Error).message);
|
|
1497
|
+
}
|
|
1392
1498
|
} else if ((document as any).msExitFullscreen) {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1499
|
+
try {
|
|
1500
|
+
await (document as any).msExitFullscreen();
|
|
1501
|
+
exitSuccess = true;
|
|
1502
|
+
} catch (err) {
|
|
1503
|
+
this.debugWarn('MS exit fullscreen failed:', (err as Error).message);
|
|
1504
|
+
}
|
|
1396
1505
|
}
|
|
1397
1506
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
this.playerWrapper
|
|
1507
|
+
if (exitSuccess || !this.isFullscreen()) {
|
|
1508
|
+
// Remove fullscreen class
|
|
1509
|
+
if (this.playerWrapper) {
|
|
1510
|
+
this.playerWrapper.classList.remove('uvf-fullscreen');
|
|
1511
|
+
}
|
|
1512
|
+
this.emit('onFullscreenChanged', false);
|
|
1513
|
+
} else {
|
|
1514
|
+
this.debugWarn('All exit fullscreen methods failed');
|
|
1515
|
+
// Still remove the class to keep UI consistent
|
|
1516
|
+
if (this.playerWrapper) {
|
|
1517
|
+
this.playerWrapper.classList.remove('uvf-fullscreen');
|
|
1518
|
+
}
|
|
1401
1519
|
}
|
|
1402
|
-
|
|
1520
|
+
|
|
1403
1521
|
} catch (error) {
|
|
1404
1522
|
this.debugWarn('Failed to exit fullscreen:', (error as Error).message);
|
|
1405
1523
|
// Don't re-throw the error to prevent breaking the user experience
|
|
@@ -4133,7 +4251,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4133
4251
|
}
|
|
4134
4252
|
}
|
|
4135
4253
|
|
|
4136
|
-
/* iOS Safari specific fixes - address bar handling */
|
|
4254
|
+
/* iOS Safari specific fixes - address bar handling and control positioning */
|
|
4137
4255
|
@supports (-webkit-appearance: none) {
|
|
4138
4256
|
.uvf-player-wrapper.uvf-fullscreen,
|
|
4139
4257
|
.uvf-video-container.uvf-fullscreen {
|
|
@@ -4151,6 +4269,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4151
4269
|
.uvf-player-wrapper {
|
|
4152
4270
|
height: -webkit-fill-available;
|
|
4153
4271
|
min-height: 100vh;
|
|
4272
|
+
/* Fix for iOS Safari control overlay positioning */
|
|
4273
|
+
position: relative;
|
|
4274
|
+
overflow: hidden;
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
/* iOS Safari specific fixes for control positioning */
|
|
4278
|
+
.uvf-controls-bar {
|
|
4279
|
+
position: absolute !important;
|
|
4280
|
+
bottom: 0 !important;
|
|
4281
|
+
left: 0 !important;
|
|
4282
|
+
right: 0 !important;
|
|
4283
|
+
/* Ensure hardware acceleration */
|
|
4284
|
+
-webkit-transform: translate3d(0,0,0);
|
|
4285
|
+
transform: translate3d(0,0,0);
|
|
4286
|
+
/* Prevent any webkit transforms that could cause positioning issues */
|
|
4287
|
+
-webkit-perspective: 1000;
|
|
4288
|
+
perspective: 1000;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
/* Ensure all control elements use hardware acceleration */
|
|
4292
|
+
.uvf-control-btn,
|
|
4293
|
+
.uvf-progress-bar,
|
|
4294
|
+
.uvf-progress-section {
|
|
4295
|
+
-webkit-transform: translateZ(0);
|
|
4296
|
+
transform: translateZ(0);
|
|
4154
4297
|
}
|
|
4155
4298
|
}
|
|
4156
4299
|
}
|
|
@@ -4211,14 +4354,45 @@ export class WebPlayer extends BasePlayer {
|
|
|
4211
4354
|
|
|
4212
4355
|
/* Fix for controls being cut off by virtual keyboard */
|
|
4213
4356
|
.uvf-controls-bar {
|
|
4214
|
-
position:
|
|
4215
|
-
bottom:
|
|
4357
|
+
position: absolute !important;
|
|
4358
|
+
bottom: 0 !important;
|
|
4359
|
+
left: 0 !important;
|
|
4360
|
+
right: 0 !important;
|
|
4361
|
+
/* Remove fixed positioning that causes issues on iOS Safari */
|
|
4362
|
+
z-index: 1000 !important;
|
|
4363
|
+
transform: translateZ(0); /* Force hardware acceleration */
|
|
4216
4364
|
}
|
|
4217
4365
|
|
|
4218
4366
|
/* Ensure controls stay above virtual keyboards */
|
|
4219
4367
|
@supports (bottom: env(keyboard-inset-height)) {
|
|
4220
4368
|
.uvf-controls-bar {
|
|
4221
|
-
bottom: max(
|
|
4369
|
+
bottom: max(0px, env(keyboard-inset-height, 0)) !important;
|
|
4370
|
+
padding-bottom: calc(16px + max(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0))) !important;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
/* Hide PiP button on mobile - not supported on most mobile browsers */
|
|
4375
|
+
#uvf-pip-btn {
|
|
4376
|
+
display: none !important;
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
/* Mobile fullscreen enhancements */
|
|
4380
|
+
.uvf-player-wrapper.uvf-fullscreen {
|
|
4381
|
+
/* Ensure fullscreen covers entire viewport on mobile */
|
|
4382
|
+
position: fixed !important;
|
|
4383
|
+
top: 0 !important;
|
|
4384
|
+
left: 0 !important;
|
|
4385
|
+
width: 100vw !important;
|
|
4386
|
+
height: 100vh !important;
|
|
4387
|
+
z-index: 2147483647 !important;
|
|
4388
|
+
background: #000 !important;
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
/* iOS Safari specific fullscreen fixes */
|
|
4392
|
+
@supports (-webkit-appearance: none) {
|
|
4393
|
+
.uvf-player-wrapper.uvf-fullscreen {
|
|
4394
|
+
/* Use viewport units that work better with iOS Safari */
|
|
4395
|
+
height: -webkit-fill-available !important;
|
|
4222
4396
|
}
|
|
4223
4397
|
}
|
|
4224
4398
|
}
|
|
@@ -4260,19 +4434,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
4260
4434
|
min-height: inherit;
|
|
4261
4435
|
}
|
|
4262
4436
|
|
|
4263
|
-
/* Enhanced mobile controls bar with safe area padding */
|
|
4437
|
+
/* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
|
|
4264
4438
|
.uvf-controls-bar {
|
|
4265
|
-
position: absolute;
|
|
4266
|
-
bottom: 0;
|
|
4267
|
-
left: 0;
|
|
4268
|
-
right: 0;
|
|
4439
|
+
position: absolute !important;
|
|
4440
|
+
bottom: 0 !important;
|
|
4441
|
+
left: 0 !important;
|
|
4442
|
+
right: 0 !important;
|
|
4269
4443
|
padding: 16px 12px;
|
|
4270
|
-
padding-bottom: calc(16px + var(--uvf-safe-area-bottom));
|
|
4271
|
-
padding-left: calc(12px + var(--uvf-safe-area-left));
|
|
4272
|
-
padding-right: calc(12px + var(--uvf-safe-area-right));
|
|
4444
|
+
padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
|
|
4445
|
+
padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
|
|
4446
|
+
padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
|
|
4273
4447
|
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
|
|
4274
4448
|
box-sizing: border-box;
|
|
4275
|
-
z-index: 1000;
|
|
4449
|
+
z-index: 1000 !important;
|
|
4450
|
+
/* iOS Safari specific fixes */
|
|
4451
|
+
transform: translateZ(0);
|
|
4452
|
+
-webkit-transform: translateZ(0);
|
|
4453
|
+
will-change: transform;
|
|
4454
|
+
/* Ensure proper stacking */
|
|
4455
|
+
isolation: isolate;
|
|
4276
4456
|
}
|
|
4277
4457
|
|
|
4278
4458
|
.uvf-progress-section {
|
|
@@ -5775,12 +5955,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
5775
5955
|
epgBtn.style.display = 'none'; // Initially hidden, will be shown when EPG data is available
|
|
5776
5956
|
rightControls.appendChild(epgBtn);
|
|
5777
5957
|
|
|
5778
|
-
// PiP button
|
|
5958
|
+
// PiP button - only show on desktop/supported browsers
|
|
5779
5959
|
const pipBtn = document.createElement('button');
|
|
5780
5960
|
pipBtn.className = 'uvf-control-btn';
|
|
5781
5961
|
pipBtn.id = 'uvf-pip-btn';
|
|
5782
5962
|
pipBtn.title = 'Picture-in-Picture';
|
|
5783
5963
|
pipBtn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z" stroke="currentColor" stroke-width="0.5" fill="currentColor"/></svg>';
|
|
5964
|
+
|
|
5965
|
+
// Hide PiP button on mobile devices and browsers that don't support it
|
|
5966
|
+
if (this.isMobileDevice() || !this.isPipSupported()) {
|
|
5967
|
+
pipBtn.style.display = 'none';
|
|
5968
|
+
}
|
|
5969
|
+
|
|
5784
5970
|
rightControls.appendChild(pipBtn);
|
|
5785
5971
|
|
|
5786
5972
|
// Fullscreen button
|
|
@@ -6080,19 +6266,26 @@ export class WebPlayer extends BasePlayer {
|
|
|
6080
6266
|
}
|
|
6081
6267
|
});
|
|
6082
6268
|
|
|
6083
|
-
// Fullscreen button with enhanced
|
|
6269
|
+
// Fullscreen button with enhanced cross-platform support
|
|
6084
6270
|
fullscreenBtn?.addEventListener('click', (event) => {
|
|
6085
|
-
// Enhanced debugging for
|
|
6271
|
+
// Enhanced debugging for all platforms
|
|
6086
6272
|
const isBrave = this.isBraveBrowser();
|
|
6087
6273
|
const isPrivate = this.isPrivateWindow();
|
|
6274
|
+
const isIOS = this.isIOSDevice();
|
|
6275
|
+
const isAndroid = this.isAndroidDevice();
|
|
6276
|
+
const isMobile = this.isMobileDevice();
|
|
6088
6277
|
|
|
6089
6278
|
this.debugLog('Fullscreen button clicked:', {
|
|
6090
6279
|
isBrave,
|
|
6091
6280
|
isPrivate,
|
|
6281
|
+
isIOS,
|
|
6282
|
+
isAndroid,
|
|
6283
|
+
isMobile,
|
|
6092
6284
|
isFullscreen: this.isFullscreen(),
|
|
6093
6285
|
eventTrusted: event.isTrusted,
|
|
6094
6286
|
eventType: event.type,
|
|
6095
|
-
timestamp: Date.now()
|
|
6287
|
+
timestamp: Date.now(),
|
|
6288
|
+
fullscreenSupported: this.isFullscreenSupported()
|
|
6096
6289
|
});
|
|
6097
6290
|
|
|
6098
6291
|
// Update user interaction timestamp
|
|
@@ -6109,22 +6302,28 @@ export class WebPlayer extends BasePlayer {
|
|
|
6109
6302
|
} else {
|
|
6110
6303
|
this.debugLog('Entering fullscreen via button');
|
|
6111
6304
|
|
|
6112
|
-
//
|
|
6113
|
-
if (
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
|
|
6118
|
-
});
|
|
6119
|
-
} else {
|
|
6120
|
-
this.enterFullscreen().catch(err => {
|
|
6121
|
-
this.debugWarn('Fullscreen button failed:', err.message);
|
|
6122
|
-
|
|
6123
|
-
if (isBrave) {
|
|
6124
|
-
this.showTemporaryMessage('Try refreshing the page or check Brave shields settings');
|
|
6125
|
-
}
|
|
6126
|
-
});
|
|
6305
|
+
// iOS Safari special message
|
|
6306
|
+
if (isIOS) {
|
|
6307
|
+
this.showShortcutIndicator('Using iOS video fullscreen');
|
|
6308
|
+
} else if (isAndroid) {
|
|
6309
|
+
this.showShortcutIndicator('Entering fullscreen - rotate to landscape');
|
|
6127
6310
|
}
|
|
6311
|
+
|
|
6312
|
+
// Use enhanced cross-platform fullscreen method
|
|
6313
|
+
this.enterFullscreen().catch(err => {
|
|
6314
|
+
this.debugWarn('Fullscreen button failed:', err.message);
|
|
6315
|
+
|
|
6316
|
+
// Platform-specific error messages
|
|
6317
|
+
if (isIOS) {
|
|
6318
|
+
this.showTemporaryMessage('iOS: Use device rotation or video controls for fullscreen');
|
|
6319
|
+
} else if (isAndroid) {
|
|
6320
|
+
this.showTemporaryMessage('Android: Try rotating device to landscape mode');
|
|
6321
|
+
} else if (isBrave) {
|
|
6322
|
+
this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
|
|
6323
|
+
} else {
|
|
6324
|
+
this.showTemporaryMessage('Fullscreen not supported in this browser');
|
|
6325
|
+
}
|
|
6326
|
+
});
|
|
6128
6327
|
}
|
|
6129
6328
|
});
|
|
6130
6329
|
|
|
@@ -6762,6 +6961,58 @@ export class WebPlayer extends BasePlayer {
|
|
|
6762
6961
|
}
|
|
6763
6962
|
}
|
|
6764
6963
|
|
|
6964
|
+
/**
|
|
6965
|
+
* Detect if user is on a mobile device
|
|
6966
|
+
*/
|
|
6967
|
+
private isMobileDevice(): boolean {
|
|
6968
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
6969
|
+
const mobileKeywords = ['android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone', 'mobile'];
|
|
6970
|
+
const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
|
|
6971
|
+
const isSmallScreen = window.innerWidth <= 768;
|
|
6972
|
+
const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
6973
|
+
|
|
6974
|
+
return isMobileUserAgent || (isSmallScreen && hasTouchScreen);
|
|
6975
|
+
}
|
|
6976
|
+
|
|
6977
|
+
/**
|
|
6978
|
+
* Check if Picture-in-Picture is supported by the browser
|
|
6979
|
+
*/
|
|
6980
|
+
private isPipSupported(): boolean {
|
|
6981
|
+
return !!(
|
|
6982
|
+
document.pictureInPictureEnabled &&
|
|
6983
|
+
HTMLVideoElement.prototype.requestPictureInPicture &&
|
|
6984
|
+
typeof HTMLVideoElement.prototype.requestPictureInPicture === 'function'
|
|
6985
|
+
);
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6988
|
+
/**
|
|
6989
|
+
* Detect if user is on iOS device
|
|
6990
|
+
*/
|
|
6991
|
+
private isIOSDevice(): boolean {
|
|
6992
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
6993
|
+
return /iphone|ipad|ipod/.test(userAgent);
|
|
6994
|
+
}
|
|
6995
|
+
|
|
6996
|
+
/**
|
|
6997
|
+
* Detect if user is on Android device
|
|
6998
|
+
*/
|
|
6999
|
+
private isAndroidDevice(): boolean {
|
|
7000
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
7001
|
+
return /android/.test(userAgent);
|
|
7002
|
+
}
|
|
7003
|
+
|
|
7004
|
+
/**
|
|
7005
|
+
* Check if fullscreen is supported on current platform
|
|
7006
|
+
*/
|
|
7007
|
+
private isFullscreenSupported(): boolean {
|
|
7008
|
+
return !!(
|
|
7009
|
+
document.fullscreenEnabled ||
|
|
7010
|
+
(document as any).webkitFullscreenEnabled ||
|
|
7011
|
+
(document as any).mozFullScreenEnabled ||
|
|
7012
|
+
(document as any).msFullscreenEnabled
|
|
7013
|
+
);
|
|
7014
|
+
}
|
|
7015
|
+
|
|
6765
7016
|
private handleVolumeChange(e: MouseEvent): void {
|
|
6766
7017
|
const slider = document.getElementById('uvf-volume-slider');
|
|
6767
7018
|
if (!slider) return;
|