Loading Multiple YouTube Videos iFrame API - javascript

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

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.

Related

How do I start and stop youtube videos created with the youtube iframe API?

I've created a function that wraps around YT.Player. It injects youtube videos into a document:
function swGetYoutubeVids ( elById, videoId ) {
var playerName = elById ;
window.playerName = new YT.Player( elById, {
height : '',
width: '',
videoId : videoId,
playerVars: {
enablejsapi : 1,
modestbranding : 1,
origin : playerOrigin,
showinfo : 0
},
}) ;
It works: A new video is created if I do swGetYoutubeVids('player_1', 'vha-Swtnj-U')
The function also creates a variable on window, so I can control the player. Well, at least that's the idea.
In the call above, window.player_1 gets created. I can confirm this by testing for the existence of window.player_1 from my browsers console.
However, window.player_1.playVideo() doesn't work.
According to the API docs, that call should start the video playing. Instead I get 'playVideo is not a function'.
So how do I actually play the video?
Here is my solution -> https://plnkr.co/edit/Tb0YpAtsiruK7H63wabj?p=preview
Loading the script is asynchronous. After initializing the player, you need to link events.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://www.youtube.com/iframe_api"></script>
</head>
<body>
<div id="player_1"></div>
<div class="wrap" style="display: none">
<button id="play" onclick="playVideo()">play</button>
<button id="pause" onclick="pauseVideo()">pause</button>
</div>
<script>
var player;
function onYouTubeIframeAPIReady(){
swGetYoutubeVids('player_1', 'vha-Swtnj-U');
}
function swGetYoutubeVids(elById, videoId ) {
player = new YT.Player(elById, {
height: '390',
width: '640',
videoId: videoId,
events: {
'onReady': onPlayerReady
},
playerVars: {
enablejsapi : 1,
modestbranding : 1,
//origin : playerOrigin,
showinfo : 0
},
});
}
function onPlayerReady(){
// here is player ready
console.info(player);
document.getElementsByClassName('wrap')[0].style.display = 'block';
}
function playVideo() {
player.playVideo()
}
function pauseVideo() {
player.pauseVideo()
}
</script>
</body>
</html>

embed video youtube video feed

I'm currently working with the youtube iframe API.
var player,
time_update_interval = 0;
function onYouTubeIframeAPIReady() {
player = new YT.Player('video-placeholder', {
width: 600,
height: 400,
videoId: 'Xa0Q0J5tOP0',
playerVars: {
color: 'white',
playlist: 'taJ60kskkns,FG0fTKAqZ5g'
},
events: {
onReady: initialize
}
});
}
function initialize(){
// Update the controls on load
updateTimerDisplay();
updateProgressBar();
// Clear any old interval.
clearInterval(time_update_interval);
// Start interval to update elapsed time display and
// the elapsed part of the progress bar every second.
time_update_interval = setInterval(function () {
updateTimerDisplay();
updateProgressBar();
}, 1000);
$('#volume-input').val(Math.round(player.getVolume()));
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.youtube.com/iframe_api"></script>
...<br>
<div id="video-placeholder"></div>
<br>...
So far it all works great but I want to display the latest video of a specific channel. I know it's something with user_feed but I don't know how exactly to do it as I'm a complete js/api beginner.
Thanks

Youtube iframe API does not work as expected

In one of my modals I want to display a youtube video. Which one (which ID) depends on which button is used to open the model.
<button class='yt-play' data-yt='xxxxxx'>Play video</button>
In my javascript file I'm using the YT player-api to generate an iframe; i followed the Getting started on google developers.
So In the modal I added an <div id='player'></div> and This is my included javascript:
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
videoId: '5ulO97zuVF0', //- just a temporary id
});
}
// on document ready do some jQuery things,
// like adding an event handler to the button.
$(document).ready(function (){
$('.yt-play').click(function(ev){
ev.preventDefault();
var videoid = $(this).data('yt');
player.loadVideoById(videoid);
$('#yt-player').modal('show');
player.playVideo();
});
});
The click-handler on yt-play should load the video by means of player.loadVideoById() as stated here in the documentation.
But somehow I get an javascript error: TypeError: player.loadVideoById is not a function
If I dump the player-object in the console I'm getting a nice player object; which holds amongst many others the loadVideoById function. At least it looks like it:
What's the reason the new video is not loaded?
It's possibly because the "loadVideoById" is not yet available.
You must construct your YT.Player Object with an events object, and include an "onReady" Event callback.
Then in your "onReady" callback function you bind your Button click event.
function onPlayerReady() {
$('.yt-play').click(function(ev){
ev.preventDefault();
var videoid = $(this).data('yt');
// player.loadVideoByID is now available as a function
player.loadVideoById(videoid);
$('#yt-player').modal('show');
player.playVideo();
});
}
player = new YT.Player('player', {
videoId: '5ulO97zuVF0', //- just a temporary id,
events:{
“onReady”: onPlayerReady
}
});
See the events section in the docs for more:
https://developers.google.com/youtube/iframe_api_reference#Events
add div element in html document
initial YT to global object in onYouTubeIframeAPIReady function
call YT object in your function
The code:
<script>
// load API
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// define player
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '360',
width: '640'
});
}
$(function () {
// load video and add event listeners
function loadVideo(id, start, end) {
// check if player is defined
if ((typeof player !== "undefined")) {
// listener for player state change
player.addEventListener('onStateChange', function (event) {
if (event.data == YT.PlayerState.ENDED) {
// do something
}
});
// listener if player is ready (including methods, like loadVideoById
player.addEventListener('onReady', function(event){
event.target.loadVideoById({
videoId: id,
startSeconds: start,
endSeconds: end
});
// pause player (my specific needs)
event.target.pauseVideo();
});
}
// if player is not defined, wait and try again
else {
setTimeout(loadVideo, 100, id, start, end);
}
}
// somewhere in the code
loadVideo('xxxxxxxx', 0, 3);
player.playVideo();
});
</script>
The other answers don't clarify the issue.
The YouTube api is confusing for sure! This is because the YT.Player constructer returns a dom reference to the iframe of the youtube player not to the YT player object.
In order to get a reference to the YT player we need to listen to the onReady event.
var ytPlayer;
var videoIframe = new YT.Player('player', {
videoId: 'xxxxxxx', // youtube video id
playerVars: {
'autoplay': 0,
'rel': 0,
'showinfo': 0
},
events: {
'onStateChange': onPlayerStateChange,
'onReady': function (ev) {
ytPlayer = ev.target;
}
}
});
Only 6 years late to the party, I have the solution.
The issue is that the YouTube API doesn't like the DOM shuffling Foundation does when it opens a modal.
The only way to achieve this is to create the YouTube player after opening the modal:
function play_video(ytid) {
modal = new Foundation.Reveal($('#yt_modal'),{});
modal.open();
ytplayer = new YT.Player('ytplayer', {
videoId: ytid,
height: '390',
width: '640',
playerVars: {
'playsinline': 1
},
events: {
'onReady': whatever()
}
});
}
This assumes
<div id="ytplayer"></div>
is inside your Foundation modal.
Oh and you'll need to remove the iframe each time else YouTube API just looks for the old iframe (which isn't where it expects, cos the modal is closed and the DOM has changed):
$(document).on('closed.zf.reveal', function(e) {
switch($(e.target).prop('id')) {
case 'yt_modal':
ytplayer.destroy();
break;
}
});
And regarding #Tosh's answer about the API returning a reference to the iframe not the player is not true as far as I can determine by comparing it to what's returned by onReady(ev.target) - they appear identical (so don't go down that blind alley like I did!)

Check if Embedded YouTube Video has Finished

So I'm writing a TamperMonkey script that sends specific videos to YouTube's embedded player however I want to automatically send them back once the video is finished.
So for example, video http://youtube.com/watch?v=XXXXX it would redirect to http://youtube.com/v/XXXXX. And then, once the video is complete, it would use window.history.go(-2) (as -1 would go to the normal page causing a loop).
The problem I'm having is that I haven't been able to get the second part, the function that runs when the video finishes, to work.
I have tried following the api and looking at other peoples problems and seeing what helped them but I can't seem to get it.
At the moment this is the code I have.
$(document).ready( function() {
var loc = document.location.href;
var l = loc.split('/');
var s = l[4];
var id = s.split('?')[0]
// create youtube player
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
height: '100%',
width: '100%',
videoId: id,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// autoplay video
function onPlayerReady(event) {
event.target.playVideo();
alert('spotted');
}
// when video ends
function onPlayerStateChange(event) {
if(event.data === 0) {
window.history.go(-2);
}
}
});
I would appreciate it if someone would work with me to get this script working.
Thanks.

Youtube video not loading with playlists

I'm having problems enqueuing a playlist using the YouTube IFrame API.
(https://developers.google.com/youtube/iframe_api_reference#Queueing_Functions)
I'm using flash to show the youtube videos. Using HTML5 shows another issue where the videoplayback call is called multiple times before the video loads.
On loading the playlist, the video itself doesn't load. It shows up in the network tab
as a call to videoplayback that is 'pending' until it times out after 7.5 minutes at which point it tries again and everything works. The playlist, incidentally, has loaded successfully - mousing over the youtube iframe shows a loaded playlist.
The code to replicate this is below, and the issue is found following these steps:
1. Click a channel
2. If channel loads, goto 1, else check network tab.
I know the method of replication is contrived, however I'm seeing this 'sometimes'
on first load.
The playing channel isn't at fault - this has been seen with many different channels.
Is it my code? Is there a work around? Is there a fix?
Tested on Windows 7 32bit using Chrome 28, Firefox 22.0 and IE 10
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<title>
Youtube Test
</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type='text/javascript'>
var collection = [];
var player = null;
var playlistEnqueued = false;
function clear() {
// Removes the iframe.
if (player !== null) {
player.destroy();
}
collection = [];
playlistEnqueued = false;
player = null;
}
function createYT(videoId) {
// Clear anything that's up currently
clear();
// Yes, this could be $.ajax, however this way reduces the dependency on jQuery
// further for the purposes of this test.
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
parseJSONResponse(JSON.parse(xmlhttp.responseText));
}
}
xmlhttp.open("GET", "https://gdata.youtube.com/feeds/users/" + videoId + "/uploads?alt=json", true);
xmlhttp.send();
}
function parseJSONResponse(data) {
var feed = data.feed;
$.each(feed.entry, function(index, entry, list) {
collection.push(entry.id.$t.match('[^/]*$')[0]);
});
playVideo();
}
function playVideo(videoId) {
try {
if (videoId === undefined || videoId === null) {
videoId = collection[0];
}
if (typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') {
window.onYouTubeIframeAPIReady = function() {
playVideo(videoId);
};
$.getScript('//www.youtube.com/iframe_api');
} else {
if (playlistEnqueued === true) {
player.playVideoAt(0);
} else {
player = new YT.Player('video', {
events: {
'onReady':function(event) {
try {
player.cuePlaylist(collection);
} catch (e) {
console.error(e);
}
},
'onError':function(error) {
console.error(error);
}
},
videoId: videoId,
width: 425,
height: 356,
playerVars: {
autoplay: 1,
}
});
// Attaching event listener after object creation due to
// http://stackoverflow.com/questions/17078094/youtube-iframe-player-api-onstatechange-not-firing
player.addEventListener('onStateChange', function(state) {
try {
stateChanged(state);
} catch (e) {
console.error(e);
}
});
}
}
} catch (e) {
console.error(e);
}
}
function stateChanged(state) {
// This state will be called on enqueueing a playlist
if (state.data == 5) {
playlistEnqueued = true;
playVideo();
}
}
$(document).on('ready', function() {
var player = $(document).find("#player");
$("a").on('click', function() {
var channel = $(this).attr('data-channel');
createYT(channel);
return false;
});
});
</script>
</head>
<body>
<div class='test'>
<div id='video'>
</div>
<a href='#' data-channel='mit'>MIT</a>
<a href='#' data-channel='tedtalksdirector'>TED</a>
<a href='#' data-channel='minutephysics'>Minute Physics</a>
</div>
</body>
</html>
When using flash, and on waiting for the video connection to timeout and try again the following is seen in the network tab. As you can see it's 'pending', failed, and then tried again after 7.5 minutes.
Chrome network tab once the video starts playing:
Chrome network tab on video playback
More images when I get past 10 reputation...
I've got this up and running on jsFiddle (http://jsfiddle.net/3Bm2V/5/) and it seems to be working ok. I did have to change the
$(document).on('ready', function () {
to
$(function() {
to get it to work in jsFiddle, so try the link above and see if it works for you now?

Categories