htmltypescriptvideoyoutubevimeo

Is there a way to recognize when Video Players (YouTube + Vimeo) get blocked by 403 Forbidden to display an image instead?


We have a site on which we display a stage video for all users. But there is a specific client that blocks all videos with a 403 forbidden status on their devices and we want to display an image when we notice that the videos are blocked.

We embed videos from either YouTube or Vimeo.

Dependencies

HTML

Vimeo

<div class="vimeo__player js-vimeo__player embed-responsive embed-responsive-16by9"
     data-video-id-16by9="true"
     data-vimeo-autoplay="true"
     data-vimeo-loop="true"
     data-vimeo-muted="true"
     id="unique-video-id">
</div>

YouTube

<div class="vimeo__player js-youtube__player embed-responsive embed-responsive-16by9" id="ytplayer">
    <iframe class="ytplayer"
            src="https://www.youtube.com/embed/<YOUTUBE_VIDEO_ID>?playlist=<YOUTUBE_VIDEO_ID>&controls=0&mute=1&autoplay=1&loop=1&modestbranding=1&showinfo=0&rel=0"
            title="YouTube video player"
            frameborder="0"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture">
    </iframe>
</div>

Is there a way to recognize when the video gets blocked with a status code of 403 forbidden?


Solution

  • After researching this issue for some time, the solution that worked for me was the following:

    General init

    // If there are videos (either Vimeo or YouTube) present on the page,
    // initialize them
    if (hasYouTubeVideo) {
        initYouTubeIFrameApi();
    }
    if (hasVimeoVideo) {
        createVimeoVideos();
    }
    

    YouTube implementation (iFrame API)

    let ytIFrameApiInitialized = false;
    
    function initYouTubeIFrameApi(): void {
        // Only run iFrame API init once
        if (ytIFrameApiInitialized) {
            return;
        }
    
        // This code loads the IFrame Player API code asynchronously.
        const tag = document.createElement('script');
        tag.onerror = () => onIFrameApiError();
        tag.src = "https://www.youtube.com/iframe_api";
        const firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    
        // Set boolean to true to not initialize it more than once
        ytIFrameApiInitialized = true;
    }
    
    // In case the initial script load of the YouTube iFrame API
    // encounters an error, this function will be called.
    function onIFrameApiError(): void {
        console.error('Error occurred during YouTube iFrame API initialization');
        // Then ==> Show images instead of YouTube video
        // (for all YouTube videos)
    }
    
    // The onYouTubeIframeAPIReady function needs to be declared as a
    // window object in TypeScript to be available when called by the
    // API in another scope
    declare global {  
        interface Window { onYouTubeIframeAPIReady: any; }  
    }  
    window.onYouTubeIframeAPIReady = function() {  
        onYouTubeIframeAPIReady();  
    }  
    // This function creates an <iframe> (and YouTube player) after the
    // API code downloads  
    function onYouTubeIframeAPIReady() {  
        createYouTubeVideos();  
    }
    
    // The actual function to create YouTube videos for multiple
    // components on the page
    function createYouTubeVideos(): void {  
        // Loop through all available YouTube video components
    document.querySelectorAll(selectorOfYouTubeVideoComponent).forEach((youTubeVideo) => {  
            // create new YouTube player
            new YT.Player(youTubePlayerId, {  
                videoId: youTubeVideoId,  
                events: {  
                    'onReady': onPlayerReady,  
                    'onError': onYouTubeError,  
                },
                playerVars: {  
                    // options ... 
                },
            });
        });
    
        // The YouTube iFrame API will call this function when the video
        // player is ready
        function onPlayerReady(e) {  
            e.target.playVideo();  
        }
     
        // The YouTube iFrame API will call this function when there's an
        // error when playing the video
        function onYouTubeError(e): void {  
            console.error('Error occurred during YouTube video player (video-ID: ' + e.target.g.id + ')');
            // Then ==> Show image instead of video
            // (only this specific video)
        }
    }
    

    Vimeo implementation ("@vimeo/player": "^2.13.0")

    function createVimeoVideos(): void {
        // Loop through all available Vimeo video components
    document.querySelectorAll(selectorOfVimeoComponent).forEach((vimeoVideo) => {
            const vimeoPlayer: VimeoPlayer = new VimeoPlayer(vimeoPlayerId, options);
    
            // Catching errors of vimeo player when indicating "ready"
            vimeoPlayer.ready().then(() => {
                // console.log('==> Vimeo Player "ready"!');
            }).catch(error => {
                console.error('Error occurred on Vimeo video player during "ready" (video-ID: ' + vimeoId + '):', error);
                // Then ==> Show image instead of video
                // (only this specific video)
            });
    
            // Catching errors of vimeo player when indicating "play" (not required to start the player, though)
            vimeoPlayer.play().then(() => {
                // console.log('==> Vimeo Player "play"!');
            }).catch(error => {
                console.error('Error occurred on Vimeo video player during "play" (video-ID: ' + vimeoId + '):', error);
                // Then ==> Show image instead of video
                // (only this specific video)
            });
    
            // Catching general errors of vimeo player when indicating "error"
            vimeoPlayer.on('error', (error) => {
                console.error('Error occurred on Vimeo video player with "error" (video-ID: ' + vimeoId + '):', error);
                // Then ==> Show image instead of video
                // (only this specific video)
            });
    
            /**
             * Attention: A timeout solution most likely never really is a
             * good option, but in this case and with my current ability,
             * it was the only solution that worked in that specific case
             * where only one of the CDN of Vimeo were blocked, but not
             * "vimeo.com" itself:
             * - https://f.vimeocdn.com
             * - https://vod-adaptive-ak.vimeocdn.com
             *
             * Catch instantiating error for Vimeo player if no valid
             * origin URL could be set after the given timeout.
             * Especially needed the specific case in which
             * "https://f.vimeocdn.com" would be blocked by the user's
             * system. The timeout should not be lower than 1000, as it
             * might fire too early, even though the player might still
             * work.
             */
            setTimeout(() => {
                // @ts-ignore (needed for "origin" not being present in
                // the Player-class, but available on the player object)
                if (vimeoPlayer.origin === '*') {
                    console.error('Vimeo video player (video-ID: ' + vimeoId + ') could not be initiated.');
                    // Then ==> Show image instead of Vimeo video
                    // (this specific video)
                }
            }, 1500);
        });
    }