YouTube iFrame API behavior is inconsistent/nondeterministic - javascript

I was having trouble isolating a bug in my JavaScript code for controlling Youtube videos via the iframe/HTML5 API.
I went back to the demo code on the main documentation page:
https://developers.google.com/youtube/iframe_api_reference
...and to my surprise, I see the same inconsistent behavior even when I use the demo code from there and don't change anything.
The behavior is that some of the time, when I load this page, the player autoplays (which is the intent of the code), and other times it doesn't. It always does succeed in loading. Also, the player never seems to stop after 6 seconds, in contrary to what the comments say.
Breakpoints verify that part of the issue at least is that onPlayerReady() is not always being called. When I set a breakpoint in onPlayerReady(), it usually is not reached, and is only reached the times that the player goes on to succeed in autoplaying.
Of course, this behavior may be dependent on the speed and reliability of my internet connection, which is wired and otherwise seems decently fast. I just tested it -- 24 Mbps, and it seems pretty consistent.
If I make superficial changes to the html, that seems to sometimes prompt the page on loading to autoplay, but not always. Sometimes I will reload every few seconds 5 times in a row with no autoplay or onPlayerReady breakpoint being hit, then do it a 6th time and that time it will autoplay fine.
I'm using Chrome v30.0.1599.101 on Mac OS 10.8.4.
I know that code is in beta and in flux, and isn't supposed to be production level yet, but I was hoping there was something i can try.
Here is the code I'm using, FYI, in case the code posted on the api reference page above changes. Again, I'm not altering a single character.
<!DOCTYPE html>
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
</script>
</body>
</html>

I was opening the page locally, which makes for different behavior in all sorts of little ways from how it works when delivered from a server. The browser client itself handles things like cacheing differently when content is local. When I put the same page on a server, it started to work every time.
In addition, FYI, for my bigger issue that led me down this rabbithole, I found that I needed to follow this advice: https://stackoverflow.com/a/14952334/2308190 ...in order to get my YouTube callback functions to be called with jQuery loaded.

I had a situation where my first YouTube iframe would load correctly, and subsequent ones would not.
The issue was caused by creating a new YT.Player before its <div> had been added to the DOM. This issue didn't occur the first time I created a YT.Player, because the YouTube API had to be loaded first. This caused a microsecond delay which allowed the rest of my code to execute, so the <div> was added to the DOM before the YouTube API finished loading.

Related

Get video play of youtube player

I'm creating this webpage on wordpress which have videos from youtube in some of it's posts, for tagging purposes I need to capture everytime a person clicks play on each of the videos. I've looked through the web, but all I could find is that, since this videos are on iframes from outside the main domain, it's impossible to catch something from the inside of it, like clicks.
Is there any way that I can catch this clicks on the play button without changing anything on the site? Just from JS.
Regards and thanks in advance.
You could capture some events within the iframe using youtube's js API
This is what you have to do:
1). Load the Player API in your page
<script src="http://www.youtube.com/player_api"></script>
2). Enable jsapi in your iframe as a trailing parameter (enablejsapi) like
<iframe id="myIframe" src="http://www.youtube.com/embed/opj24KnzrWo?enablejsapi=1&autoplay=0" width="420" height="280" frameborder="0"></iframe>
... also notice we added an ID to the iframe.
3). Create 3 functions :
a). onPlayerReady :
Here is where you can detect when the video has loaded and is ready to play. Here you can actually start the video, etc.
function onPlayerReady(event) {
// video is ready, do something
event.target.playVideo();
}
b). onPlayerStateChange :
Here is where you can detect what events are triggered:
function onPlayerStateChange(event) {
console.log(event.data); // event triggered
// e.g. end of video
if (event.data === 0) {
// end of video: do something here
}
}
When you click play or pause, an event is triggered. Then you can decide what action you want do, including pushing a tracking event, etc.
This is a list of the returned values of each event :
-1 – unstarted
0 – ended
1 – playing
2 – paused
3 – buffering
5 – video cued
Refer to the documentation to learn more about those events
c). onYouTubePlayerAPIReady :
This is where you actually initialize the API and bind the events to the other functions :
function onYouTubePlayerAPIReady() {
var id = document.getElementById("myIframe").getAttribute("id");
var player = new YT.Player(id, {
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
} // youtube API ready
See JSFIDDLE

When using Session variables to control my embedded YouTube player, I receive the error: Uncaught TypeError: Cannot call method 'postMessage' of null?

A link to the page where it is deployed
After attempting to control using Session, the YouTube embedded player stops being responsive, and returns the error: Uncaught TypeError: Cannot call method 'postMessage' of null. This doesn't happen everytime, but can be reproduced by entering a song in the link and clicking on the play button.
Here is the setting up of the YouTube API temporarily placed in the HTML file:
mixtape.html
<script>
// Called automatically when JavaScript client library is loaded.
function onClientLoad() {
gapi.client.load('youtube', 'v3', onYouTubeApiLoad);
}
// Called automatically when YouTube API interface is loaded
function onYouTubeApiLoad() {
gapi.client.setApiKey('AIzaSyD1VcsNnysOY6_Za-8kE-BK6Zh8jQwvo4w');
}
</script>
<script src="https://apis.google.com/js/client.js?onload=onClientLoad"></script>
<script src= "https://www.youtube.com/iframe_api"></script>
<script>
// Load the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Replace the 'ytplayer' element with an <iframe> and
// YouTube player after the API code downloads.
var player;
function onYouTubePlayerAPIReady() {
console.log("playerAPIready");
player = new YT.Player('ytplayer', {
height: '400',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
//console.log("loaded");
$("#player").hide();
}
function onPlayerReady(){
//console.log("ready");
}
function onPlayerStateChange(){
//console.log("changed");
}
function generatePlaylist(list){
console.log("list: ", list)
player.loadPlaylist(list);
}
</script>
And the printing out occurs here. The nav_playlist reads from a Session variable which contains what it should, but the printing out below pretens that nav_playlist is empty.
<template name="player">
<div id="player">
<div id="ytplayer"></div>
<div id="nav">
<br>
{{#each nav_playlist}}
{{> unremovable_track}}
{{/each}}
</div>
</div>
</template>
```
An in mixtape.js
```
Template.player.nav_playlist = function(){
return Session.get("current_list");
}
```
And here are the Session variables being given their values. These contain exactly what they should. They update when the user clicks the play button.
/*Update List on generate button*/
Template.list.updateList = function(){
var ret = [];
$( "#playlist .list_element" ).each(function() {
if($(this).is(':visible')){
ret.push( Links.findOne({_id:$(this).attr('id')}) );
}
});
var urls = [];
for (var i = 0; i < ret.length; i++){
urls[i] = ret[i].videoId;
}
Session.set("current_list",ret);
Session.set("current_urls",urls);
}
And finally this is where the toggling happens to unhide the YouTube player and start the video.
Template.header.events({
'click #generate_button': function (evt, template){
//bad code below:
Template.list.updateList();
if (Template.list.my_playlist().fetch().length == 0){
alert('Your tape is empty!');
}
else{
generatePlaylist( Session.get("current_urls") );
$("#playlist").css('display','none');
$("#player").fadeIn(1000);
$(".absolute_center").hide();
/*Things to hide*/
$(".absolute_center2").fadeIn();
$("#query").hide();
$("#share").fadeOut(1000);
$("#playlist_container").fadeOut(1000);
$('body').animate({backgroundColor: 'rgb(53,53,53)'}, 'slow');
$('#title').animate({color: '#fff'}, 'slow');
}
},
'click #close_player': function (evt, template){
player.stopVideo();
$(".absolute_center2").hide();
$("#player").hide();
$("#playlist").css('display','block');
/*Things that must reappear*/
$("#query").show();
$("#share").fadeIn(1000);
$(".absolute_center").fadeIn(1000);
$("#playlist_container").fadeIn(1000);
$('body').animate({backgroundColor: '#fff'}, 'slow');
$('#title').animate({color: '#000'}, 'slow');
}
});
Here is the GitHub page if you would like to look at the source and dig deeper
The problem seems to be that although you're waiting for the YouTube API to load before adding the iFrame, you're not waiting for the DOM to finish rendering, so the element you're trying to replace with the iFrame (ytplayer) doesn't actually exist when new YT.Player is called in the script tag. Any time you try to play, stop, change video or anything else on the player you've initialised will result in this error under these circumstances, even if the relevant element is subsequently added to the DOM. You can manually run the code in the script tag in the console, and you should find that everything then works as the element will by that point exist at player reinitialisation.
I would advise moving this whole code segment into a Template.player.rendered function in the js file. You'll need to check whether the player has already been created (and only run the code if it hasn't), but putting it all here rather than a script tag will guarantee that the element exists as well as that the API has been loaded. Anyway, I'm sure you can work it out.
UPDATE:
Since you're doing most of the work in the script tags through callbacks, it's actually much easier to add this to the js file:
Template.player.created = function() {
var tag = document.createElement('script');
tag.src = "https://apis.google.com/js/client.js?onload=onClientLoad";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
};
and take this line out of the html:
<script src="https://apis.google.com/js/client.js?onload=onClientLoad"></script>
Apologies if what I said above isn't clear. In your version, the YouTube API is trying to replace the div ytplayer with an iFrame, but since the Javascript is in a script tag, it's being run before Meteor has finished rendering all your templates, which means that ytplayer doesn't yet exist in the DOM and so it doesn't get replaced. So although player exists, you can't do anything with it, since there's no iFrame in the DOM. By making the change above, you will only load the YouTube API once the template containing ytplayer has been loaded for the first time, guaranteeing a successful replacement.
Note that if you have any significant latency, the YouTube API might not get round to firing the callbacks until the DOM has fully rendered, in which case the whole thing would work, which I assume it did sporadically.

Youtube Flash / HTML5 Player

I am working on a small project that envolves geting the time to wich a user skips to when playing a video.
I am using the javascript part of the API to play the video, pause and so on, but I cannot seem to understand how to get the time that the user skips to. I know I can seekTo on my own... But there seems to be no corresponding event for when the user does this
I am trying to find the event that fires when the user skips to a certain time...
From YT API I see they do not have such an event (in javascript... I wouldn't like to create my own custom player based on HTML5 and/or AS3).
It seems that there isn't a real way to do it, but probably you can "intercept" it and understand it, it won't take long:
In this page you can see all the featurea available for the api. You can do it in 2 way:
checking and confronting the time of the video with a counter continuously (probably a bad idea).
Intercepting the player state:
There are 5 state for the player, when the player cursor is moved:
-if the player was playing, the state go from 1 to 2 and then from 2 to 1.
-if the players was paused, nothing happens to his state.
Considering that you can intercept the player state you can easily get the current time when the player is paused and then, confront it with the current time when the user restart play. In each of the 2 case, you should be able to know if the user moved or not the cursor. I don't know well this api so I don't know if some other state of the player can mean something important for your question. I think this way, if isn't complete, it's a good start from where begin developing your idea.
UPDATE
HERE IS THE CODE EXAMPLE TESTED + JSFIDDLE
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
var doing = -1;
var timenow=0;
var timeold=0;
var flag=0;
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if(flag==1){
flag=0;
timenow=timeold;
}else{
if((doing==-1)&&(event['data']==1)){//start play
doing=1;
}
if((doing==1)&&(event['data']==2)){//from play to pause
timeold=player.getCurrentTime();
timeold=timeold;
doing=2;
console.log('go pause at:'+timeold);
}
if((doing==2)&&(event['data']==1)){//from pause to play
timenow=player.getCurrentTime();
console.log('go play at:'+timenow);
if(timenow>timeold){
alert('are you kidding me?! You switched from '+timeold+' to '+timenow+'!');
player.seekTo(timeold);
flag=1;
}else{
timeold=timenow;
}
}
if(event['data']==5 || event['data']<1){//stopped or ended
doing=-1;
timeold=0;
timenew=0;
}
}
}
function stopVideo() {
player.stopVideo();
}
</script>
</body>
</html>

Embeded Youtubu doesn't load on mobile safari, when hidden

Google changed something last days, so I have strange problem with Youtube embeded as iFrame in mobile safari. When iFrame is hidden (some parent div has set display:none) during page load and later is changed to be visible, Youtube player do not play.
Any solution? I've tried to reload iframe, when it turns to visible state, it works. But it is not comfortable solution…
I am assuming you are using the third party youtube Iframe API to load the youtube player into a div. The code from youtube actually has a race condition in it where if the div is not loaded/ parsed by the browser prior to onYouTubeIframeAPIReady being called, the video load will fail. In firefox it will just hang and for a second and Chrome is smart enough to deal with the problem.
The solution is to make sure that this code. Is run after the container holding the video is parsed.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
And this code...
function onYouTubeIframeAPIReady() {
Has global scope and is called after the script is created.
The way I typically solve this to to create a function 'class' with the tag creator and event listener and create the object in my document.ready function. Then right below the document.ready function I put.
function onYouTubeIframeAPIReady() {
myVideo.apiReady();
}
Where 'myVideo' is the class I have create and apiReady(); contains.
this.apiReady = function () {
player = new YT.Player('player', {
height: '548',
width: '900',
videoId: 'VIDEO234324',
events: {
'onReady': onPlayerReady
}
});
}
I hope this helps.

Youtube API not loading in firefox

I have this test slider http://jsfiddle.net/dUyLY/2/
In Chrome it works nice, but in firefox and safari it bugs out when the animation is done.
In the scripts file I check after each animation if the current visible element is the youtube player, if so play the video. And when leaving the slide pause it.
I get this error in console player.pauseVideo is not a function
Anyone has a solution to this?
You're deferring the definition of onYouTubePlayerAPIReady. For this reason, it's likely that the YouTube API finishes before onYouTubePlayerAPIReady is defined. In that case, your code will fail.
To solve the problem, check whether the API is ready at run-time.
window.onYouTubePlayerAPIReady = function () {
player = new YT.Player('player', {
height: '315',
width: '560',
videoId: 'bpOR_HuHRNs',
});
};
if (window.YT) {
// Apparently, the API was ready before this script was executed.
// Manually invoke the function
onYouTubePlayerAPIReady();
}
Note. For simple one-way functions, such as player.playVideo() and player.pauseVideo(), I recommend to use this simple function, which is not as bloated as the documented YouTube API. See this answer.
Here's a the updated part of your page, using the callPlayer function from my other answer instead of the YouTube API: http://jsfiddle.net/ryyCZ/
<div id="player">
<iframe width="560" height="315" frameborder="0" src="http://www.youtube.com/embed/bpOR_HuHRNs?enablejsapi=1"></iframe>
</div>
if ($(".slides_control > div:visible #player").length == 1) {
callPlayer("player","playVideo");
} else {
callPlayer("player", "pauseVideo");
}

Categories