using YouTube iFrame API within jQuery event - javascript

I have a page in which clicking a link opens a lightbox and embeds a YouTube video in an <iframe>. The lightbox and <iframe> markup are generate on the fly by Lity.
Following the example right out of the documentation, where the <iframe> is hard-coded into the page, it works as expected.
<iframe id="existing-iframe-example"
width="640" height="360"
src="https://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1"
frameborder="0"
style="border: solid 4px #37474F">
</iframe>
<script type="text/javascript">
var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
console.log('api ready');
player = new YT.Player('existing-iframe-example', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
document.getElementById('existing-iframe-example').style.borderColor = '#FF6D00';
}
function changeBorderColor(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById('existing-iframe-example').style.borderColor = color;
}
}
function onPlayerStateChange(event) {
changeBorderColor(event.data);
}
</script>
See this Codepen
I'm trying to modify it to work with a dynamically generated <iframe> (which is how Lity handles YouTube videos). I can see that the YouTube API script is being added to the page, but the function onYouTubeIframeAPIReady does not ever seem to be called, which according to documentation is supposed to fire as soon as API script loads.
HTML (to open lightbox)
<a href="https://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1" class="lity" data-lity>open</a>
JavaScript (fires after lightbox object is available in DOM)
$(document).on("lity:ready", function (event, instance) {
console.log("Lightbox ready");
$(".lity-youtube iframe").attr("id", "x");
if ($("iframe#x").length) {
console.log("ID added to <iframe>");
} else {
console.log("ID NOT added to <iframe>");
}
var tag = document.createElement("script");
tag.id = "iframe-demo";
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
if ($("#iframe-demo").length) {
console.log("api script added");
} else {
console.log("api script NOT added");
}
var player;
function onYouTubeIframeAPIReady() {
console.log("api ready");
player = new YT.Player("x", {
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
}
function onPlayerReady(event) {
console.log("player ready");
document.getElementById("x").style.borderColor = "#FF6D00";
}
function changeBorderColor(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById("x").style.borderColor = color;
}
}
function onPlayerStateChange(event) {
console.log('state change');
changeBorderColor(event.data);
}
});
See this Codepen.
I tried pulling everything out of the lity:ready event handler with the same results....
Can anyone see what's going wrong?
EDIT/UPDATE
I tried running onYouTubeIframeAPIReady as a deferred object
var YTdeferred = $.Deferred();
window.onYouTubeIframeAPIReady = function() {
YTdeferred.resolve(window.YT);
};
and using it in the callback
var player;
YTdeferred.done(function (YT) {
console.log("api ready");
player = new YT.Player("x", {
events: {
//onReady: onPlayerReady,
//onStateChange: onPlayerStateChange
onReady: function () {
console.log("player ready from ananonymous");
},
onStateChange: onPlayerStateChange
}
});
console.log(player);
});
Now the YT.Player object is created (presumably on the iframe created by lity), but neither of the events (onReady and onStateChange) seem to be triggering. Also, it doesn't have access to any of the methods or properties that it should:
player.playVideo()
produces an error: player.playVideo is not a function`
Still stumped.
Codepen
ANOTHER EDIT/UPDATE
Now I'm very confused because i created a version that replicates what lity does (I think) and the events (onReady and onStateChange) do in fact fire as expected.
Codepen
Solution to a STUPID mistake
I can' believe I beat my head into the wall over this for so long... G'AAAAAAH!!
It's definitely a lity thing
When lity receives an argument (YouTube URL) formatted like
https://www.youtube.com/embed/XXXXXXXXX
or
http://youtu.be/XXXXXXXXX
it converts it to
https://www.youtube.com/embed/XXXXXXXXX?autoplay=1
If you try to pass query string arguments to to a similarly formatted URL (such as: https://www.youtube.com/embed/XXXXXXXXX?enablejsapi=1 (which is required for the API calls to work), it URLencodes the ?enablejsapi=1 and adds it to the end of the converted URL, so you get:
https://www.youtube.com/embed/XXXXXXXX?autoplay=1&%3Fenablejsapi=1
As a result the enablejsapi=1 is never read in and therefore the YT.Player is never attached to the <iframe> DOM element.
** SOLUTION **
Pass the URL to lity in a format that passes the video ID as a query string argument (and append enablejsapi=1):
https://www.youtube.com/watch?v=XXXXXXXX&enablejsapi=1
lity will now keep your argument(s) intact and convert to
https://www.youtube.com/embed/XXXXXXXX?autoplay=1&enablejsapi=1
Now the YT.Player call can read enablejsapi=1 and properly attach to the <iframe> element, and all is good in the universe.
<a href="https://www.youtube.com/watch?v=TJU-tBquzcM&enablejsapi=1" class="lity" data-lity>open lightbox with video</a>
Final Working CodePen
.

It looks like the "lity:ready" event is not fired in my case.
I changed
$(document).on("lity:ready", function (event, instance) {
to
$(document).ready(function (event, instance) {
using the jQuery ready event.
And most important, the function onYouTubeIframeAPIReady must be global (on window scope) to be detected by the youtube iFrame API (actually it was created inside an anonymous function() ). So I changed
function onYouTubeIframeAPIReady() {
to
window.onYouTubeIframeAPIReady=function() {
Codepen
EDIT:
To work on a global context inside an anonymous function. Other local functions created and referenced (onPlayerReady and onPlayerStateChange) must be declared in the global context using the window object.
window.onPlayerReady=function(event) {
console.log("player ready");
document.getElementById("x").style.borderColor = "#FF6D00";
}
window.changeBorderColor=function(playerStatus) {
var color;
if (playerStatus == -1) {
color = "#37474F"; // unstarted = gray
} else if (playerStatus == 0) {
color = "#FFFF00"; // ended = yellow
} else if (playerStatus == 1) {
color = "#33691E"; // playing = green
} else if (playerStatus == 2) {
color = "#DD2C00"; // paused = red
} else if (playerStatus == 3) {
color = "#AA00FF"; // buffering = purple
} else if (playerStatus == 5) {
color = "#FF6DOO"; // video cued = orange
}
if (color) {
document.getElementById("x").style.borderColor = color;
}
}
window.onPlayerStateChange=function(event) {
console.log('state change');
changeBorderColor(event.data);
}

Related

Moving through Vimeo videos with Javascript

I would like to play through Vimeo videos in sequence with JavaScript with continuous playback and looping through items. The following code moves to the second video at the end of the first but the same functions are not called at the end of the second video. Only the end of the first video shows Called! in the Javascript console, not the end of the second.
The Vimeo Player SDK has player.getVideoId() but not player.setVideoId() so I used a hack to load the next video: setting the source of the iframe and starting the Vimeo player anew. I wonder if this is the problem and the listener for the end of the video is attached to the discarded player.
Another issue with this code is that it exits fullscreen to load the next video.
I attempted this CodePen that sets the Vimeo Player on any element instead of an iframe and was unable to change the played video.
What is a cleaner or effective way of changing videos with the Vimeo Player SDK?
The HTML is:
<div class="vimeo">
<div class="resp-iframe-container container-centered">
<iframe class="resp-iframe" scrolling="no" frameborder="no" src="https://player.vimeo.com/video/238301525" allowfullscreen="" data-ready="true"></iframe></div></div>
And the javascript is:
var l = ["238301525", "496371201", "238301525", "496371201", "..."];
window.current = 0;
var increment = function() {
window.current += 1;
if (window.current == l.length) {
window.current = 0;
}
};
var decrement = function() {
window.current -= 1;
if (window.current < 0) {
window.current = l.length - 1;
}
};
prevA.addEventListener("click", function() {
decrement();
loadPlayer();
});
nextA.addEventListener("click", function() {
increment();
console.log("here!");
loadPlayer();
console.log("there!");
});
var loadPlayer = function() {
console.log("Called!");
var iframe = document.querySelector('iframe');
iframe.src = "https://player.vimeo.com/video/" + l[window.current];
var player = new Vimeo.Player(iframe);
player.autoplay = true;
var treatEvent = function(data, eventLabel) {
// Custom code...
if ("end" == eventLabel) {
console.log("incrementing automatically");
increment();
loadPlayer();
}
};
var onPlay = function(data) {
treatEvent(data, "play");
}
var onPause = function(data) {
treatEvent(data, "pause");
}
var onSeeked = function(data) {
treatEvent(data, "seeked");
}
var onEnd = function(data) {
treatEvent(data, "end");
}
player.on('play', onPlay);
player.on('pause', onPause);
player.on('seeked', onSeeked);
player.on('ended', onEnd);
setTimeout(function() {
//console.log("Trying to play...");
player.play().then(function() {
// the video was played
console.log("success: video played");
}).catch(function(error) {
console.log("Error! " + error.name);
});
}, 1000);
}
loadPlayer();
It seems that the Vimeo Player keeps track of whether it was initialized, adding a tag data-vimeo-initialized="true" in the HTML element.
One solution is to use the Vimeo Player SDK function .loadVideo():
var song_iframe = document.querySelector("iframe#song_video");
var song_player = new Vimeo.Player(song_iframe);
var on_song_end = function(data) {
// Logic to get the new id.
song_player.loadVideo(newId).then(function() {
song_player.play();
});
};
song_player.on("ended", on_song_end);

Controlling when Vimeo thumbnail turns off and video begins

I'm looking into the vimeo embed api. I want the video to autoplay on load which I have working but currently what happens is like this:
player loads then autoplays [event "ready"]
thumbnail removed and shows black for about a second
video begins playing [event "playProgess"]
The problem is the second step. I'm trying to eliminate that black screen between when the thumbnail hides (when play is initiated) to when the video actually appears and starts playing.
The way I figure it can be solved is to keep the thumbnail around and trigger the thumbnail hide on the first "playProgress" but I can't seem to find anyway to control when the thumbnail turns on or off.
Is this possible to control? I'm aware that I can pull the thumbnail and overlay it over the iframe but I'm hoping for a cleaner fix (keep it all contained to the iframe).
Here's a pen with api running:
http://codepen.io/mattcoady/pen/KMzZMZ
$(function() {
var player = $('iframe');
var playerOrigin = '*';
var status = $('.status');
// Listen for messages from the player
if (window.addEventListener) {
window.addEventListener('message', onMessageReceived, false);
}
else {
window.attachEvent('onmessage', onMessageReceived, false);
}
// Handle messages received from the player
function onMessageReceived(event) {
// Handle messages from the vimeo player only
if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {
return false;
}
if (playerOrigin === '*') {
playerOrigin = event.origin;
}
var data = JSON.parse(event.data);
console.log(data.event);
switch (data.event) {
case 'ready':
onReady();
break;
case 'playProgress':
onPlayProgress(data.data);
break;
case 'pause':
onPause();
break;
case 'finish':
onFinish();
break;
case 'play':
onPlay();
break;
}
}
// Call the API when a button is pressed
$('button').on('click', function() {
post($(this).text().toLowerCase());
});
// Helper function for sending a message to the player
function post(action, value) {
var data = {
method: action
};
if (value) {
data.value = value;
}
var message = JSON.stringify(data);
player[0].contentWindow.postMessage(message, playerOrigin);
}
function onReady() {
status.text('ready');
post('play');
post('addEventListener', 'pause');
post('addEventListener', 'finish');
post('addEventListener', 'playProgress');
}
function onPause() {
status.text('paused');
}
function onFinish() {
status.text('finished');
}
function onPlay(){
alert('play')
}
function onPlayProgress(data) {
status.text(data.seconds + 's played');
}
});
What I ended up going with my hacky fix. It's pulls the thumbnail and lays it over the video. When my script detects the 'playProgress' event that means the video is actually playing. I use jQuery to fade away the thumbnail cover.
http://codepen.io/mattcoady/pen/YWqaWJ
$(function() {
var player = $('iframe');
var playerOrigin = '*';
var videoId = 76979871;
player.attr('src', 'https://player.vimeo.com/video/' + videoId + '?api=1&player_id=player1&background=1&autoplay=1&loop=1');
// Listen for messages from the player
if (window.addEventListener) {
window.addEventListener('message', onMessageReceived, false);
} else {
window.attachEvent('onmessage', onMessageReceived, false);
}
$.getJSON('http://vimeo.com/api/v2/video/' + videoId + '.json', {jsonp: 'callback',dataType: 'jsonp'}, function(data) {
var thumbnail = document.createElement('img');
thumbnail.src = data[0].thumbnail_large;
thumbnail.style.width = document.querySelector('#player1').offsetWidth + 'px';
thumbnail.style.height = document.querySelector('#player1').offsetHeight + 'px';
document.querySelector('#vimeo-thumb-container').appendChild(thumbnail);
})
// Handle messages received from the player
function onMessageReceived(event) {
// Handle messages from the vimeo player only
if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {return false;}
if (playerOrigin === '*') { playerOrigin = event.origin; }
var data = JSON.parse(event.data);
switch (data.event) {
case 'ready':
onReady();
break;
case 'playProgress':
onPlayProgress(data.data);
break;
}
}
// Helper function for sending a message to the player
function post(action, value) {
var data = { method: action };
if (value) {data.value = value;}
var message = JSON.stringify(data);
player[0].contentWindow.postMessage(message, playerOrigin);
}
function onReady() {
post('play');
post('addEventListener', 'playProgress');
}
function onPlayProgress(data) {
$('#vimeo-thumb-container').fadeOut(250);
}
});

How can I detect whether my audio stream is: playing, paused or finished?

I'm working on an online radio website and am trying to detect what state the audio player is currently in for the user.
The current setup I've got is often wrong in knowing this and if I'm on iOS and I pause from the lock screen player instead, it will still think it's playing because I never clicked the html pause button.
Finally how is it possible to detect when my stream has fully ended. I've tried: onstalled="", onwaiting="", onerror="", onended="". But none of them work 100% of the time. The closest one to work would be: onstalled="", but even that only had a 60% success rate or so (occasionally when I'm loading the site it would tell me it has ended).
HTML:
<audio autoplay="" id="player" title="" oncanplay="radioLoaded()">
<source src="...">
</audio>
Javascript:
function radioLoaded() {
if (player.paused) {
document.getElementById('radioplaypause').innerHTML = varRadioResume;
} else if (player.play) {
document.getElementById('radioplaypause').innerHTML = varRadioPause;
} else {
document.getElementById('radioplaypause').innerHTML = varRadioLoading;
}
window.player = document.getElementById('player');
document.getElementById('radioplaypause').onclick = function () {
if (player.paused) {
player.play();
this.innerHTML = varRadioPause;
} else {
player.pause();
this.innerHTML = varRadioResume;
}
}
};
function radioEnded() {
document.getElementById('radiolivetext').innerHTML = 'OFFLINE';
document.getElementById('radioplayer').style.display = 'none';
document.getElementById('radioinformation').style.display = 'none';
};
Try this, with some corrections/notes to the code in general:
function radioLoaded() {
// move this line to the top
window.player = document.getElementById('player'); // !!! <-- name too generic, possibility of global collisions
var playPauseButton = document.getElementById('radioplaypause');
if (player.paused) {
playPauseButton.innerHTML = varRadioResume;
} else if (player.play) {
playPauseButton.innerHTML = varRadioPause;
} else {
playPauseButton.innerHTML = varRadioLoading;
}
playPauseButton.onclick = function () {
if (player.paused) {
player.play();
this.innerHTML = varRadioPause;
} else {
player.pause();
this.innerHTML = varRadioResume;
}
};
player.onended = function () {
console.log('ended');
};
// other helpful events
player.onpause = function () {
console.log('paused...');
}
player.onplay = function () {
console.log('playing...');
}
player.onprogress = function (e) {
console.log(e.loaded + ' of ' + e.total + ' loaded');
}
};

Start youtube video on hover/mouseover

I'm trying to get youtube videos to start on hover. It will pause (not stop) when the user hovers over another video...
I am stuck on the hover command. Can someone help me work it out please?
The page has 16 videos, this is the working code from the jsfiddle that contains 3 videos as an example.
http://jsfiddle.net/sebwhite/gpJN4/
VIDEO:
<iframe id="player" width="385" height="230" src="http://www.youtube.com/embed/erDxb4IkgjM?rel=0&wmode=Opaque&enablejsapi=1;showinfo=0;controls=0" frameborder="0" allowfullscreen></iframe>
JAVASCRIPT:
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
events: {
'onStateChange': onPlayerStateChange
}
});
onYouTubeIframeAPIReady();
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
player1.pauseVideo();
player2.pauseVideo();
}
UPDATED FIDDLE
Try this:
var $$ = function(tagname) { return document.getElementsByTagName(tagname); }
function onYouTubeIframeAPIReady() {
var videos = $$('iframe'), // the iframes elements
players = [], // an array where we stock each videos youtube instances class
playingID = null; // stock the current playing video
for (var i = 0; i < videos.length; i++) // for each iframes
{
var currentIframeID = videos[i].id; // we get the iframe ID
players[currentIframeID] = new YT.Player(currentIframeID); // we stock in the array the instance
// note, the key of each array element will be the iframe ID
videos[i].onmouseover = function(e) { // assigning a callback for this event
if (playingID !== currentHoveredElement.id) {
players[playingID].stopVideo();
}
var currentHoveredElement = e.target;
if (playingID) // if a video is currently played
{
players[playingID].pauseVideo();
}
players[currentHoveredElement.id].playVideo();
playingID = currentHoveredElement.id;
};
}
}
onYouTubeIframeAPIReady();
Fiddle:
http://jsfiddle.net/gpJN4/3/

Using an array in Jquery

I'm not so experienced with Javascript and I have been struggling with this one pretty much all day.
I'm using Jquery to create and array of the ids of embedded youtube videos:
$(function() {
$('li').on("click",function(){
alert($(this).attr('data-pile'));
var pilename = $(this).attr('data-pile');
var videoIDs = [];
$("li[data-pile='"+pilename+"']").each(function(index){
videoIDs.push($(this).attr('id'));
});
$.each(videoIDs,function(){
});
});
});
And I need to use the array in this JS:
<script src="//www.youtube.com/iframe_api"></script>
<script>
/**
* Put your video IDs in this array
*/
var videoIDs = [
//my array of Ids here
];
var player, currentVideoId = 0;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '350',
width: '425',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
event.target.loadVideoById(videoIDs[currentVideoId]);
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
currentVideoId++;
if (currentVideoId < videoIDs.length) {
player.loadVideoById(videoIDs[currentVideoId]);
}
}
}
</script>
In each div where embedded videos are I'm applying an id with same id as video.
How should I make the two scripts work?
I'll really appreciate if someone can point me in the right direction.
You're declaring your videoIDs array twice, once in your click events and again in your second
script.
The one inside your click events is local to that function whereas the other one is global. Javascript has function scope, so that click event one gets discarded once that function ends.
If you remove the one inside your click events, I believe it should work. You should also remove the $.each... as I don't think it's going to help (you're trying to make a playlist, right?).
It should be noted that it's considered bad practice to pollute the global namespace by using global variables. If this is all the code you have on your page, it's probably not an issue.
Try doing it this way: add a custom listener after "click" event. Didn't check your array forming section, tested with a custom array, hope you won't have issues with it.
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '350',
width: '425',
});
}
$(function(){
$(document.body).on("click",".play", function(){
player.stopVideo();
var pilename = $(this).attr('data-pile');
var videoIDs = [];
$("li[data-pile='"+pilename+"']").each(function(index){
videoIDs.push($(this).attr('id'));
});
if(videoIDs.length > 0){
currentVideoId = 0;
player.loadVideoById(videoIDs[0]);
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED) {
currentVideoId++;
if (currentVideoId < videoIDs.length) {
player.loadVideoById(videoIDs[currentVideoId]);
}
}
}
player.addEventListener("onStateChange", onPlayerStateChange)
player.playVideo();
}
});
});
</script>

Categories

Resources