unified-video-framework 1.4.158 → 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.
Files changed (67) hide show
  1. package/package.json +12 -2
  2. package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts +18 -0
  3. package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts.map +1 -0
  4. package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js +117 -0
  5. package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js.map +1 -0
  6. package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts +18 -0
  7. package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts.map +1 -0
  8. package/packages/core/dist/analytics/core/AnalyticsProvider.js +99 -0
  9. package/packages/core/dist/analytics/core/AnalyticsProvider.js.map +1 -0
  10. package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts +20 -0
  11. package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts.map +1 -0
  12. package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js +161 -0
  13. package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js.map +1 -0
  14. package/packages/core/dist/analytics/core/EventBatcher.d.ts +32 -0
  15. package/packages/core/dist/analytics/core/EventBatcher.d.ts.map +1 -0
  16. package/packages/core/dist/analytics/core/EventBatcher.js +98 -0
  17. package/packages/core/dist/analytics/core/EventBatcher.js.map +1 -0
  18. package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts +19 -0
  19. package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts.map +1 -0
  20. package/packages/core/dist/analytics/core/PlayerAnalytics.js +80 -0
  21. package/packages/core/dist/analytics/core/PlayerAnalytics.js.map +1 -0
  22. package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts +32 -0
  23. package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts.map +1 -0
  24. package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js +220 -0
  25. package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js.map +1 -0
  26. package/packages/core/dist/analytics/index.d.ts +13 -0
  27. package/packages/core/dist/analytics/index.d.ts.map +1 -0
  28. package/packages/core/dist/analytics/index.js +13 -0
  29. package/packages/core/dist/analytics/index.js.map +1 -0
  30. package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts +239 -0
  31. package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts.map +1 -0
  32. package/packages/core/dist/analytics/types/AnalyticsTypes.js +8 -0
  33. package/packages/core/dist/analytics/types/AnalyticsTypes.js.map +1 -0
  34. package/packages/core/dist/analytics/utils/DeviceDetection.d.ts +27 -0
  35. package/packages/core/dist/analytics/utils/DeviceDetection.d.ts.map +1 -0
  36. package/packages/core/dist/analytics/utils/DeviceDetection.js +184 -0
  37. package/packages/core/dist/analytics/utils/DeviceDetection.js.map +1 -0
  38. package/packages/core/dist/chapter-manager.d.ts +39 -0
  39. package/packages/core/dist/index.d.ts +1 -0
  40. package/packages/core/dist/index.d.ts.map +1 -1
  41. package/packages/core/dist/index.js +1 -0
  42. package/packages/core/dist/index.js.map +1 -1
  43. package/packages/core/src/analytics/README.md +902 -0
  44. package/packages/core/src/analytics/adapters/PlayerAnalyticsAdapter.ts +156 -0
  45. package/packages/core/src/analytics/core/AnalyticsProvider.ts +169 -0
  46. package/packages/core/src/analytics/core/DynamicAnalyticsManager.ts +199 -0
  47. package/packages/core/src/analytics/core/EventBatcher.ts +160 -0
  48. package/packages/core/src/analytics/core/PlayerAnalytics.ts +147 -0
  49. package/packages/core/src/analytics/index.ts +51 -0
  50. package/packages/core/src/analytics/types/AnalyticsTypes.ts +315 -0
  51. package/packages/core/src/analytics/utils/DeviceDetection.ts +220 -0
  52. package/packages/core/src/index.ts +3 -0
  53. package/packages/ios/README.md +84 -0
  54. package/packages/web/dist/WebPlayer.d.ts +5 -0
  55. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  56. package/packages/web/dist/WebPlayer.js +255 -63
  57. package/packages/web/dist/WebPlayer.js.map +1 -1
  58. package/packages/web/dist/epg/EPGController.d.ts +78 -0
  59. package/packages/web/dist/epg/EPGController.d.ts.map +1 -0
  60. package/packages/web/dist/epg/EPGController.js +476 -0
  61. package/packages/web/dist/epg/EPGController.js.map +1 -0
  62. package/packages/web/src/WebPlayer.ts +303 -79
  63. package/src/analytics/README.md +902 -0
  64. package/src/analytics/adapters/PlayerAnalyticsAdapter.ts +572 -0
  65. package/src/analytics/core/DynamicAnalyticsManager.ts +526 -0
  66. package/src/analytics/examples/DynamicAnalyticsExample.ts +324 -0
  67. package/src/analytics/index.ts +60 -0
@@ -1341,20 +1341,41 @@ export class WebPlayer extends BasePlayer {
1341
1341
  if (!this.playerWrapper) return;
1342
1342
 
1343
1343
  try {
1344
- // Check if fullscreen is supported
1345
- if (!document.fullscreenEnabled &&
1346
- !(document as any).webkitFullscreenEnabled &&
1347
- !(document as any).mozFullScreenEnabled &&
1348
- !(document as any).msFullscreenEnabled) {
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 (document.fullscreenElement ||
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
- await element.requestFullscreen().catch(err => {
1367
- this.debugWarn('Fullscreen request failed:', err.message);
1368
- // Don't throw, just log the error
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
- await (element as any).webkitRequestFullscreen().catch((err: any) => {
1372
- this.debugWarn('WebKit fullscreen request failed:', err.message);
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
- await (element as any).mozRequestFullScreen().catch((err: any) => {
1376
- this.debugWarn('Mozilla fullscreen request failed:', err.message);
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
- await (element as any).msRequestFullscreen().catch((err: any) => {
1380
- this.debugWarn('MS fullscreen request failed:', err.message);
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('Fullscreen API not supported by this browser');
1384
- return;
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 (!document.fullscreenElement &&
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
- await document.exitFullscreen().catch(err => {
1409
- this.debugWarn('Exit fullscreen failed:', err.message);
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
- await (document as any).webkitExitFullscreen().catch((err: any) => {
1413
- this.debugWarn('WebKit exit fullscreen failed:', err.message);
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
- await (document as any).mozCancelFullScreen().catch((err: any) => {
1417
- this.debugWarn('Mozilla exit fullscreen failed:', err.message);
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
- await (document as any).msExitFullscreen().catch((err: any) => {
1421
- this.debugWarn('MS exit fullscreen failed:', err.message);
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
- // Remove fullscreen class
1426
- if (this.playerWrapper) {
1427
- this.playerWrapper.classList.remove('uvf-fullscreen');
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
- this.emit('onFullscreenChanged', false);
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: fixed !important;
4242
- bottom: var(--uvf-safe-area-bottom, 0) !important;
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(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0)) !important;
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 Brave support
6269
+ // Fullscreen button with enhanced cross-platform support
6111
6270
  fullscreenBtn?.addEventListener('click', (event) => {
6112
- // Enhanced debugging for Brave browser
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
- // Special handling for Brave browser
6140
- if (isBrave && !isPrivate) {
6141
- // For Brave normal tabs, try direct API call first
6142
- this.enterFullscreenWithBraveSupport().catch(err => {
6143
- this.debugWarn('Brave fullscreen button failed:', err.message);
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;