unified-video-framework 1.4.196 → 1.4.197
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.
|
@@ -238,6 +238,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
238
238
|
this.video.playsInline = this.config.playsInline ?? true;
|
|
239
239
|
this.video.preload = this.config.preload ?? 'metadata';
|
|
240
240
|
|
|
241
|
+
// Enable AirPlay for iOS devices
|
|
242
|
+
(this.video as any).webkitAllowsAirPlay = true;
|
|
243
|
+
this.video.setAttribute('x-webkit-airplay', 'allow');
|
|
244
|
+
|
|
241
245
|
if (this.config.crossOrigin) {
|
|
242
246
|
this.video.crossOrigin = this.config.crossOrigin;
|
|
243
247
|
}
|
|
@@ -938,6 +942,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
938
942
|
try {
|
|
939
943
|
await this.play();
|
|
940
944
|
this.debugLog('✅ Muted autoplay successful');
|
|
945
|
+
// Show YouTube-style unmute button instead of blocking overlay
|
|
946
|
+
this.showUnmuteButton();
|
|
941
947
|
return true;
|
|
942
948
|
} catch (error) {
|
|
943
949
|
this.debugLog('❌ Muted autoplay failed');
|
|
@@ -1133,6 +1139,129 @@ export class WebPlayer extends BasePlayer {
|
|
|
1133
1139
|
}
|
|
1134
1140
|
}
|
|
1135
1141
|
|
|
1142
|
+
/**
|
|
1143
|
+
* Show YouTube-style unmute button when video autoplays muted
|
|
1144
|
+
*/
|
|
1145
|
+
private showUnmuteButton(): void {
|
|
1146
|
+
// Remove existing unmute button
|
|
1147
|
+
this.hideUnmuteButton();
|
|
1148
|
+
|
|
1149
|
+
this.debugLog('🔇 Showing unmute button - video autoplaying muted');
|
|
1150
|
+
|
|
1151
|
+
const unmuteBtn = document.createElement('button');
|
|
1152
|
+
unmuteBtn.id = 'uvf-unmute-btn';
|
|
1153
|
+
unmuteBtn.className = 'uvf-unmute-btn';
|
|
1154
|
+
unmuteBtn.setAttribute('aria-label', 'Tap to unmute');
|
|
1155
|
+
unmuteBtn.innerHTML = `
|
|
1156
|
+
<svg viewBox="0 0 24 24" class="uvf-unmute-icon">
|
|
1157
|
+
<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
|
|
1158
|
+
</svg>
|
|
1159
|
+
<span class="uvf-unmute-text">Tap to unmute</span>
|
|
1160
|
+
`;
|
|
1161
|
+
|
|
1162
|
+
// Click handler to unmute
|
|
1163
|
+
unmuteBtn.addEventListener('click', (e) => {
|
|
1164
|
+
e.stopPropagation();
|
|
1165
|
+
if (this.video) {
|
|
1166
|
+
this.video.muted = false;
|
|
1167
|
+
this.debugLog('🔊 Video unmuted by user');
|
|
1168
|
+
this.hideUnmuteButton();
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
// Add enhanced styles
|
|
1173
|
+
const style = document.createElement('style');
|
|
1174
|
+
style.textContent = `
|
|
1175
|
+
.uvf-unmute-btn {
|
|
1176
|
+
position: absolute !important;
|
|
1177
|
+
bottom: 80px !important;
|
|
1178
|
+
left: 20px !important;
|
|
1179
|
+
z-index: 1000 !important;
|
|
1180
|
+
display: flex !important;
|
|
1181
|
+
align-items: center !important;
|
|
1182
|
+
gap: 8px !important;
|
|
1183
|
+
padding: 12px 16px !important;
|
|
1184
|
+
background: rgba(0, 0, 0, 0.8) !important;
|
|
1185
|
+
border: none !important;
|
|
1186
|
+
border-radius: 4px !important;
|
|
1187
|
+
color: white !important;
|
|
1188
|
+
font-size: 14px !important;
|
|
1189
|
+
font-weight: 500 !important;
|
|
1190
|
+
cursor: pointer !important;
|
|
1191
|
+
transition: all 0.2s ease !important;
|
|
1192
|
+
backdrop-filter: blur(10px) !important;
|
|
1193
|
+
-webkit-backdrop-filter: blur(10px) !important;
|
|
1194
|
+
animation: uvf-unmute-pulse 2s ease-in-out infinite !important;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.uvf-unmute-btn:hover {
|
|
1198
|
+
background: rgba(0, 0, 0, 0.9) !important;
|
|
1199
|
+
transform: scale(1.05) !important;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.uvf-unmute-btn:active {
|
|
1203
|
+
transform: scale(0.95) !important;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.uvf-unmute-icon {
|
|
1207
|
+
width: 20px !important;
|
|
1208
|
+
height: 20px !important;
|
|
1209
|
+
fill: white !important;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.uvf-unmute-text {
|
|
1213
|
+
white-space: nowrap !important;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
@keyframes uvf-unmute-pulse {
|
|
1217
|
+
0%, 100% {
|
|
1218
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
|
|
1219
|
+
}
|
|
1220
|
+
50% {
|
|
1221
|
+
box-shadow: 0 2px 16px rgba(255, 255, 255, 0.2) !important;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/* Mobile responsive */
|
|
1226
|
+
@media (max-width: 767px) {
|
|
1227
|
+
.uvf-unmute-btn {
|
|
1228
|
+
bottom: 70px !important;
|
|
1229
|
+
left: 50% !important;
|
|
1230
|
+
transform: translateX(-50%) !important;
|
|
1231
|
+
padding: 10px 14px !important;
|
|
1232
|
+
font-size: 13px !important;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.uvf-unmute-btn:hover {
|
|
1236
|
+
transform: translateX(-50%) scale(1.05) !important;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
`;
|
|
1240
|
+
|
|
1241
|
+
// Add to page if not already added
|
|
1242
|
+
if (!document.getElementById('uvf-unmute-styles')) {
|
|
1243
|
+
style.id = 'uvf-unmute-styles';
|
|
1244
|
+
document.head.appendChild(style);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Add to player
|
|
1248
|
+
if (this.playerWrapper) {
|
|
1249
|
+
this.playerWrapper.appendChild(unmuteBtn);
|
|
1250
|
+
this.debugLog('✅ Unmute button added to player');
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* Hide unmute button
|
|
1256
|
+
*/
|
|
1257
|
+
private hideUnmuteButton(): void {
|
|
1258
|
+
const unmuteBtn = document.getElementById('uvf-unmute-btn');
|
|
1259
|
+
if (unmuteBtn) {
|
|
1260
|
+
unmuteBtn.remove();
|
|
1261
|
+
this.debugLog('Unmute button removed');
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1136
1265
|
private updateTimeTooltip(e: MouseEvent): void {
|
|
1137
1266
|
const progressBar = document.getElementById('uvf-progress-bar');
|
|
1138
1267
|
const tooltip = document.getElementById('uvf-time-tooltip');
|
|
@@ -4112,18 +4241,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4112
4241
|
height: 24px;
|
|
4113
4242
|
}
|
|
4114
4243
|
|
|
4115
|
-
.uvf-player-wrapper.uvf-fullscreen .uvf-top-btn {
|
|
4116
|
-
width: 40px;
|
|
4117
|
-
height: 40px;
|
|
4118
|
-
min-width: 40px;
|
|
4119
|
-
min-height: 40px;
|
|
4120
|
-
}
|
|
4121
|
-
|
|
4122
|
-
.uvf-player-wrapper.uvf-fullscreen .uvf-top-btn svg {
|
|
4123
|
-
width: 20px;
|
|
4124
|
-
height: 20px;
|
|
4125
|
-
}
|
|
4126
|
-
|
|
4127
4244
|
.uvf-player-wrapper.uvf-fullscreen .uvf-time-display {
|
|
4128
4245
|
font-size: 14px;
|
|
4129
4246
|
padding: 0 10px;
|
|
@@ -4240,11 +4357,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4240
4357
|
display: flex !important;
|
|
4241
4358
|
}
|
|
4242
4359
|
|
|
4243
|
-
.uvf-top-btn {
|
|
4244
|
-
display: flex !important;
|
|
4245
|
-
border-radius: 50% !important;
|
|
4246
|
-
}
|
|
4247
|
-
|
|
4248
4360
|
/* Show top bar when controls are visible or on hover */
|
|
4249
4361
|
.uvf-player-wrapper:hover .uvf-top-bar,
|
|
4250
4362
|
.uvf-player-wrapper.controls-visible .uvf-top-bar {
|
|
@@ -4348,18 +4460,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4348
4460
|
gap: 6px !important;
|
|
4349
4461
|
}
|
|
4350
4462
|
|
|
4351
|
-
.uvf-top-btn {
|
|
4352
|
-
width: 40px;
|
|
4353
|
-
height: 40px;
|
|
4354
|
-
min-width: 40px;
|
|
4355
|
-
min-height: 40px;
|
|
4356
|
-
}
|
|
4357
|
-
|
|
4358
|
-
.uvf-top-btn svg {
|
|
4359
|
-
width: 18px;
|
|
4360
|
-
height: 18px;
|
|
4361
|
-
}
|
|
4362
|
-
|
|
4363
4463
|
/* Title bar within top bar - landscape */
|
|
4364
4464
|
.uvf-top-bar .uvf-title-bar {
|
|
4365
4465
|
padding: 0;
|
|
@@ -4445,19 +4545,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4445
4545
|
height: 23px;
|
|
4446
4546
|
}
|
|
4447
4547
|
|
|
4448
|
-
/* Tablet top controls */
|
|
4449
|
-
.uvf-top-btn {
|
|
4450
|
-
width: 42px;
|
|
4451
|
-
height: 42px;
|
|
4452
|
-
min-width: 42px;
|
|
4453
|
-
min-height: 42px;
|
|
4454
|
-
}
|
|
4455
|
-
|
|
4456
|
-
.uvf-top-btn svg {
|
|
4457
|
-
width: 19px;
|
|
4458
|
-
height: 19px;
|
|
4459
|
-
}
|
|
4460
|
-
|
|
4461
4548
|
/* Top bar for tablet */
|
|
4462
4549
|
.uvf-top-bar {
|
|
4463
4550
|
padding: 16px;
|
|
@@ -4596,23 +4683,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4596
4683
|
height: 24px;
|
|
4597
4684
|
}
|
|
4598
4685
|
|
|
4599
|
-
/* Enhanced top controls */
|
|
4600
|
-
.uvf-top-btn {
|
|
4601
|
-
width: 40px;
|
|
4602
|
-
height: 40px;
|
|
4603
|
-
transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
|
|
4604
|
-
}
|
|
4605
|
-
|
|
4606
|
-
.uvf-top-btn:hover {
|
|
4607
|
-
transform: scale(1.1);
|
|
4608
|
-
background: var(--uvf-overlay-medium);
|
|
4609
|
-
}
|
|
4610
|
-
|
|
4611
|
-
.uvf-top-btn svg {
|
|
4612
|
-
width: 20px;
|
|
4613
|
-
height: 20px;
|
|
4614
|
-
}
|
|
4615
|
-
|
|
4616
4686
|
/* Top bar for desktop 1024px+ */
|
|
4617
4687
|
.uvf-top-bar {
|
|
4618
4688
|
padding: 20px;
|
|
@@ -4732,16 +4802,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4732
4802
|
height: 28px;
|
|
4733
4803
|
}
|
|
4734
4804
|
|
|
4735
|
-
.uvf-top-btn {
|
|
4736
|
-
width: 44px;
|
|
4737
|
-
height: 44px;
|
|
4738
|
-
}
|
|
4739
|
-
|
|
4740
|
-
.uvf-top-btn svg {
|
|
4741
|
-
width: 22px;
|
|
4742
|
-
height: 22px;
|
|
4743
|
-
}
|
|
4744
|
-
|
|
4745
4805
|
.uvf-center-play-btn {
|
|
4746
4806
|
width: clamp(64px, 10vw, 76px);
|
|
4747
4807
|
height: clamp(64px, 10vw, 76px);
|
|
@@ -4782,8 +4842,7 @@ export class WebPlayer extends BasePlayer {
|
|
|
4782
4842
|
/* High-DPI display optimizations */
|
|
4783
4843
|
@media screen and (-webkit-min-device-pixel-ratio: 2),
|
|
4784
4844
|
screen and (min-resolution: 192dpi) {
|
|
4785
|
-
.uvf-control-btn
|
|
4786
|
-
.uvf-top-btn {
|
|
4845
|
+
.uvf-control-btn {
|
|
4787
4846
|
image-rendering: -webkit-optimize-contrast;
|
|
4788
4847
|
image-rendering: crisp-edges;
|
|
4789
4848
|
}
|
|
@@ -4800,7 +4859,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4800
4859
|
/* Reduced motion accessibility */
|
|
4801
4860
|
@media (prefers-reduced-motion: reduce) {
|
|
4802
4861
|
.uvf-control-btn,
|
|
4803
|
-
.uvf-top-btn,
|
|
4804
4862
|
.uvf-center-play-btn,
|
|
4805
4863
|
.uvf-progress-handle,
|
|
4806
4864
|
.uvf-volume-slider,
|
|
@@ -4809,7 +4867,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4809
4867
|
}
|
|
4810
4868
|
|
|
4811
4869
|
.uvf-control-btn:hover,
|
|
4812
|
-
.uvf-top-btn:hover,
|
|
4813
4870
|
.uvf-center-play-btn:hover {
|
|
4814
4871
|
transform: none !important;
|
|
4815
4872
|
}
|
|
@@ -4835,11 +4892,6 @@ export class WebPlayer extends BasePlayer {
|
|
|
4835
4892
|
border-radius: 50%
|
|
4836
4893
|
}
|
|
4837
4894
|
|
|
4838
|
-
.uvf-top-btn {
|
|
4839
|
-
min-width: 44px;
|
|
4840
|
-
min-height: 44px;
|
|
4841
|
-
}
|
|
4842
|
-
|
|
4843
4895
|
.uvf-progress-bar {
|
|
4844
4896
|
height: 3px;
|
|
4845
4897
|
}
|
|
@@ -4858,22 +4910,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
4858
4910
|
background: var(--uvf-overlay-medium);
|
|
4859
4911
|
transform: scale(0.95);
|
|
4860
4912
|
}
|
|
4861
|
-
|
|
4862
|
-
.uvf-top-btn:active {
|
|
4863
|
-
background: var(--uvf-overlay-medium);
|
|
4864
|
-
transform: scale(0.95);
|
|
4865
|
-
}
|
|
4866
4913
|
}
|
|
4867
4914
|
|
|
4868
4915
|
/* Keyboard navigation and accessibility */
|
|
4869
4916
|
.uvf-control-btn:focus-visible,
|
|
4870
|
-
.uvf-top-btn:focus-visible,
|
|
4871
4917
|
.uvf-center-play-btn:focus-visible {
|
|
4872
4918
|
outline: 2px solid var(--uvf-primary-color, #007bff);
|
|
4873
4919
|
outline-offset: 2px;
|
|
4874
4920
|
background: var(--uvf-overlay-medium);
|
|
4875
4921
|
}
|
|
4876
4922
|
|
|
4923
|
+
.uvf-top-btn:focus-visible {
|
|
4924
|
+
outline: 2px solid var(--uvf-primary-color, #007bff);
|
|
4925
|
+
outline-offset: 2px;
|
|
4926
|
+
}
|
|
4927
|
+
|
|
4877
4928
|
.uvf-progress-bar-wrapper:focus-visible {
|
|
4878
4929
|
outline: 2px solid var(--uvf-primary-color, #007bff);
|
|
4879
4930
|
outline-offset: 2px;
|
|
@@ -4905,7 +4956,10 @@ export class WebPlayer extends BasePlayer {
|
|
|
4905
4956
|
|
|
4906
4957
|
/* High contrast mode support */
|
|
4907
4958
|
@media (prefers-contrast: high) {
|
|
4908
|
-
.uvf-control-btn
|
|
4959
|
+
.uvf-control-btn {
|
|
4960
|
+
border: 1px solid;
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4909
4963
|
.uvf-top-btn {
|
|
4910
4964
|
border: 1px solid;
|
|
4911
4965
|
}
|
|
@@ -5692,6 +5746,8 @@ export class WebPlayer extends BasePlayer {
|
|
|
5692
5746
|
} else {
|
|
5693
5747
|
volumeIcon.style.display = 'block';
|
|
5694
5748
|
muteIcon.style.display = 'none';
|
|
5749
|
+
// Hide unmute button when video is unmuted
|
|
5750
|
+
this.hideUnmuteButton();
|
|
5695
5751
|
}
|
|
5696
5752
|
}
|
|
5697
5753
|
});
|
|
@@ -5868,6 +5924,17 @@ export class WebPlayer extends BasePlayer {
|
|
|
5868
5924
|
const stopCastBtn = document.getElementById('uvf-stop-cast-btn');
|
|
5869
5925
|
const shareBtn = document.getElementById('uvf-share-btn');
|
|
5870
5926
|
|
|
5927
|
+
// Update cast button icon and functionality for iOS (AirPlay)
|
|
5928
|
+
if (this.isIOSDevice() && castBtn) {
|
|
5929
|
+
castBtn.innerHTML = `
|
|
5930
|
+
<svg viewBox="0 0 24 24">
|
|
5931
|
+
<path d="M1 18h6v-2H1v2zm0-4h12v-2H1v2zm16.5 4.5c-1.25 0-2.45-.5-3.35-1.41L12 18.5l2.09 2.09c1.8 1.8 4.72 1.8 6.52 0 1.8-1.8 1.8-4.72 0-6.52L12 5.5 3.39 14.11c-1.8 1.8-1.8 4.72 0 6.52.9.9 2.1 1.41 3.35 1.41l6.76-6.76M12 7.91l6.89 6.89c.78.78.78 2.05 0 2.83-.78.78-2.05.78-2.83 0L12 13.57 7.94 17.63c-.78.78-2.05.78-2.83 0-.78-.78-.78-2.05 0-2.83L12 7.91z"/>
|
|
5932
|
+
</svg>
|
|
5933
|
+
`;
|
|
5934
|
+
castBtn.setAttribute('title', 'AirPlay');
|
|
5935
|
+
castBtn.setAttribute('aria-label', 'AirPlay');
|
|
5936
|
+
}
|
|
5937
|
+
|
|
5871
5938
|
castBtn?.addEventListener('click', () => this.onCastButtonClick());
|
|
5872
5939
|
stopCastBtn?.addEventListener('click', () => this.stopCasting());
|
|
5873
5940
|
shareBtn?.addEventListener('click', () => this.shareVideo());
|
|
@@ -8211,6 +8278,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
8211
8278
|
}
|
|
8212
8279
|
|
|
8213
8280
|
private onCastButtonClick(): void {
|
|
8281
|
+
// On iOS, use AirPlay instead of Google Cast
|
|
8282
|
+
if (this.isIOSDevice()) {
|
|
8283
|
+
this.showAirPlayPicker();
|
|
8284
|
+
return;
|
|
8285
|
+
}
|
|
8286
|
+
|
|
8287
|
+
// Google Cast for non-iOS devices
|
|
8214
8288
|
try {
|
|
8215
8289
|
const castNs = (window as any).cast;
|
|
8216
8290
|
if (this.isCasting && castNs && castNs.framework) {
|
|
@@ -8222,6 +8296,31 @@ export class WebPlayer extends BasePlayer {
|
|
|
8222
8296
|
// Not casting yet
|
|
8223
8297
|
this.initCast();
|
|
8224
8298
|
}
|
|
8299
|
+
|
|
8300
|
+
/**
|
|
8301
|
+
* Show AirPlay picker for iOS devices
|
|
8302
|
+
*/
|
|
8303
|
+
private showAirPlayPicker(): void {
|
|
8304
|
+
if (!this.video) {
|
|
8305
|
+
this.showNotification('Video not ready');
|
|
8306
|
+
return;
|
|
8307
|
+
}
|
|
8308
|
+
|
|
8309
|
+
// Check if AirPlay is supported
|
|
8310
|
+
const videoElement = this.video as any;
|
|
8311
|
+
if (typeof videoElement.webkitShowPlaybackTargetPicker === 'function') {
|
|
8312
|
+
try {
|
|
8313
|
+
videoElement.webkitShowPlaybackTargetPicker();
|
|
8314
|
+
this.debugLog('AirPlay picker shown');
|
|
8315
|
+
} catch (error) {
|
|
8316
|
+
this.debugWarn('Failed to show AirPlay picker:', (error as Error).message);
|
|
8317
|
+
this.showNotification('AirPlay not available');
|
|
8318
|
+
}
|
|
8319
|
+
} else {
|
|
8320
|
+
this.debugWarn('AirPlay not supported on this device');
|
|
8321
|
+
this.showNotification('AirPlay not supported');
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8225
8324
|
|
|
8226
8325
|
private stopCasting(): void {
|
|
8227
8326
|
try {
|