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.
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 +6 -0
  55. package/packages/web/dist/WebPlayer.d.ts.map +1 -1
  56. package/packages/web/dist/WebPlayer.js +273 -67
  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 +336 -85
  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
@@ -140,7 +140,7 @@ export class WebPlayer extends BasePlayer {
140
140
  this.video.className = 'uvf-video';
141
141
  this.video.controls = false;
142
142
  this.video.autoplay = false;
143
- this.video.muted = this.config.autoPlay ? true : (this.config.muted ?? false);
143
+ this.video.muted = this.config.muted ?? false;
144
144
  this.video.loop = this.config.loop ?? false;
145
145
  this.video.playsInline = this.config.playsInline ?? true;
146
146
  this.video.preload = this.config.preload ?? 'metadata';
@@ -660,14 +660,28 @@ export class WebPlayer extends BasePlayer {
660
660
  this.autoplayCapabilities.canAutoplay = true;
661
661
  }
662
662
  }
663
+ hasUserActivation() {
664
+ if (typeof navigator !== 'undefined' && navigator.userActivation) {
665
+ const hasActivation = navigator.userActivation.hasBeenActive;
666
+ this.debugLog(`🎯 User activation detected: ${hasActivation}`);
667
+ return hasActivation;
668
+ }
669
+ const hasInteracted = this.lastUserInteraction > 0 &&
670
+ (Date.now() - this.lastUserInteraction) < 5000;
671
+ this.debugLog(`🎯 Recent user interaction: ${hasInteracted}`);
672
+ return hasInteracted;
673
+ }
663
674
  async attemptIntelligentAutoplay() {
664
675
  if (!this.config.autoPlay || !this.video)
665
676
  return false;
666
677
  await this.detectAutoplayCapabilities();
667
- if (this.autoplayCapabilities.canAutoplayUnmuted && !this.config.muted) {
678
+ const hasActivation = this.hasUserActivation();
679
+ const shouldTryUnmuted = (this.autoplayCapabilities.canAutoplayUnmuted || hasActivation)
680
+ && this.config.muted !== true;
681
+ if (shouldTryUnmuted) {
668
682
  this.video.muted = false;
669
683
  this.video.volume = this.config.volume ?? 1.0;
670
- this.debugLog('🔊 Attempting unmuted autoplay');
684
+ this.debugLog(`🔊 Attempting unmuted autoplay (activation: ${hasActivation})`);
671
685
  try {
672
686
  await this.play();
673
687
  this.debugLog('✅ Unmuted autoplay successful');
@@ -677,7 +691,7 @@ export class WebPlayer extends BasePlayer {
677
691
  this.debugLog('⚠️ Unmuted autoplay failed, trying muted');
678
692
  }
679
693
  }
680
- if (this.autoplayCapabilities.canAutoplayMuted) {
694
+ if (this.autoplayCapabilities.canAutoplayMuted || hasActivation) {
681
695
  this.video.muted = true;
682
696
  this.debugLog('🔇 Attempting muted autoplay');
683
697
  try {
@@ -1036,47 +1050,96 @@ export class WebPlayer extends BasePlayer {
1036
1050
  if (!this.playerWrapper)
1037
1051
  return;
1038
1052
  try {
1039
- if (!document.fullscreenEnabled &&
1040
- !document.webkitFullscreenEnabled &&
1041
- !document.mozFullScreenEnabled &&
1042
- !document.msFullscreenEnabled) {
1053
+ if (this.isIOSDevice() && this.video) {
1054
+ this.debugLog('iOS device detected - using video element fullscreen');
1055
+ try {
1056
+ if (this.video.webkitEnterFullscreen) {
1057
+ await this.video.webkitEnterFullscreen();
1058
+ this.playerWrapper.classList.add('uvf-fullscreen');
1059
+ this.emit('onFullscreenChanged', true);
1060
+ return;
1061
+ }
1062
+ else if (this.video.webkitRequestFullscreen) {
1063
+ await this.video.webkitRequestFullscreen();
1064
+ this.playerWrapper.classList.add('uvf-fullscreen');
1065
+ this.emit('onFullscreenChanged', true);
1066
+ return;
1067
+ }
1068
+ }
1069
+ catch (iosError) {
1070
+ this.debugWarn('iOS video fullscreen failed:', iosError.message);
1071
+ }
1072
+ }
1073
+ if (!this.isFullscreenSupported()) {
1043
1074
  this.debugWarn('Fullscreen not supported by browser');
1075
+ if (this.isMobileDevice()) {
1076
+ this.showShortcutIndicator('Rotate device for fullscreen experience');
1077
+ }
1044
1078
  return;
1045
1079
  }
1046
- if (document.fullscreenElement ||
1047
- document.webkitFullscreenElement ||
1048
- document.mozFullScreenElement ||
1049
- document.msFullscreenElement) {
1080
+ if (this.isFullscreen()) {
1050
1081
  this.debugLog('Already in fullscreen mode');
1051
1082
  return;
1052
1083
  }
1053
1084
  const element = this.playerWrapper;
1085
+ let fullscreenSuccess = false;
1054
1086
  if (element.requestFullscreen) {
1055
- await element.requestFullscreen().catch(err => {
1056
- this.debugWarn('Fullscreen request failed:', err.message);
1057
- });
1087
+ try {
1088
+ await element.requestFullscreen();
1089
+ fullscreenSuccess = true;
1090
+ }
1091
+ catch (err) {
1092
+ this.debugWarn('Standard fullscreen request failed:', err.message);
1093
+ }
1058
1094
  }
1059
1095
  else if (element.webkitRequestFullscreen) {
1060
- await element.webkitRequestFullscreen().catch((err) => {
1096
+ try {
1097
+ await element.webkitRequestFullscreen();
1098
+ fullscreenSuccess = true;
1099
+ }
1100
+ catch (err) {
1061
1101
  this.debugWarn('WebKit fullscreen request failed:', err.message);
1062
- });
1102
+ }
1063
1103
  }
1064
1104
  else if (element.mozRequestFullScreen) {
1065
- await element.mozRequestFullScreen().catch((err) => {
1105
+ try {
1106
+ await element.mozRequestFullScreen();
1107
+ fullscreenSuccess = true;
1108
+ }
1109
+ catch (err) {
1066
1110
  this.debugWarn('Mozilla fullscreen request failed:', err.message);
1067
- });
1111
+ }
1068
1112
  }
1069
1113
  else if (element.msRequestFullscreen) {
1070
- await element.msRequestFullscreen().catch((err) => {
1114
+ try {
1115
+ await element.msRequestFullscreen();
1116
+ fullscreenSuccess = true;
1117
+ }
1118
+ catch (err) {
1071
1119
  this.debugWarn('MS fullscreen request failed:', err.message);
1072
- });
1120
+ }
1121
+ }
1122
+ if (fullscreenSuccess) {
1123
+ this.playerWrapper.classList.add('uvf-fullscreen');
1124
+ this.emit('onFullscreenChanged', true);
1125
+ if (this.isAndroidDevice()) {
1126
+ setTimeout(() => {
1127
+ this.showShortcutIndicator('Rotate device to landscape for best experience');
1128
+ }, 1000);
1129
+ }
1073
1130
  }
1074
1131
  else {
1075
- this.debugWarn('Fullscreen API not supported by this browser');
1076
- return;
1132
+ this.debugWarn('All fullscreen methods failed');
1133
+ if (this.isIOSDevice()) {
1134
+ this.showShortcutIndicator('Fullscreen not available - use device controls');
1135
+ }
1136
+ else if (this.isAndroidDevice()) {
1137
+ this.showShortcutIndicator('Try rotating device to landscape');
1138
+ }
1139
+ else {
1140
+ this.showShortcutIndicator('Fullscreen not supported in this browser');
1141
+ }
1077
1142
  }
1078
- this.playerWrapper.classList.add('uvf-fullscreen');
1079
- this.emit('onFullscreenChanged', true);
1080
1143
  }
1081
1144
  catch (error) {
1082
1145
  this.debugWarn('Failed to enter fullscreen:', error.message);
@@ -1084,37 +1147,74 @@ export class WebPlayer extends BasePlayer {
1084
1147
  }
1085
1148
  async exitFullscreen() {
1086
1149
  try {
1087
- if (!document.fullscreenElement &&
1088
- !document.webkitFullscreenElement &&
1089
- !document.mozFullScreenElement &&
1090
- !document.msFullscreenElement) {
1150
+ if (this.isIOSDevice() && this.video) {
1151
+ try {
1152
+ if (this.video.webkitExitFullscreen) {
1153
+ await this.video.webkitExitFullscreen();
1154
+ if (this.playerWrapper) {
1155
+ this.playerWrapper.classList.remove('uvf-fullscreen');
1156
+ }
1157
+ this.emit('onFullscreenChanged', false);
1158
+ return;
1159
+ }
1160
+ }
1161
+ catch (iosError) {
1162
+ this.debugWarn('iOS video exit fullscreen failed:', iosError.message);
1163
+ }
1164
+ }
1165
+ if (!this.isFullscreen()) {
1091
1166
  this.debugLog('Not in fullscreen mode');
1092
1167
  return;
1093
1168
  }
1169
+ let exitSuccess = false;
1094
1170
  if (document.exitFullscreen) {
1095
- await document.exitFullscreen().catch(err => {
1096
- this.debugWarn('Exit fullscreen failed:', err.message);
1097
- });
1171
+ try {
1172
+ await document.exitFullscreen();
1173
+ exitSuccess = true;
1174
+ }
1175
+ catch (err) {
1176
+ this.debugWarn('Standard exit fullscreen failed:', err.message);
1177
+ }
1098
1178
  }
1099
1179
  else if (document.webkitExitFullscreen) {
1100
- await document.webkitExitFullscreen().catch((err) => {
1180
+ try {
1181
+ await document.webkitExitFullscreen();
1182
+ exitSuccess = true;
1183
+ }
1184
+ catch (err) {
1101
1185
  this.debugWarn('WebKit exit fullscreen failed:', err.message);
1102
- });
1186
+ }
1103
1187
  }
1104
1188
  else if (document.mozCancelFullScreen) {
1105
- await document.mozCancelFullScreen().catch((err) => {
1189
+ try {
1190
+ await document.mozCancelFullScreen();
1191
+ exitSuccess = true;
1192
+ }
1193
+ catch (err) {
1106
1194
  this.debugWarn('Mozilla exit fullscreen failed:', err.message);
1107
- });
1195
+ }
1108
1196
  }
1109
1197
  else if (document.msExitFullscreen) {
1110
- await document.msExitFullscreen().catch((err) => {
1198
+ try {
1199
+ await document.msExitFullscreen();
1200
+ exitSuccess = true;
1201
+ }
1202
+ catch (err) {
1111
1203
  this.debugWarn('MS exit fullscreen failed:', err.message);
1112
- });
1204
+ }
1113
1205
  }
1114
- if (this.playerWrapper) {
1115
- this.playerWrapper.classList.remove('uvf-fullscreen');
1206
+ if (exitSuccess || !this.isFullscreen()) {
1207
+ if (this.playerWrapper) {
1208
+ this.playerWrapper.classList.remove('uvf-fullscreen');
1209
+ }
1210
+ this.emit('onFullscreenChanged', false);
1211
+ }
1212
+ else {
1213
+ this.debugWarn('All exit fullscreen methods failed');
1214
+ if (this.playerWrapper) {
1215
+ this.playerWrapper.classList.remove('uvf-fullscreen');
1216
+ }
1116
1217
  }
1117
- this.emit('onFullscreenChanged', false);
1118
1218
  }
1119
1219
  catch (error) {
1120
1220
  this.debugWarn('Failed to exit fullscreen:', error.message);
@@ -3683,7 +3783,7 @@ export class WebPlayer extends BasePlayer {
3683
3783
  }
3684
3784
  }
3685
3785
 
3686
- /* iOS Safari specific fixes - address bar handling */
3786
+ /* iOS Safari specific fixes - address bar handling and control positioning */
3687
3787
  @supports (-webkit-appearance: none) {
3688
3788
  .uvf-player-wrapper.uvf-fullscreen,
3689
3789
  .uvf-video-container.uvf-fullscreen {
@@ -3701,6 +3801,31 @@ export class WebPlayer extends BasePlayer {
3701
3801
  .uvf-player-wrapper {
3702
3802
  height: -webkit-fill-available;
3703
3803
  min-height: 100vh;
3804
+ /* Fix for iOS Safari control overlay positioning */
3805
+ position: relative;
3806
+ overflow: hidden;
3807
+ }
3808
+
3809
+ /* iOS Safari specific fixes for control positioning */
3810
+ .uvf-controls-bar {
3811
+ position: absolute !important;
3812
+ bottom: 0 !important;
3813
+ left: 0 !important;
3814
+ right: 0 !important;
3815
+ /* Ensure hardware acceleration */
3816
+ -webkit-transform: translate3d(0,0,0);
3817
+ transform: translate3d(0,0,0);
3818
+ /* Prevent any webkit transforms that could cause positioning issues */
3819
+ -webkit-perspective: 1000;
3820
+ perspective: 1000;
3821
+ }
3822
+
3823
+ /* Ensure all control elements use hardware acceleration */
3824
+ .uvf-control-btn,
3825
+ .uvf-progress-bar,
3826
+ .uvf-progress-section {
3827
+ -webkit-transform: translateZ(0);
3828
+ transform: translateZ(0);
3704
3829
  }
3705
3830
  }
3706
3831
  }
@@ -3761,14 +3886,45 @@ export class WebPlayer extends BasePlayer {
3761
3886
 
3762
3887
  /* Fix for controls being cut off by virtual keyboard */
3763
3888
  .uvf-controls-bar {
3764
- position: fixed !important;
3765
- bottom: var(--uvf-safe-area-bottom, 0) !important;
3889
+ position: absolute !important;
3890
+ bottom: 0 !important;
3891
+ left: 0 !important;
3892
+ right: 0 !important;
3893
+ /* Remove fixed positioning that causes issues on iOS Safari */
3894
+ z-index: 1000 !important;
3895
+ transform: translateZ(0); /* Force hardware acceleration */
3766
3896
  }
3767
3897
 
3768
3898
  /* Ensure controls stay above virtual keyboards */
3769
3899
  @supports (bottom: env(keyboard-inset-height)) {
3770
3900
  .uvf-controls-bar {
3771
- bottom: max(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0)) !important;
3901
+ bottom: max(0px, env(keyboard-inset-height, 0)) !important;
3902
+ padding-bottom: calc(16px + max(var(--uvf-safe-area-bottom, 0), env(keyboard-inset-height, 0))) !important;
3903
+ }
3904
+ }
3905
+
3906
+ /* Hide PiP button on mobile - not supported on most mobile browsers */
3907
+ #uvf-pip-btn {
3908
+ display: none !important;
3909
+ }
3910
+
3911
+ /* Mobile fullscreen enhancements */
3912
+ .uvf-player-wrapper.uvf-fullscreen {
3913
+ /* Ensure fullscreen covers entire viewport on mobile */
3914
+ position: fixed !important;
3915
+ top: 0 !important;
3916
+ left: 0 !important;
3917
+ width: 100vw !important;
3918
+ height: 100vh !important;
3919
+ z-index: 2147483647 !important;
3920
+ background: #000 !important;
3921
+ }
3922
+
3923
+ /* iOS Safari specific fullscreen fixes */
3924
+ @supports (-webkit-appearance: none) {
3925
+ .uvf-player-wrapper.uvf-fullscreen {
3926
+ /* Use viewport units that work better with iOS Safari */
3927
+ height: -webkit-fill-available !important;
3772
3928
  }
3773
3929
  }
3774
3930
  }
@@ -3810,19 +3966,25 @@ export class WebPlayer extends BasePlayer {
3810
3966
  min-height: inherit;
3811
3967
  }
3812
3968
 
3813
- /* Enhanced mobile controls bar with safe area padding */
3969
+ /* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
3814
3970
  .uvf-controls-bar {
3815
- position: absolute;
3816
- bottom: 0;
3817
- left: 0;
3818
- right: 0;
3971
+ position: absolute !important;
3972
+ bottom: 0 !important;
3973
+ left: 0 !important;
3974
+ right: 0 !important;
3819
3975
  padding: 16px 12px;
3820
- padding-bottom: calc(16px + var(--uvf-safe-area-bottom));
3821
- padding-left: calc(12px + var(--uvf-safe-area-left));
3822
- padding-right: calc(12px + var(--uvf-safe-area-right));
3976
+ padding-bottom: calc(16px + var(--uvf-safe-area-bottom, 0px));
3977
+ padding-left: calc(12px + var(--uvf-safe-area-left, 0px));
3978
+ padding-right: calc(12px + var(--uvf-safe-area-right, 0px));
3823
3979
  background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
3824
3980
  box-sizing: border-box;
3825
- z-index: 1000;
3981
+ z-index: 1000 !important;
3982
+ /* iOS Safari specific fixes */
3983
+ transform: translateZ(0);
3984
+ -webkit-transform: translateZ(0);
3985
+ will-change: transform;
3986
+ /* Ensure proper stacking */
3987
+ isolation: isolate;
3826
3988
  }
3827
3989
 
3828
3990
  .uvf-progress-section {
@@ -5254,6 +5416,9 @@ export class WebPlayer extends BasePlayer {
5254
5416
  pipBtn.id = 'uvf-pip-btn';
5255
5417
  pipBtn.title = 'Picture-in-Picture';
5256
5418
  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>';
5419
+ if (this.isMobileDevice() || !this.isPipSupported()) {
5420
+ pipBtn.style.display = 'none';
5421
+ }
5257
5422
  rightControls.appendChild(pipBtn);
5258
5423
  const fullscreenBtn = document.createElement('button');
5259
5424
  fullscreenBtn.className = 'uvf-control-btn';
@@ -5492,13 +5657,20 @@ export class WebPlayer extends BasePlayer {
5492
5657
  fullscreenBtn?.addEventListener('click', (event) => {
5493
5658
  const isBrave = this.isBraveBrowser();
5494
5659
  const isPrivate = this.isPrivateWindow();
5660
+ const isIOS = this.isIOSDevice();
5661
+ const isAndroid = this.isAndroidDevice();
5662
+ const isMobile = this.isMobileDevice();
5495
5663
  this.debugLog('Fullscreen button clicked:', {
5496
5664
  isBrave,
5497
5665
  isPrivate,
5666
+ isIOS,
5667
+ isAndroid,
5668
+ isMobile,
5498
5669
  isFullscreen: this.isFullscreen(),
5499
5670
  eventTrusted: event.isTrusted,
5500
5671
  eventType: event.type,
5501
- timestamp: Date.now()
5672
+ timestamp: Date.now(),
5673
+ fullscreenSupported: this.isFullscreenSupported()
5502
5674
  });
5503
5675
  this.lastUserInteraction = Date.now();
5504
5676
  this.checkFullscreenPermissions();
@@ -5510,20 +5682,27 @@ export class WebPlayer extends BasePlayer {
5510
5682
  }
5511
5683
  else {
5512
5684
  this.debugLog('Entering fullscreen via button');
5513
- if (isBrave && !isPrivate) {
5514
- this.enterFullscreenWithBraveSupport().catch(err => {
5515
- this.debugWarn('Brave fullscreen button failed:', err.message);
5516
- this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
5517
- });
5685
+ if (isIOS) {
5686
+ this.showShortcutIndicator('Using iOS video fullscreen');
5518
5687
  }
5519
- else {
5520
- this.enterFullscreen().catch(err => {
5521
- this.debugWarn('Fullscreen button failed:', err.message);
5522
- if (isBrave) {
5523
- this.showTemporaryMessage('Try refreshing the page or check Brave shields settings');
5524
- }
5525
- });
5688
+ else if (isAndroid) {
5689
+ this.showShortcutIndicator('Entering fullscreen - rotate to landscape');
5526
5690
  }
5691
+ this.enterFullscreen().catch(err => {
5692
+ this.debugWarn('Fullscreen button failed:', err.message);
5693
+ if (isIOS) {
5694
+ this.showTemporaryMessage('iOS: Use device rotation or video controls for fullscreen');
5695
+ }
5696
+ else if (isAndroid) {
5697
+ this.showTemporaryMessage('Android: Try rotating device to landscape mode');
5698
+ }
5699
+ else if (isBrave) {
5700
+ this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
5701
+ }
5702
+ else {
5703
+ this.showTemporaryMessage('Fullscreen not supported in this browser');
5704
+ }
5705
+ });
5527
5706
  }
5528
5707
  });
5529
5708
  const updateFullscreenIcon = () => {
@@ -6058,6 +6237,33 @@ export class WebPlayer extends BasePlayer {
6058
6237
  this.mute();
6059
6238
  }
6060
6239
  }
6240
+ isMobileDevice() {
6241
+ const userAgent = navigator.userAgent.toLowerCase();
6242
+ const mobileKeywords = ['android', 'iphone', 'ipad', 'ipod', 'blackberry', 'windows phone', 'mobile'];
6243
+ const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
6244
+ const isSmallScreen = window.innerWidth <= 768;
6245
+ const hasTouchScreen = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
6246
+ return isMobileUserAgent || (isSmallScreen && hasTouchScreen);
6247
+ }
6248
+ isPipSupported() {
6249
+ return !!(document.pictureInPictureEnabled &&
6250
+ HTMLVideoElement.prototype.requestPictureInPicture &&
6251
+ typeof HTMLVideoElement.prototype.requestPictureInPicture === 'function');
6252
+ }
6253
+ isIOSDevice() {
6254
+ const userAgent = navigator.userAgent.toLowerCase();
6255
+ return /iphone|ipad|ipod/.test(userAgent);
6256
+ }
6257
+ isAndroidDevice() {
6258
+ const userAgent = navigator.userAgent.toLowerCase();
6259
+ return /android/.test(userAgent);
6260
+ }
6261
+ isFullscreenSupported() {
6262
+ return !!(document.fullscreenEnabled ||
6263
+ document.webkitFullscreenEnabled ||
6264
+ document.mozFullScreenEnabled ||
6265
+ document.msFullscreenEnabled);
6266
+ }
6061
6267
  handleVolumeChange(e) {
6062
6268
  const slider = document.getElementById('uvf-volume-slider');
6063
6269
  if (!slider)