unified-video-framework 1.4.157 → 1.4.159
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +12 -2
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts +18 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.d.ts.map +1 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js +117 -0
- package/packages/core/dist/analytics/adapters/PlayerAnalyticsAdapter.js.map +1 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts +18 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.js +99 -0
- package/packages/core/dist/analytics/core/AnalyticsProvider.js.map +1 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts +20 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js +161 -0
- package/packages/core/dist/analytics/core/DynamicAnalyticsManager.js.map +1 -0
- package/packages/core/dist/analytics/core/EventBatcher.d.ts +32 -0
- package/packages/core/dist/analytics/core/EventBatcher.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/EventBatcher.js +98 -0
- package/packages/core/dist/analytics/core/EventBatcher.js.map +1 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts +19 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.d.ts.map +1 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.js +80 -0
- package/packages/core/dist/analytics/core/PlayerAnalytics.js.map +1 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts +32 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.d.ts.map +1 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js +220 -0
- package/packages/core/dist/analytics/examples/DynamicAnalyticsExample.js.map +1 -0
- package/packages/core/dist/analytics/index.d.ts +13 -0
- package/packages/core/dist/analytics/index.d.ts.map +1 -0
- package/packages/core/dist/analytics/index.js +13 -0
- package/packages/core/dist/analytics/index.js.map +1 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts +239 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.d.ts.map +1 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.js +8 -0
- package/packages/core/dist/analytics/types/AnalyticsTypes.js.map +1 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.d.ts +27 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.d.ts.map +1 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.js +184 -0
- package/packages/core/dist/analytics/utils/DeviceDetection.js.map +1 -0
- package/packages/core/dist/chapter-manager.d.ts +39 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/src/analytics/README.md +902 -0
- package/packages/core/src/analytics/adapters/PlayerAnalyticsAdapter.ts +156 -0
- package/packages/core/src/analytics/core/AnalyticsProvider.ts +169 -0
- package/packages/core/src/analytics/core/DynamicAnalyticsManager.ts +199 -0
- package/packages/core/src/analytics/core/EventBatcher.ts +160 -0
- package/packages/core/src/analytics/core/PlayerAnalytics.ts +147 -0
- package/packages/core/src/analytics/index.ts +51 -0
- package/packages/core/src/analytics/types/AnalyticsTypes.ts +315 -0
- package/packages/core/src/analytics/utils/DeviceDetection.ts +220 -0
- package/packages/core/src/index.ts +3 -0
- package/packages/ios/README.md +84 -0
- package/packages/web/dist/WebPlayer.d.ts +6 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +273 -67
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/epg/EPGController.d.ts +78 -0
- package/packages/web/dist/epg/EPGController.d.ts.map +1 -0
- package/packages/web/dist/epg/EPGController.js +476 -0
- package/packages/web/dist/epg/EPGController.js.map +1 -0
- package/packages/web/src/WebPlayer.ts +336 -85
- package/src/analytics/README.md +902 -0
- package/src/analytics/adapters/PlayerAnalyticsAdapter.ts +572 -0
- package/src/analytics/core/DynamicAnalyticsManager.ts +526 -0
- package/src/analytics/examples/DynamicAnalyticsExample.ts +324 -0
- package/src/analytics/index.ts +60 -0
|
@@ -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.
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
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 (
|
|
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
|
-
|
|
1056
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
1076
|
-
|
|
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 (
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1115
|
-
this.playerWrapper
|
|
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:
|
|
3765
|
-
bottom:
|
|
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(
|
|
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 (
|
|
5514
|
-
this.
|
|
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.
|
|
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)
|