Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading Multiple YouTube Videos iFrame API

I'm having an issue with the following code, but I'm not sure where the problem is.

On my master page, in javascript, I have an array defined to hold a list of YouTubePlayer objects that I create. I also load the YouTube API here.

I also have (sometime several) YouTube user control(s) that contains the YouTube div in a bootstrap modal. It also contains a JavaScript for pushing a new YouTubePlayer object to the array in the master page js. Lastly, on the user control, I define methods for auto-starting and stopping the video on the 'shown' and 'hide' events of the bootstrap modal.

Finally, to (hopefully) solve the race condition between the document being loaded and the YouTube API being loaded, I set two bool variables (one for document, one for API), and check both for true before calling an initVideos function which iterates through the array of YouTubePlayer objects and initializes them, setting the YT.Player object in the window. Part of the issue, I think, is that I can't statically set window.player1 etc., because I never know how many YouTube user controls will be loaded.

The problem is whenever the bootstrap modal events fire, the YT.Player object I retrieve from the window doesn't contain the methods for playVideo() and pauseVideo().

On my master page:

$(document).ready(function () {
    window.docReady = true;

    if (window.apiReady)
        initVideos();
});

function onYouTubeIframeAPIReady() {
    window.apiReady = true;

    if (window.docReady)
        initVideos();

    initVideos();
}

function initVideos() {

    if (typeof ytPlayerList === 'undefined')
        return;

    for (var i = 0; i < ytPlayerList.length; i++) {
        var player = ytPlayerList[i];
        var pl = new YT.Player(player.DivId, {
            playerVars: {
                'autoplay': '0',
                'controls': '1',
                'autohide': '2',
                'modestbranding': '1',
                'playsinline': '1',
                'rel': '0',
                'wmode': 'opaque'
            },
            videoId: player.VideoId,
            events: {
                'onStateChange': player.StateChangeHandler
            }
        });

        window[player.Id] = pl;
    }
}

And on the user control:

window.ytPlayerList.push({
    Id: "<%=ClientID%>player",
    DivId: "<%=ClientID%>player",
    VideoId: "<%=VideoId%>",
    StateChangeHandler: hide<%=ClientID%>Player
});

function hide<%=ClientID %>Player(state) {
    if (state.data == '0') {
        hide<%=ClientID %>Video();
    }
}

function show<%=ClientID %>Video() {
    $('#<%=ClientID %>video-modal').modal('show');
}

function hide<%=ClientID %>Video() {
    $('#<%=ClientID %>video-modal').modal('hide');
}

$(document).ready(function() {
    $("#<%=ClientID%>Video").click(function() {
        show<%=ClientID%>Video();
    });

    $("#<%=ClientID %>video-modal").on('shown', function () {
        window["<%=ClientID%>player"].playVideo();
    });

    $("#<%=ClientID %>video-modal").on('hide', function () {
        window["<%=ClientID%>player"].pauseVideo();
    });
});

This may be a lack of js expertise, but I'm absolutely stuck. Any help would be greatly appreciated. Also, for reference, the exception I get is

Uncaught TypeError: window.ctl100_phContent_ctl100player.playVideo is not a function
like image 733
Jack Avatar asked Apr 17 '15 21:04

Jack


1 Answers

So the problem is that the video only gets that playVideo() method when the YouTube API has finished loading the video element. This video element is loaded async, so the code execution will continue with the $(document).ready(function() { jQuery code where it will try to attach the playVideo() functions which at that point in time - do not yet exist.

I've reproduced this error in that HTML/JS page:

<!doctype html>
<html class="no-js" lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title></title>
</head>
<body>
    <div id="ctl100_phContent_ctl100player" style="border:1px solid red;"></div>
    <div id="ctl100_phContent_ctl101player" style="border:1px solid red;"></div>
    <div id="ctl100_phContent_ctl102player" style="border:1px solid red;"></div>

    <script src="https://www.youtube.com/iframe_api"></script>
    <script>window.jQuery || document.write('<script src="js/vendor/jquery-1.6.2.min.js"><\/script>')</script>
    <script>
        window.ytPlayerList = [];
        window.players = [];

        window.ytPlayerList.push({ Id: 'ctl100_phContent_ctl100player', DivId: 'ctl100_phContent_ctl100player', VideoId: 'IaHxPi9dM7o', StateChangeHandler: 'hide_player_1' });
        window.ytPlayerList.push({ Id: 'ctl100_phContent_ctl101player', DivId: 'ctl100_phContent_ctl101player', VideoId: 'IaHxPi9dM7o', StateChangeHandler: 'hide_player_2' });
        window.ytPlayerList.push({ Id: 'ctl100_phContent_ctl102player', DivId: 'ctl100_phContent_ctl102player', VideoId: 'IaHxPi9dM7o', StateChangeHandler: 'hide_player_3' });

        function onYouTubeIframeAPIReady() {
            initVideos();
        }

        function initVideos() {
            for (var i = 0; i < ytPlayerList.length; i++) {
                var player = ytPlayerList[i];
                var pl = new YT.Player(player.DivId, {
                    height: '390',
                    width: '640',
                    videoId: player.VideoId,
                });
                window[player.Id] = pl;
            }
        }

        window.ctl100_phContent_ctl100player.playVideo();
    </script>
</body>
</html>

This gives me the same error you describe. So in order to test my theory ( and make sure there were no other script errors ) I did this:

setTimeout(function() {
        window.ctl100_phContent_ctl100player.playVideo() 
}, 1000);

That did work. So indeed this seems to the problem. However - we don't know for sure that 1000ms is enough to guarantee that the player is initialized. So what you could do - if you want to not refactor too much - is start listening for the onready events of the player:

    function initVideos() {
        for (var i = 0; i < ytPlayerList.length; i++) {
            var player = ytPlayerList[i];
            var pl = new YT.Player(player.DivId, {
                height: '390',
                width: '640',
                videoId: player.VideoId,
                events: {
                     'onReady': window[player.Id + '_ready']
                }
            });
            window[player.Id] = pl;
        }
    }

Which assumes you used codegeneration to create functions like:

    function ctl100_phContent_ctl100player_ready() {
        // Hook up the hide() / onShow() and other behaviors here
        // ... to prove that playVideo() is a function here, I'm calling it
        window.ctl100_phContent_ctl100player.playVideo();
    }
    function ctl100_phContent_ctl101player_ready() {
        window.ctl100_phContent_ctl101player.playVideo();
    }
    function ctl100_phContent_ctl102player_ready() {
        window.ctl100_phContent_ctl102player.playVideo();
    }

So this is not a copy-paste solution for you problem but should give you insight into why the code is not working. There are probably more elegant ways to achieve what you're trying to do, but let's just keep the fix simple for now.

Let me know if it worked or if you have any other questions.

like image 62
Jochen van Wylick Avatar answered Oct 05 '22 17:10

Jochen van Wylick