unified-video-framework 1.4.158 → 1.4.160
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 +11 -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 +5 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +255 -63
- 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 +303 -79
|
@@ -1341,20 +1341,41 @@ export class WebPlayer extends BasePlayer {
|
|
|
1341
1341
|
if (!this.playerWrapper) return;
|
|
1342
1342
|
|
|
1343
1343
|
try {
|
|
1344
|
-
//
|
|
1345
|
-
if (
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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()) {
|
|
1349
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
|
+
}
|
|
1350
1374
|
return;
|
|
1351
1375
|
}
|
|
1352
1376
|
|
|
1353
1377
|
// Check if already in fullscreen
|
|
1354
|
-
if (
|
|
1355
|
-
(document as any).webkitFullscreenElement ||
|
|
1356
|
-
(document as any).mozFullScreenElement ||
|
|
1357
|
-
(document as any).msFullscreenElement) {
|
|
1378
|
+
if (this.isFullscreen()) {
|
|
1358
1379
|
this.debugLog('Already in fullscreen mode');
|
|
1359
1380
|
return;
|
|
1360
1381
|
}
|
|
@@ -1362,31 +1383,63 @@ export class WebPlayer extends BasePlayer {
|
|
|
1362
1383
|
// Target the player wrapper to maintain custom controls
|
|
1363
1384
|
const element = this.playerWrapper;
|
|
1364
1385
|
|
|
1386
|
+
// Try different fullscreen APIs with better error handling
|
|
1387
|
+
let fullscreenSuccess = false;
|
|
1388
|
+
|
|
1365
1389
|
if (element.requestFullscreen) {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
})
|
|
1390
|
+
try {
|
|
1391
|
+
await element.requestFullscreen();
|
|
1392
|
+
fullscreenSuccess = true;
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
this.debugWarn('Standard fullscreen request failed:', (err as Error).message);
|
|
1395
|
+
}
|
|
1370
1396
|
} else if ((element as any).webkitRequestFullscreen) {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
+
}
|
|
1374
1403
|
} else if ((element as any).mozRequestFullScreen) {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
+
}
|
|
1378
1410
|
} else if ((element as any).msRequestFullscreen) {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
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
|
+
}
|
|
1382
1430
|
} else {
|
|
1383
|
-
this.debugWarn('
|
|
1384
|
-
|
|
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
|
+
}
|
|
1385
1441
|
}
|
|
1386
1442
|
|
|
1387
|
-
// Add fullscreen class for styling
|
|
1388
|
-
this.playerWrapper.classList.add('uvf-fullscreen');
|
|
1389
|
-
this.emit('onFullscreenChanged', true);
|
|
1390
1443
|
} catch (error) {
|
|
1391
1444
|
this.debugWarn('Failed to enter fullscreen:', (error as Error).message);
|
|
1392
1445
|
// Don't re-throw the error to prevent breaking the user experience
|
|
@@ -1395,38 +1448,76 @@ export class WebPlayer extends BasePlayer {
|
|
|
1395
1448
|
|
|
1396
1449
|
async exitFullscreen(): Promise<void> {
|
|
1397
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
|
+
|
|
1398
1468
|
// Check if we're actually in fullscreen
|
|
1399
|
-
if (!
|
|
1400
|
-
!(document as any).webkitFullscreenElement &&
|
|
1401
|
-
!(document as any).mozFullScreenElement &&
|
|
1402
|
-
!(document as any).msFullscreenElement) {
|
|
1469
|
+
if (!this.isFullscreen()) {
|
|
1403
1470
|
this.debugLog('Not in fullscreen mode');
|
|
1404
1471
|
return;
|
|
1405
1472
|
}
|
|
1406
1473
|
|
|
1474
|
+
// Try different exit fullscreen methods
|
|
1475
|
+
let exitSuccess = false;
|
|
1476
|
+
|
|
1407
1477
|
if (document.exitFullscreen) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1478
|
+
try {
|
|
1479
|
+
await document.exitFullscreen();
|
|
1480
|
+
exitSuccess = true;
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
this.debugWarn('Standard exit fullscreen failed:', (err as Error).message);
|
|
1483
|
+
}
|
|
1411
1484
|
} else if ((document as any).webkitExitFullscreen) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
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
|
+
}
|
|
1415
1491
|
} else if ((document as any).mozCancelFullScreen) {
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
+
}
|
|
1419
1498
|
} else if ((document as any).msExitFullscreen) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
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
|
+
}
|
|
1423
1505
|
}
|
|
1424
1506
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
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
|
+
}
|
|
1428
1519
|
}
|
|
1429
|
-
|
|
1520
|
+
|
|
1430
1521
|
} catch (error) {
|
|
1431
1522
|
this.debugWarn('Failed to exit fullscreen:', (error as Error).message);
|
|
1432
1523
|
// Don't re-throw the error to prevent breaking the user experience
|
|
@@ -4160,7 +4251,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4160
4251
|
}
|
|
4161
4252
|
}
|
|
4162
4253
|
|
|
4163
|
-
/* iOS Safari specific fixes - address bar handling */
|
|
4254
|
+
/* iOS Safari specific fixes - address bar handling and control positioning */
|
|
4164
4255
|
@supports (-webkit-appearance: none) {
|
|
4165
4256
|
.uvf-player-wrapper.uvf-fullscreen,
|
|
4166
4257
|
.uvf-video-container.uvf-fullscreen {
|
|
@@ -4178,6 +4269,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
4178
4269
|
.uvf-player-wrapper {
|
|
4179
4270
|
height: -webkit-fill-available;
|
|
4180
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);
|
|
4181
4297
|
}
|
|
4182
4298
|
}
|
|
4183
4299
|
}
|
|
@@ -4238,14 +4354,45 @@ export class WebPlayer extends BasePlayer {
|
|
|
4238
4354
|
|
|
4239
4355
|
/* Fix for controls being cut off by virtual keyboard */
|
|
4240
4356
|
.uvf-controls-bar {
|
|
4241
|
-
position:
|
|
4242
|
-
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 */
|
|
4243
4364
|
}
|
|
4244
4365
|
|
|
4245
4366
|
/* Ensure controls stay above virtual keyboards */
|
|
4246
4367
|
@supports (bottom: env(keyboard-inset-height)) {
|
|
4247
4368
|
.uvf-controls-bar {
|
|
4248
|
-
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;
|
|
4249
4396
|
}
|
|
4250
4397
|
}
|
|
4251
4398
|
}
|
|
@@ -4287,19 +4434,25 @@ export class WebPlayer extends BasePlayer {
|
|
|
4287
4434
|
min-height: inherit;
|
|
4288
4435
|
}
|
|
4289
4436
|
|
|
4290
|
-
/* Enhanced mobile controls bar with safe area padding */
|
|
4437
|
+
/* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
|
|
4291
4438
|
.uvf-controls-bar {
|
|
4292
|
-
position: absolute;
|
|
4293
|
-
bottom: 0;
|
|
4294
|
-
left: 0;
|
|
4295
|
-
right: 0;
|
|
4439
|
+
position: absolute !important;
|
|
4440
|
+
bottom: 0 !important;
|
|
4441
|
+
left: 0 !important;
|
|
4442
|
+
right: 0 !important;
|
|
4296
4443
|
padding: 16px 12px;
|
|
4297
|
-
padding-bottom: calc(16px + var(--uvf-safe-area-bottom));
|
|
4298
|
-
padding-left: calc(12px + var(--uvf-safe-area-left));
|
|
4299
|
-
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));
|
|
4300
4447
|
background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
|
|
4301
4448
|
box-sizing: border-box;
|
|
4302
|
-
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;
|
|
4303
4456
|
}
|
|
4304
4457
|
|
|
4305
4458
|
.uvf-progress-section {
|
|
@@ -5802,12 +5955,18 @@ export class WebPlayer extends BasePlayer {
|
|
|
5802
5955
|
epgBtn.style.display = 'none'; // Initially hidden, will be shown when EPG data is available
|
|
5803
5956
|
rightControls.appendChild(epgBtn);
|
|
5804
5957
|
|
|
5805
|
-
// PiP button
|
|
5958
|
+
// PiP button - only show on desktop/supported browsers
|
|
5806
5959
|
const pipBtn = document.createElement('button');
|
|
5807
5960
|
pipBtn.className = 'uvf-control-btn';
|
|
5808
5961
|
pipBtn.id = 'uvf-pip-btn';
|
|
5809
5962
|
pipBtn.title = 'Picture-in-Picture';
|
|
5810
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
|
+
|
|
5811
5970
|
rightControls.appendChild(pipBtn);
|
|
5812
5971
|
|
|
5813
5972
|
// Fullscreen button
|
|
@@ -6107,19 +6266,26 @@ export class WebPlayer extends BasePlayer {
|
|
|
6107
6266
|
}
|
|
6108
6267
|
});
|
|
6109
6268
|
|
|
6110
|
-
// Fullscreen button with enhanced
|
|
6269
|
+
// Fullscreen button with enhanced cross-platform support
|
|
6111
6270
|
fullscreenBtn?.addEventListener('click', (event) => {
|
|
6112
|
-
// Enhanced debugging for
|
|
6271
|
+
// Enhanced debugging for all platforms
|
|
6113
6272
|
const isBrave = this.isBraveBrowser();
|
|
6114
6273
|
const isPrivate = this.isPrivateWindow();
|
|
6274
|
+
const isIOS = this.isIOSDevice();
|
|
6275
|
+
const isAndroid = this.isAndroidDevice();
|
|
6276
|
+
const isMobile = this.isMobileDevice();
|
|
6115
6277
|
|
|
6116
6278
|
this.debugLog('Fullscreen button clicked:', {
|
|
6117
6279
|
isBrave,
|
|
6118
6280
|
isPrivate,
|
|
6281
|
+
isIOS,
|
|
6282
|
+
isAndroid,
|
|
6283
|
+
isMobile,
|
|
6119
6284
|
isFullscreen: this.isFullscreen(),
|
|
6120
6285
|
eventTrusted: event.isTrusted,
|
|
6121
6286
|
eventType: event.type,
|
|
6122
|
-
timestamp: Date.now()
|
|
6287
|
+
timestamp: Date.now(),
|
|
6288
|
+
fullscreenSupported: this.isFullscreenSupported()
|
|
6123
6289
|
});
|
|
6124
6290
|
|
|
6125
6291
|
// Update user interaction timestamp
|
|
@@ -6136,22 +6302,28 @@ export class WebPlayer extends BasePlayer {
|
|
|
6136
6302
|
} else {
|
|
6137
6303
|
this.debugLog('Entering fullscreen via button');
|
|
6138
6304
|
|
|
6139
|
-
//
|
|
6140
|
-
if (
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
|
|
6145
|
-
});
|
|
6146
|
-
} else {
|
|
6147
|
-
this.enterFullscreen().catch(err => {
|
|
6148
|
-
this.debugWarn('Fullscreen button failed:', err.message);
|
|
6149
|
-
|
|
6150
|
-
if (isBrave) {
|
|
6151
|
-
this.showTemporaryMessage('Try refreshing the page or check Brave shields settings');
|
|
6152
|
-
}
|
|
6153
|
-
});
|
|
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');
|
|
6154
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
|
+
});
|
|
6155
6327
|
}
|
|
6156
6328
|
});
|
|
6157
6329
|
|
|
@@ -6789,6 +6961,58 @@ export class WebPlayer extends BasePlayer {
|
|
|
6789
6961
|
}
|
|
6790
6962
|
}
|
|
6791
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
|
+
|
|
6792
7016
|
private handleVolumeChange(e: MouseEvent): void {
|
|
6793
7017
|
const slider = document.getElementById('uvf-volume-slider');
|
|
6794
7018
|
if (!slider) return;
|