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
@@ -1050,47 +1050,96 @@ export class WebPlayer extends BasePlayer {
1050
1050
  if (!this.playerWrapper)
1051
1051
  return;
1052
1052
  try {
1053
- if (!document.fullscreenEnabled &&
1054
- !document.webkitFullscreenEnabled &&
1055
- !document.mozFullScreenEnabled &&
1056
- !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()) {
1057
1074
  this.debugWarn('Fullscreen not supported by browser');
1075
+ if (this.isMobileDevice()) {
1076
+ this.showShortcutIndicator('Rotate device for fullscreen experience');
1077
+ }
1058
1078
  return;
1059
1079
  }
1060
- if (document.fullscreenElement ||
1061
- document.webkitFullscreenElement ||
1062
- document.mozFullScreenElement ||
1063
- document.msFullscreenElement) {
1080
+ if (this.isFullscreen()) {
1064
1081
  this.debugLog('Already in fullscreen mode');
1065
1082
  return;
1066
1083
  }
1067
1084
  const element = this.playerWrapper;
1085
+ let fullscreenSuccess = false;
1068
1086
  if (element.requestFullscreen) {
1069
- await element.requestFullscreen().catch(err => {
1070
- this.debugWarn('Fullscreen request failed:', err.message);
1071
- });
1087
+ try {
1088
+ await element.requestFullscreen();
1089
+ fullscreenSuccess = true;
1090
+ }
1091
+ catch (err) {
1092
+ this.debugWarn('Standard fullscreen request failed:', err.message);
1093
+ }
1072
1094
  }
1073
1095
  else if (element.webkitRequestFullscreen) {
1074
- await element.webkitRequestFullscreen().catch((err) => {
1096
+ try {
1097
+ await element.webkitRequestFullscreen();
1098
+ fullscreenSuccess = true;
1099
+ }
1100
+ catch (err) {
1075
1101
  this.debugWarn('WebKit fullscreen request failed:', err.message);
1076
- });
1102
+ }
1077
1103
  }
1078
1104
  else if (element.mozRequestFullScreen) {
1079
- await element.mozRequestFullScreen().catch((err) => {
1105
+ try {
1106
+ await element.mozRequestFullScreen();
1107
+ fullscreenSuccess = true;
1108
+ }
1109
+ catch (err) {
1080
1110
  this.debugWarn('Mozilla fullscreen request failed:', err.message);
1081
- });
1111
+ }
1082
1112
  }
1083
1113
  else if (element.msRequestFullscreen) {
1084
- await element.msRequestFullscreen().catch((err) => {
1114
+ try {
1115
+ await element.msRequestFullscreen();
1116
+ fullscreenSuccess = true;
1117
+ }
1118
+ catch (err) {
1085
1119
  this.debugWarn('MS fullscreen request failed:', err.message);
1086
- });
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
+ }
1087
1130
  }
1088
1131
  else {
1089
- this.debugWarn('Fullscreen API not supported by this browser');
1090
- 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
+ }
1091
1142
  }
1092
- this.playerWrapper.classList.add('uvf-fullscreen');
1093
- this.emit('onFullscreenChanged', true);
1094
1143
  }
1095
1144
  catch (error) {
1096
1145
  this.debugWarn('Failed to enter fullscreen:', error.message);
@@ -1098,37 +1147,74 @@ export class WebPlayer extends BasePlayer {
1098
1147
  }
1099
1148
  async exitFullscreen() {
1100
1149
  try {
1101
- if (!document.fullscreenElement &&
1102
- !document.webkitFullscreenElement &&
1103
- !document.mozFullScreenElement &&
1104
- !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()) {
1105
1166
  this.debugLog('Not in fullscreen mode');
1106
1167
  return;
1107
1168
  }
1169
+ let exitSuccess = false;
1108
1170
  if (document.exitFullscreen) {
1109
- await document.exitFullscreen().catch(err => {
1110
- this.debugWarn('Exit fullscreen failed:', err.message);
1111
- });
1171
+ try {
1172
+ await document.exitFullscreen();
1173
+ exitSuccess = true;
1174
+ }
1175
+ catch (err) {
1176
+ this.debugWarn('Standard exit fullscreen failed:', err.message);
1177
+ }
1112
1178
  }
1113
1179
  else if (document.webkitExitFullscreen) {
1114
- await document.webkitExitFullscreen().catch((err) => {
1180
+ try {
1181
+ await document.webkitExitFullscreen();
1182
+ exitSuccess = true;
1183
+ }
1184
+ catch (err) {
1115
1185
  this.debugWarn('WebKit exit fullscreen failed:', err.message);
1116
- });
1186
+ }
1117
1187
  }
1118
1188
  else if (document.mozCancelFullScreen) {
1119
- await document.mozCancelFullScreen().catch((err) => {
1189
+ try {
1190
+ await document.mozCancelFullScreen();
1191
+ exitSuccess = true;
1192
+ }
1193
+ catch (err) {
1120
1194
  this.debugWarn('Mozilla exit fullscreen failed:', err.message);
1121
- });
1195
+ }
1122
1196
  }
1123
1197
  else if (document.msExitFullscreen) {
1124
- await document.msExitFullscreen().catch((err) => {
1198
+ try {
1199
+ await document.msExitFullscreen();
1200
+ exitSuccess = true;
1201
+ }
1202
+ catch (err) {
1125
1203
  this.debugWarn('MS exit fullscreen failed:', err.message);
1126
- });
1204
+ }
1127
1205
  }
1128
- if (this.playerWrapper) {
1129
- 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
+ }
1130
1217
  }
1131
- this.emit('onFullscreenChanged', false);
1132
1218
  }
1133
1219
  catch (error) {
1134
1220
  this.debugWarn('Failed to exit fullscreen:', error.message);
@@ -3697,7 +3783,7 @@ export class WebPlayer extends BasePlayer {
3697
3783
  }
3698
3784
  }
3699
3785
 
3700
- /* iOS Safari specific fixes - address bar handling */
3786
+ /* iOS Safari specific fixes - address bar handling and control positioning */
3701
3787
  @supports (-webkit-appearance: none) {
3702
3788
  .uvf-player-wrapper.uvf-fullscreen,
3703
3789
  .uvf-video-container.uvf-fullscreen {
@@ -3715,6 +3801,31 @@ export class WebPlayer extends BasePlayer {
3715
3801
  .uvf-player-wrapper {
3716
3802
  height: -webkit-fill-available;
3717
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);
3718
3829
  }
3719
3830
  }
3720
3831
  }
@@ -3775,14 +3886,45 @@ export class WebPlayer extends BasePlayer {
3775
3886
 
3776
3887
  /* Fix for controls being cut off by virtual keyboard */
3777
3888
  .uvf-controls-bar {
3778
- position: fixed !important;
3779
- 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 */
3780
3896
  }
3781
3897
 
3782
3898
  /* Ensure controls stay above virtual keyboards */
3783
3899
  @supports (bottom: env(keyboard-inset-height)) {
3784
3900
  .uvf-controls-bar {
3785
- 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;
3786
3928
  }
3787
3929
  }
3788
3930
  }
@@ -3824,19 +3966,25 @@ export class WebPlayer extends BasePlayer {
3824
3966
  min-height: inherit;
3825
3967
  }
3826
3968
 
3827
- /* Enhanced mobile controls bar with safe area padding */
3969
+ /* Enhanced mobile controls bar with safe area padding - iOS Safari specific fixes */
3828
3970
  .uvf-controls-bar {
3829
- position: absolute;
3830
- bottom: 0;
3831
- left: 0;
3832
- right: 0;
3971
+ position: absolute !important;
3972
+ bottom: 0 !important;
3973
+ left: 0 !important;
3974
+ right: 0 !important;
3833
3975
  padding: 16px 12px;
3834
- padding-bottom: calc(16px + var(--uvf-safe-area-bottom));
3835
- padding-left: calc(12px + var(--uvf-safe-area-left));
3836
- 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));
3837
3979
  background: linear-gradient(to top, var(--uvf-overlay-strong) 0%, var(--uvf-overlay-medium) 80%, var(--uvf-overlay-transparent) 100%);
3838
3980
  box-sizing: border-box;
3839
- 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;
3840
3988
  }
3841
3989
 
3842
3990
  .uvf-progress-section {
@@ -5268,6 +5416,9 @@ export class WebPlayer extends BasePlayer {
5268
5416
  pipBtn.id = 'uvf-pip-btn';
5269
5417
  pipBtn.title = 'Picture-in-Picture';
5270
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
+ }
5271
5422
  rightControls.appendChild(pipBtn);
5272
5423
  const fullscreenBtn = document.createElement('button');
5273
5424
  fullscreenBtn.className = 'uvf-control-btn';
@@ -5506,13 +5657,20 @@ export class WebPlayer extends BasePlayer {
5506
5657
  fullscreenBtn?.addEventListener('click', (event) => {
5507
5658
  const isBrave = this.isBraveBrowser();
5508
5659
  const isPrivate = this.isPrivateWindow();
5660
+ const isIOS = this.isIOSDevice();
5661
+ const isAndroid = this.isAndroidDevice();
5662
+ const isMobile = this.isMobileDevice();
5509
5663
  this.debugLog('Fullscreen button clicked:', {
5510
5664
  isBrave,
5511
5665
  isPrivate,
5666
+ isIOS,
5667
+ isAndroid,
5668
+ isMobile,
5512
5669
  isFullscreen: this.isFullscreen(),
5513
5670
  eventTrusted: event.isTrusted,
5514
5671
  eventType: event.type,
5515
- timestamp: Date.now()
5672
+ timestamp: Date.now(),
5673
+ fullscreenSupported: this.isFullscreenSupported()
5516
5674
  });
5517
5675
  this.lastUserInteraction = Date.now();
5518
5676
  this.checkFullscreenPermissions();
@@ -5524,20 +5682,27 @@ export class WebPlayer extends BasePlayer {
5524
5682
  }
5525
5683
  else {
5526
5684
  this.debugLog('Entering fullscreen via button');
5527
- if (isBrave && !isPrivate) {
5528
- this.enterFullscreenWithBraveSupport().catch(err => {
5529
- this.debugWarn('Brave fullscreen button failed:', err.message);
5530
- this.showTemporaryMessage('Brave Browser: Please allow fullscreen in site settings');
5531
- });
5685
+ if (isIOS) {
5686
+ this.showShortcutIndicator('Using iOS video fullscreen');
5532
5687
  }
5533
- else {
5534
- this.enterFullscreen().catch(err => {
5535
- this.debugWarn('Fullscreen button failed:', err.message);
5536
- if (isBrave) {
5537
- this.showTemporaryMessage('Try refreshing the page or check Brave shields settings');
5538
- }
5539
- });
5688
+ else if (isAndroid) {
5689
+ this.showShortcutIndicator('Entering fullscreen - rotate to landscape');
5540
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
+ });
5541
5706
  }
5542
5707
  });
5543
5708
  const updateFullscreenIcon = () => {
@@ -6072,6 +6237,33 @@ export class WebPlayer extends BasePlayer {
6072
6237
  this.mute();
6073
6238
  }
6074
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
+ }
6075
6267
  handleVolumeChange(e) {
6076
6268
  const slider = document.getElementById('uvf-volume-slider');
6077
6269
  if (!slider)