unified-video-framework 1.4.234 → 1.4.236
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 +1 -1
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts +25 -0
- package/packages/core/dist/interfaces/IVideoPlayer.d.ts.map +1 -1
- package/packages/core/dist/interfaces.d.ts +11 -0
- package/packages/core/dist/interfaces.d.ts.map +1 -1
- package/packages/core/src/interfaces/IVideoPlayer.ts +25 -0
- package/packages/core/src/interfaces.ts +9 -0
- package/packages/web/dist/WebPlayer.d.ts +11 -0
- package/packages/web/dist/WebPlayer.d.ts.map +1 -1
- package/packages/web/dist/WebPlayer.js +223 -30
- package/packages/web/dist/WebPlayer.js.map +1 -1
- package/packages/web/dist/react/WebPlayerView.d.ts +22 -0
- package/packages/web/dist/react/WebPlayerView.d.ts.map +1 -1
- package/packages/web/dist/react/WebPlayerView.js +23 -0
- package/packages/web/dist/react/WebPlayerView.js.map +1 -1
- package/packages/web/src/WebPlayer.ts +304 -35
- package/packages/web/src/react/WebPlayerView.tsx +44 -1
|
@@ -140,6 +140,15 @@ export class WebPlayer extends BasePlayer {
|
|
|
140
140
|
|
|
141
141
|
// Premium qualities configuration
|
|
142
142
|
private premiumQualities: any = null;
|
|
143
|
+
|
|
144
|
+
// Ad playing state (set by Google Ads Manager)
|
|
145
|
+
private isAdPlaying: boolean = false;
|
|
146
|
+
|
|
147
|
+
// Fallback source management
|
|
148
|
+
private fallbackSourceIndex: number = -1;
|
|
149
|
+
private fallbackErrors: Array<{ url: string; error: any }> = [];
|
|
150
|
+
private isLoadingFallback: boolean = false;
|
|
151
|
+
private currentRetryAttempt: number = 0;
|
|
143
152
|
|
|
144
153
|
// Debug logging helper
|
|
145
154
|
private debugLog(message: string, ...args: any[]): void {
|
|
@@ -560,6 +569,12 @@ export class WebPlayer extends BasePlayer {
|
|
|
560
569
|
// Reset autoplay flag for new source
|
|
561
570
|
this.autoplayAttempted = false;
|
|
562
571
|
|
|
572
|
+
// Reset fallback state for new source
|
|
573
|
+
this.fallbackSourceIndex = -1;
|
|
574
|
+
this.fallbackErrors = [];
|
|
575
|
+
this.isLoadingFallback = false;
|
|
576
|
+
this.currentRetryAttempt = 0;
|
|
577
|
+
|
|
563
578
|
// Clean up previous instances
|
|
564
579
|
await this.cleanup();
|
|
565
580
|
|
|
@@ -567,49 +582,254 @@ export class WebPlayer extends BasePlayer {
|
|
|
567
582
|
throw new Error('Video element not initialized');
|
|
568
583
|
}
|
|
569
584
|
|
|
585
|
+
// Setup error handler for automatic fallback
|
|
586
|
+
this.setupFallbackErrorHandler();
|
|
570
587
|
|
|
571
588
|
// Detect source type
|
|
572
589
|
const sourceType = this.detectSourceType(source);
|
|
573
590
|
|
|
574
591
|
try {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
592
|
+
await this.loadVideoSource(source.url, sourceType, source);
|
|
593
|
+
} catch (error) {
|
|
594
|
+
// Try fallback sources if available
|
|
595
|
+
const fallbackLoaded = await this.tryFallbackSource(error);
|
|
596
|
+
if (!fallbackLoaded) {
|
|
597
|
+
this.handleError({
|
|
598
|
+
code: 'LOAD_ERROR',
|
|
599
|
+
message: `Failed to load video: ${error}`,
|
|
600
|
+
type: 'network',
|
|
601
|
+
fatal: true,
|
|
602
|
+
details: error
|
|
603
|
+
});
|
|
604
|
+
throw error;
|
|
584
605
|
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Load a video source (main or fallback)
|
|
611
|
+
*/
|
|
612
|
+
private async loadVideoSource(url: string, sourceType: string, source: any): Promise<void> {
|
|
613
|
+
switch (sourceType) {
|
|
614
|
+
case 'hls':
|
|
615
|
+
await this.loadHLS(url);
|
|
616
|
+
break;
|
|
617
|
+
case 'dash':
|
|
618
|
+
await this.loadDASH(url);
|
|
619
|
+
break;
|
|
620
|
+
default:
|
|
621
|
+
await this.loadNative(url);
|
|
622
|
+
}
|
|
585
623
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
624
|
+
// Load subtitles if provided
|
|
625
|
+
if (source.subtitles && source.subtitles.length > 0) {
|
|
626
|
+
this.loadSubtitles(source.subtitles);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Apply metadata
|
|
630
|
+
if (source.metadata) {
|
|
631
|
+
if (source.metadata.posterUrl && this.video) {
|
|
632
|
+
this.video.poster = source.metadata.posterUrl;
|
|
589
633
|
}
|
|
634
|
+
// Update player UI with metadata (title, description, thumbnail)
|
|
635
|
+
this.updateMetadataUI();
|
|
636
|
+
} else {
|
|
637
|
+
// Clear to defaults if no metadata
|
|
638
|
+
this.updateMetadataUI();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Setup error handler for automatic fallback on video errors
|
|
644
|
+
*/
|
|
645
|
+
private setupFallbackErrorHandler(): void {
|
|
646
|
+
if (!this.video) return;
|
|
647
|
+
|
|
648
|
+
// Remove existing error handlers to avoid duplicates
|
|
649
|
+
const newVideo = this.video.cloneNode(false) as HTMLVideoElement;
|
|
650
|
+
this.video.replaceWith(newVideo);
|
|
651
|
+
this.video = newVideo;
|
|
590
652
|
|
|
591
|
-
|
|
592
|
-
if (
|
|
593
|
-
|
|
594
|
-
|
|
653
|
+
this.video.addEventListener('error', async (e) => {
|
|
654
|
+
if (this.isLoadingFallback) return; // Avoid recursive fallback attempts
|
|
655
|
+
|
|
656
|
+
const error = this.video?.error;
|
|
657
|
+
if (error) {
|
|
658
|
+
this.debugLog(`Video error detected (code: ${error.code}):`, error.message);
|
|
659
|
+
|
|
660
|
+
// Try fallback on media errors
|
|
661
|
+
const fallbackLoaded = await this.tryFallbackSource(error);
|
|
662
|
+
|
|
663
|
+
if (!fallbackLoaded) {
|
|
664
|
+
// No more fallbacks available, handle final error
|
|
665
|
+
this.handleError({
|
|
666
|
+
code: `MEDIA_ERR_${error.code}`,
|
|
667
|
+
message: error.message || this.getMediaErrorMessage(error.code),
|
|
668
|
+
type: 'media',
|
|
669
|
+
fatal: true,
|
|
670
|
+
details: error
|
|
671
|
+
});
|
|
595
672
|
}
|
|
596
|
-
// Update player UI with metadata (title, description, thumbnail)
|
|
597
|
-
this.updateMetadataUI();
|
|
598
|
-
} else {
|
|
599
|
-
// Clear to defaults if no metadata
|
|
600
|
-
this.updateMetadataUI();
|
|
601
673
|
}
|
|
674
|
+
});
|
|
675
|
+
}
|
|
602
676
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
});
|
|
611
|
-
throw error;
|
|
677
|
+
/**
|
|
678
|
+
* Try loading the next fallback source
|
|
679
|
+
*/
|
|
680
|
+
private async tryFallbackSource(error: any): Promise<boolean> {
|
|
681
|
+
if (this.isLoadingFallback) return false;
|
|
682
|
+
if (!this.source?.fallbackSources || this.source.fallbackSources.length === 0) {
|
|
683
|
+
return this.showFallbackPoster();
|
|
612
684
|
}
|
|
685
|
+
|
|
686
|
+
this.isLoadingFallback = true;
|
|
687
|
+
|
|
688
|
+
// Record current error
|
|
689
|
+
const currentUrl = this.fallbackSourceIndex === -1
|
|
690
|
+
? this.source.url
|
|
691
|
+
: this.source.fallbackSources[this.fallbackSourceIndex]?.url;
|
|
692
|
+
|
|
693
|
+
this.fallbackErrors.push({ url: currentUrl, error });
|
|
694
|
+
this.debugLog(`Source failed: ${currentUrl}`, error);
|
|
695
|
+
|
|
696
|
+
// Check retry attempts for current source
|
|
697
|
+
const maxRetries = this.source.fallbackRetryAttempts || 1;
|
|
698
|
+
if (this.currentRetryAttempt < maxRetries) {
|
|
699
|
+
this.currentRetryAttempt++;
|
|
700
|
+
this.debugLog(`Retrying source (attempt ${this.currentRetryAttempt}/${maxRetries}): ${currentUrl}`);
|
|
701
|
+
|
|
702
|
+
const retryDelay = this.source.fallbackRetryDelay || 1000;
|
|
703
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
const sourceType = this.detectSourceType({ url: currentUrl, type: this.source.type });
|
|
707
|
+
await this.loadVideoSource(currentUrl, sourceType, this.source);
|
|
708
|
+
this.isLoadingFallback = false;
|
|
709
|
+
this.currentRetryAttempt = 0;
|
|
710
|
+
this.debugLog(`Retry successful for: ${currentUrl}`);
|
|
711
|
+
return true;
|
|
712
|
+
} catch (retryError) {
|
|
713
|
+
this.debugLog(`Retry failed for: ${currentUrl}`, retryError);
|
|
714
|
+
// Continue to next fallback
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Move to next fallback source
|
|
719
|
+
this.currentRetryAttempt = 0;
|
|
720
|
+
this.fallbackSourceIndex++;
|
|
721
|
+
|
|
722
|
+
if (this.fallbackSourceIndex >= this.source.fallbackSources.length) {
|
|
723
|
+
// All sources exhausted
|
|
724
|
+
this.isLoadingFallback = false;
|
|
725
|
+
this.debugLog('All video sources failed. Attempting to show fallback poster.');
|
|
726
|
+
|
|
727
|
+
// Trigger callback if provided
|
|
728
|
+
if (this.source.onAllSourcesFailed) {
|
|
729
|
+
try {
|
|
730
|
+
this.source.onAllSourcesFailed(this.fallbackErrors);
|
|
731
|
+
} catch (callbackError) {
|
|
732
|
+
console.error('Error in onAllSourcesFailed callback:', callbackError);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return this.showFallbackPoster();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Try next fallback source
|
|
740
|
+
const fallbackSource = this.source.fallbackSources[this.fallbackSourceIndex];
|
|
741
|
+
this.debugLog(`Trying fallback source ${this.fallbackSourceIndex + 1}/${this.source.fallbackSources.length}: ${fallbackSource.url}`);
|
|
742
|
+
|
|
743
|
+
const retryDelay = this.source.fallbackRetryDelay || 1000;
|
|
744
|
+
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
745
|
+
|
|
746
|
+
try {
|
|
747
|
+
const sourceType = this.detectSourceType(fallbackSource);
|
|
748
|
+
await this.loadVideoSource(fallbackSource.url, sourceType, this.source);
|
|
749
|
+
this.isLoadingFallback = false;
|
|
750
|
+
this.debugLog(`Successfully loaded fallback source: ${fallbackSource.url}`);
|
|
751
|
+
this.showNotification(`Switched to backup source ${this.fallbackSourceIndex + 1}`);
|
|
752
|
+
return true;
|
|
753
|
+
} catch (fallbackError) {
|
|
754
|
+
this.debugLog(`Fallback source failed: ${fallbackSource.url}`, fallbackError);
|
|
755
|
+
this.isLoadingFallback = false;
|
|
756
|
+
// Recursively try next fallback
|
|
757
|
+
return this.tryFallbackSource(fallbackError);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Show fallback poster image when all video sources fail
|
|
763
|
+
*/
|
|
764
|
+
private showFallbackPoster(): boolean {
|
|
765
|
+
if (!this.source?.fallbackPoster) {
|
|
766
|
+
this.debugLog('No fallback poster available');
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
this.debugLog('Showing fallback poster:', this.source.fallbackPoster);
|
|
771
|
+
|
|
772
|
+
if (this.video) {
|
|
773
|
+
// Hide video element, show poster
|
|
774
|
+
this.video.style.display = 'none';
|
|
775
|
+
this.video.poster = this.source.fallbackPoster;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Create poster overlay
|
|
779
|
+
const posterOverlay = document.createElement('div');
|
|
780
|
+
posterOverlay.id = 'uvf-fallback-poster';
|
|
781
|
+
posterOverlay.style.cssText = `
|
|
782
|
+
position: absolute;
|
|
783
|
+
top: 0;
|
|
784
|
+
left: 0;
|
|
785
|
+
width: 100%;
|
|
786
|
+
height: 100%;
|
|
787
|
+
background-image: url('${this.source.fallbackPoster}');
|
|
788
|
+
background-size: cover;
|
|
789
|
+
background-position: center;
|
|
790
|
+
background-repeat: no-repeat;
|
|
791
|
+
z-index: 10;
|
|
792
|
+
display: flex;
|
|
793
|
+
align-items: center;
|
|
794
|
+
justify-content: center;
|
|
795
|
+
`;
|
|
796
|
+
|
|
797
|
+
// Add error message overlay
|
|
798
|
+
const errorMessage = document.createElement('div');
|
|
799
|
+
errorMessage.style.cssText = `
|
|
800
|
+
background: rgba(0, 0, 0, 0.8);
|
|
801
|
+
color: white;
|
|
802
|
+
padding: 20px 30px;
|
|
803
|
+
border-radius: 8px;
|
|
804
|
+
text-align: center;
|
|
805
|
+
max-width: 400px;
|
|
806
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
807
|
+
`;
|
|
808
|
+
errorMessage.innerHTML = `
|
|
809
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-bottom: 10px;">
|
|
810
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
811
|
+
<line x1="12" y1="8" x2="12" y2="12"></line>
|
|
812
|
+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
|
813
|
+
</svg>
|
|
814
|
+
<div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">Video Unavailable</div>
|
|
815
|
+
<div style="font-size: 14px; opacity: 0.9;">This video cannot be played at the moment.</div>
|
|
816
|
+
`;
|
|
817
|
+
|
|
818
|
+
posterOverlay.appendChild(errorMessage);
|
|
819
|
+
|
|
820
|
+
// Remove existing fallback poster if any
|
|
821
|
+
const existingPoster = this.playerWrapper?.querySelector('#uvf-fallback-poster');
|
|
822
|
+
if (existingPoster) {
|
|
823
|
+
existingPoster.remove();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Add to player
|
|
827
|
+
if (this.playerWrapper) {
|
|
828
|
+
this.playerWrapper.appendChild(posterOverlay);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
this.showNotification('Video unavailable');
|
|
832
|
+
return true;
|
|
613
833
|
}
|
|
614
834
|
|
|
615
835
|
private detectSourceType(source: VideoSource): string {
|
|
@@ -6653,6 +6873,13 @@ export class WebPlayer extends BasePlayer {
|
|
|
6653
6873
|
return;
|
|
6654
6874
|
}
|
|
6655
6875
|
|
|
6876
|
+
// Block all keyboard controls during ads (Google IMA handles ad controls)
|
|
6877
|
+
if (this.isAdPlaying) {
|
|
6878
|
+
this.debugLog('Keyboard blocked: Ad is playing');
|
|
6879
|
+
e.preventDefault();
|
|
6880
|
+
return;
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6656
6883
|
// Debug logging
|
|
6657
6884
|
this.debugLog('Keyboard event:', e.key, 'target:', target.tagName);
|
|
6658
6885
|
|
|
@@ -8795,6 +9022,21 @@ export class WebPlayer extends BasePlayer {
|
|
|
8795
9022
|
}
|
|
8796
9023
|
}
|
|
8797
9024
|
|
|
9025
|
+
/**
|
|
9026
|
+
* Set ad playing state (called by Google Ads Manager)
|
|
9027
|
+
*/
|
|
9028
|
+
public setAdPlaying(isPlaying: boolean): void {
|
|
9029
|
+
this.isAdPlaying = isPlaying;
|
|
9030
|
+
this.debugLog('Ad playing state:', isPlaying);
|
|
9031
|
+
}
|
|
9032
|
+
|
|
9033
|
+
/**
|
|
9034
|
+
* Check if ad is currently playing
|
|
9035
|
+
*/
|
|
9036
|
+
public isAdCurrentlyPlaying(): boolean {
|
|
9037
|
+
return this.isAdPlaying;
|
|
9038
|
+
}
|
|
9039
|
+
|
|
8798
9040
|
/**
|
|
8799
9041
|
* Check if a quality level is premium
|
|
8800
9042
|
*/
|
|
@@ -9441,18 +9683,45 @@ export class WebPlayer extends BasePlayer {
|
|
|
9441
9683
|
}
|
|
9442
9684
|
|
|
9443
9685
|
private async shareVideo(): Promise<void> {
|
|
9444
|
-
|
|
9445
|
-
const
|
|
9446
|
-
|
|
9686
|
+
// Get share configuration
|
|
9687
|
+
const shareConfig = this.config.share;
|
|
9688
|
+
|
|
9689
|
+
// Determine share URL
|
|
9690
|
+
let shareUrl: string;
|
|
9691
|
+
if (shareConfig?.url) {
|
|
9692
|
+
// Use custom static URL
|
|
9693
|
+
shareUrl = shareConfig.url;
|
|
9694
|
+
} else if (shareConfig?.generateUrl) {
|
|
9695
|
+
// Use dynamic URL generator
|
|
9696
|
+
try {
|
|
9697
|
+
shareUrl = shareConfig.generateUrl({
|
|
9698
|
+
videoId: this.source?.metadata?.videoId,
|
|
9699
|
+
metadata: this.source?.metadata
|
|
9700
|
+
});
|
|
9701
|
+
} catch (error) {
|
|
9702
|
+
console.warn('Share URL generator failed, falling back to window.location.href:', error);
|
|
9703
|
+
shareUrl = window.location.href;
|
|
9704
|
+
}
|
|
9705
|
+
} else {
|
|
9706
|
+
// Default: Use current page URL
|
|
9707
|
+
shareUrl = window.location.href;
|
|
9708
|
+
}
|
|
9709
|
+
|
|
9710
|
+
// Prepare share data
|
|
9711
|
+
const shareData: ShareData = { url: shareUrl };
|
|
9712
|
+
|
|
9713
|
+
// Get title and text from config or metadata
|
|
9714
|
+
const t = (shareConfig?.title || this.source?.metadata?.title || '').toString().trim();
|
|
9715
|
+
const d = (shareConfig?.text || this.source?.metadata?.description || '').toString().trim();
|
|
9447
9716
|
if (t) shareData.title = t;
|
|
9448
9717
|
if (d) shareData.text = d;
|
|
9449
|
-
|
|
9718
|
+
|
|
9450
9719
|
try {
|
|
9451
9720
|
if (navigator.share) {
|
|
9452
9721
|
await navigator.share(shareData);
|
|
9453
9722
|
} else {
|
|
9454
9723
|
// Fallback: Copy to clipboard
|
|
9455
|
-
await navigator.clipboard.writeText(
|
|
9724
|
+
await navigator.clipboard.writeText(shareUrl);
|
|
9456
9725
|
this.showNotification('Link copied to clipboard');
|
|
9457
9726
|
}
|
|
9458
9727
|
} catch (error) {
|
|
@@ -137,7 +137,16 @@ export type WebPlayerViewProps = {
|
|
|
137
137
|
|
|
138
138
|
// Framework branding control
|
|
139
139
|
showFrameworkBranding?: boolean;
|
|
140
|
-
|
|
140
|
+
|
|
141
|
+
// Share configuration
|
|
142
|
+
share?: {
|
|
143
|
+
enabled?: boolean; // Enable/disable share button (default: true)
|
|
144
|
+
url?: string; // Custom share URL (default: window.location.href)
|
|
145
|
+
title?: string; // Custom share title (default: video metadata title)
|
|
146
|
+
text?: string; // Custom share description (default: video metadata description)
|
|
147
|
+
generateUrl?: (videoData: { videoId?: string; metadata?: any }) => string; // Dynamic URL generator
|
|
148
|
+
};
|
|
149
|
+
|
|
141
150
|
// Watermark configuration (can be boolean for simple enable/disable or object for full config)
|
|
142
151
|
watermark?: boolean | {
|
|
143
152
|
enabled?: boolean;
|
|
@@ -201,6 +210,13 @@ export type WebPlayerViewProps = {
|
|
|
201
210
|
subtitles?: SubtitleTrack[];
|
|
202
211
|
metadata?: VideoMetadata;
|
|
203
212
|
|
|
213
|
+
// Fallback configuration
|
|
214
|
+
fallbackSources?: Array<{ url: string; type?: 'mp4' | 'hls' | 'dash' | 'webm' | 'auto'; priority?: number }>;
|
|
215
|
+
fallbackPoster?: string; // Static image to show when all video sources fail
|
|
216
|
+
fallbackRetryDelay?: number; // Delay in ms before trying next fallback (default: 1000)
|
|
217
|
+
fallbackRetryAttempts?: number; // Number of retry attempts per source (default: 1)
|
|
218
|
+
onAllSourcesFailed?: (errors: Array<{ url: string; error: any }>) => void; // Callback when all sources fail
|
|
219
|
+
|
|
204
220
|
// Optional Google Cast sender SDK loader
|
|
205
221
|
cast?: boolean;
|
|
206
222
|
|
|
@@ -838,6 +854,7 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
838
854
|
settings: props.settings,
|
|
839
855
|
showFrameworkBranding: props.showFrameworkBranding,
|
|
840
856
|
watermark: watermarkConfig,
|
|
857
|
+
share: props.share, // Add share configuration
|
|
841
858
|
qualityFilter: props.qualityFilter, // Add quality filter to config
|
|
842
859
|
premiumQualities: props.premiumQualities, // Add premium qualities config
|
|
843
860
|
// Navigation configuration
|
|
@@ -879,6 +896,11 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
879
896
|
type: props.type ?? 'auto',
|
|
880
897
|
subtitles: props.subtitles,
|
|
881
898
|
metadata: props.metadata,
|
|
899
|
+
fallbackSources: props.fallbackSources,
|
|
900
|
+
fallbackPoster: props.fallbackPoster,
|
|
901
|
+
fallbackRetryDelay: props.fallbackRetryDelay,
|
|
902
|
+
fallbackRetryAttempts: props.fallbackRetryAttempts,
|
|
903
|
+
onAllSourcesFailed: props.onAllSourcesFailed,
|
|
882
904
|
};
|
|
883
905
|
|
|
884
906
|
await player.load(source);
|
|
@@ -1076,18 +1098,34 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1076
1098
|
companionAdSlots: props.googleAds.companionAdSlots,
|
|
1077
1099
|
onAdStart: () => {
|
|
1078
1100
|
setIsAdPlaying(true);
|
|
1101
|
+
// Notify player to block keyboard controls
|
|
1102
|
+
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1103
|
+
(player as any).setAdPlaying(true);
|
|
1104
|
+
}
|
|
1079
1105
|
props.googleAds?.onAdStart?.();
|
|
1080
1106
|
},
|
|
1081
1107
|
onAdEnd: () => {
|
|
1082
1108
|
setIsAdPlaying(false);
|
|
1109
|
+
// Notify player to unblock keyboard controls
|
|
1110
|
+
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1111
|
+
(player as any).setAdPlaying(false);
|
|
1112
|
+
}
|
|
1083
1113
|
props.googleAds?.onAdEnd?.();
|
|
1084
1114
|
},
|
|
1085
1115
|
onAdError: (error) => {
|
|
1086
1116
|
setIsAdPlaying(false);
|
|
1117
|
+
// Notify player to unblock keyboard controls
|
|
1118
|
+
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1119
|
+
(player as any).setAdPlaying(false);
|
|
1120
|
+
}
|
|
1087
1121
|
props.googleAds?.onAdError?.(error);
|
|
1088
1122
|
},
|
|
1089
1123
|
onAllAdsComplete: () => {
|
|
1090
1124
|
setIsAdPlaying(false);
|
|
1125
|
+
// Notify player to unblock keyboard controls
|
|
1126
|
+
if (typeof (player as any).setAdPlaying === 'function') {
|
|
1127
|
+
(player as any).setAdPlaying(false);
|
|
1128
|
+
}
|
|
1091
1129
|
props.googleAds?.onAllAdsComplete?.();
|
|
1092
1130
|
},
|
|
1093
1131
|
onAdCuePoints: (cuePoints: number[]) => {
|
|
@@ -1170,10 +1208,15 @@ export const WebPlayerView: React.FC<WebPlayerViewProps> = (props) => {
|
|
|
1170
1208
|
JSON.stringify(props.settings),
|
|
1171
1209
|
props.showFrameworkBranding,
|
|
1172
1210
|
JSON.stringify(props.watermark),
|
|
1211
|
+
JSON.stringify(props.share),
|
|
1173
1212
|
JSON.stringify(props.navigation),
|
|
1174
1213
|
JSON.stringify(props.googleAds),
|
|
1175
1214
|
JSON.stringify(props.qualityFilter),
|
|
1176
1215
|
JSON.stringify(props.premiumQualities),
|
|
1216
|
+
JSON.stringify(props.fallbackSources),
|
|
1217
|
+
props.fallbackPoster,
|
|
1218
|
+
props.fallbackRetryDelay,
|
|
1219
|
+
props.fallbackRetryAttempts,
|
|
1177
1220
|
]);
|
|
1178
1221
|
|
|
1179
1222
|
// Helper function to filter quality levels based on qualityFilter prop
|