Add/loop multiple videos, player names and events via YouTube API dynamically? - javascript

I'm trying to make an array of players to pass to new YT.Player. I keep getting 'player1' undefined, and the iFrame never gets added to the stated 'playerInfo.id'.
My code, without player2 or player3 included for simplicity:
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 playerInfoList = [
{playerName: 'player1', id: 'container1', videoId: 'WPvGqX-TXP0', eventId: 'playerOneReady'},
{playerName: 'player2', id: 'container2', videoId: 'Yj0G5UdBJZw', eventId: 'playerTwoReady'},
{playerName: 'player3', id: 'container3', videoId: '9gTw2EDkaDQ', eventId: 'playerThreeReady'}];
var players = new Array();
function onYouTubeIframeAPIReady() {
if (typeof playerInfoList === 'undefined') return;
for (var i = 0; i < playerInfoList.length; i++) {
var generatePlayers = createPlayer(playerInfoList[i]);
players[i] = generatePlayers;
}
}
function createPlayer(playerInfo) {
playerInfo.playerName = new YT.Player(playerInfo.id, {
height: '390',
width: '640',
videoId: playerInfo.videoId,
events: {
'onReady': playerInfo.eventId
}
});
}
// When the Players are ready.
var duration1;
function playerOneReady() {
duration1 = player1.getDuration();
}
function play(playerNum) {
playerNum.seekTo(0);
playerNum.playVideo();
}
$('#player1-trigger').click(function(){
play(player1);
}
I'm not sure why it keeps coming back as undefined. I have a simpler structure that works fine, but it involves adding each player manually, and I'm trying to make it more dynamic/efficient. Although this is more efficient than dynamic.

from what i'm seeing you made a mistake in the function createPlayer. while you're trying to make a player with the name player1 you're actually saving the new YT.player on the location of playerInfo.playername. it would be better to save in te players list directly like this:
function onYouTubeIframeAPIReady() {
if (typeof playerInfoList === 'undefined') return;
for (var i = 0; i < playerInfoList.length; i++) {
var generatePlayers = createPlayer(playerInfoList[i],i);
}
}
function createPlayer(playerInfo, locationInList) {
players[locationInList] = new YT.Player(playerInfo.id, {
height: '390',
width: '640',
videoId: playerInfo.videoId,
events: {
'onReady': playerInfo.eventId
}
});
}
and then when calling for the players use:
$('#player1-trigger').click(function(){
play(players[1]);
}
For the future I would suggest when making code more efficient/dynamic/readable to make multiple smaller changes and to test after every change if the code still works. That way it's easier to find what has broken.

Related

Index of object and play next with youtube api

I am using the youtube api to search for youtube videos. The videos will then be displayed on #searchBar with the video id ex. NJNlqeMM8Ns as data-video. I get the video id by pressing on a img:
<img data-video = "{{videoid}}" src = "bilder/play.png" alt = "play" class = "knapp" width = "40" height = "40">
Which in my poorly understanding of javascript becomes (this).
When I search for videos I will get more than one result which means that I will get more than one img tag.
In this case I want to play the next song when the first one is finished. I tried to get the index when I pressed on my img tag:
$(".knapp").click(function(){
var index = $(".knapp").index(this);
alert(index);
});
However, when I alerted the index after the video was finshed I always got the value 0 back.
So I thought I could do something like this:
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED){
playNext();
}
}
$('#searchBar').on('click', '[data-video]', function(){
player.current_video = $(this).attr('data-video');
playVideo();
});
function playVideo(){
var video_id = player.current_video;
player.loadVideoById(video_id, 0, "large");
}
function playNext(){
var player.current_videon = **$(this + 1).attr('data-video');**
var next_id = player.current_videon;
player.loadVideoById(next_id, 0, "large");
}
But I'm not sure how to make it work, as you can see in the bold section, can I solve my problem like this or do I need another approach?
Edit:
With some research I found out that I need to set the value of the current video being played and also efter the video was done playing I add this number by 1.
However even if it did make the next video play, I was unable to chose which song I wanted anymore...
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.ENDED){
player.current_video++;
playVideo();
}
}
var player = document.querySelector('iframe');
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: '40mSZPyqpag',
playerVars: {rel: 0},
events: {
'onStateChange': onPlayerStateChange
}
});
player.current_video = 0;
}
$('#searchBar').on('click', '[data-video]', function(){
player.current_video = $(this).index();
alert(player.current_video);
playVideo();
});
function playVideo(){
var video_id = $('[data-video]').eq(player.current_video).attr('data-video');
player.loadVideoById(video_id, 0, "large");
}
Here is a working PEN (click to RUN)
The solution is based on a global currentSongIndex index, without facilitating next()
var currentSongIndex = null;
$(".knapp").click(function () {
var index = $(this).attr('data-video');
playVideo(index);
});
function playVideo(index) {
console.log("Playing song INDEX: " + index);
currentSongIndex = index;
playNext();
}
function playNext() {
currentSongIndex++;
let nextSongIndex = $('img[data-video="' + currentSongIndex + '"]').attr('data-video');
console.log("Next song INDEX: " + nextSongIndex);
if(typeof nextSongIndex != "undefined") {
playVideo(nextSongIndex);
} else {
console.log('end of list... you can play from index 1 or whatever....');
}
}
I got a suggestion to get player.current_video by the closest li index, so I made an update to my data-video img tag.
<li class = liItem>
<img data-video = "{{videoid}}" src = "bilder/play.png" alt = "play" class = "knapp" width = "40" height = "40">
</li>
Then I changed the index on my click function in my edited example:
$('#searchBar').on('click', '[data-video]', function(){
player.current_video = $(this).closest('li').index();
playVideo();
});
With this new fuction I was able to chose and play the next song!
Shoutout to #cale_b for providing me with the .closest('li') suggestion.

How to use an iframe as input for YT.Player

The docs say we should use a div container where the <iframe> element will be appended.
That works:
new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
But I should have an element <div id="player"></div> on the page.
Is it possible that if I already have the iframe element with the embedded video to use that iframe in the YT.Player constructor?
I would like to have something like this:
new YT.Player('iframeId').playVideo();
But the playVideo isn't there because the player is not loading (I guess that's happening because we are providing the iframe id).
Is there a way to connect an existing iframe with the YT.Player instance?
I wrote a quick hack for it: get the video id from the <iframe> url, replace the iframe with a div and initialize the player:
YTPlayerIframe = (function () {
function youtube_parser(url){
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
return (match&&match[7].length==11)? match[7] : false;
}
var __oldPlayer = YT.Player;
var player = function (id, options) {
var el = document.getElementById(id);
options.videoId = youtube_parser(el.getAttribute("src"))
el.outerHTML = "<div id='" + id + "'>test</div>";
return new __oldPlayer(id, options);
};
return player;
})();
Then you can use it this way:
var myPlayer = YTPlayerIframe("iframe-id", {
// The videoId option will be taken from the iframe src.
events: {
onReady: function () {...}
}
});

Lose context of this when youtube constructor method is called

I have created a youtube player using the youtube iframe api, I am listening for the ENDED event but I've realised that I lose the reference to this which becomes the window but I'm really unsure how to resolve this. I've tried binding this to the contsructor etc but with no joy whatesoever so could really do with you guys help.
JS
startPlayer: function (videoId) {
var instance = this;
console.log('startPlayer', instance);
if( instance.flags.isPlaying ) {
instance.selectors.playerCtn.empty();
instance.flags.isPlaying = false;
}
instance.selectors.playerCtn.append('<div id="player"></div>');
instance.player = new YT.Player('player', {
height: '390',
width: '640',
videoId: videoId,
events: {
'onReady': this.onPlayerReady,
'onStateChange': this.onPlayerStateChange
}
});
instance.flags.isPlaying = true;
},
onPlayerStateChange: function (event) {
console.log('onPlayerStateChange');
var instance = this;
console.log(instance); //undefined??
if (event.data == YT.PlayerState.PLAYING) {
console.log('PLAYING...');
}
if (event.data == YT.PlayerState.PAUSED) {
console.log('PAUSED...');
}
if (event.data == YT.PlayerState.ENDED) {
console.log('what is this', instance);
// if instance.counter === instance.playlist
if (instance.counter === instance.playlist) {
console.log('you\'ve come to the end of your playlist');
// Display message or go back to first?
return;
}
// Increase the counter
instance.counter++
// Set the new current element
instance.current = instance.selectors.listItems[instance.counter];
console.log(instance.counter);
console.log(instance.current);
// Get the new current element data-id
var videoId = instance.current.attr('data-id');
// Start the player
startPlayer(videoId);
}
if (event.data == YT.PlayerState.BUFFERING) {
console.log('BUFFERING...');
}
}
Test page http://go.shr.lc/1lh2dmu
events: {
'onReady': this.onPlayerReady.bind(this),
'onStateChange': this.onPlayerStateChange.bind(this)
}
Besides, why var instance = this;? this is quite shorter to type and you aren't using instance in any closure.

Binding YouTube Video to Div Element from Seperate JS file

I have this problem embedding YouTube video in a PhoneJS single-page mobile application. In PhoneJS, the JS scripts are defined in a different file. So I defined the HTML div like this:
<div id="player"></div>
Now in the JS file, I did this:
function getVideo() {
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var playerDiv = document.getElementById('player');
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player(playerDiv, {
height: '250',
width: '444',
videoId: sIFYPQjYhv8
});
}
}
When I run and view the debugger, the call is made to Youtube and response is received, but it is not displayed on the view.
Ok since I am using KnockoutJS binding, I modified the div in the html view like this:
<iframe id="player" type="text/html" width="444" height="250" frameborder="0" data-bind="attr: { src: src }"></iframe>
And then pass in the src video id thus:
src: ko.observable('http://www.youtube.com/embed/' + sIFYPQjYhv8 + '?autoplay=1')
In this case however, in the debugger, the call is not even made to Youtube. Nothing just happens. Actually I prefer to use the API call instead of the second approach.
Any suggestions on how to make the first approach work? I mean using the API call?
EDIT
Just want to mention that when I add the code below in the view, the video is streamed alright.
<h1>Video</h1>
<div id="player"></div>
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var playerDiv = document.getElementById('player');
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player(playerDiv, {
height: '250',
width: '444',
videoId: 'sIFYPQjYhv8'
});
}
</script>
I think the easiest way to do this is to use a custom binding handler with a flag set from the onYouTubeIFrameAPIReady callback
Sample jsFiddle
ko.bindingHandlers['player'] = {
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
// Check if global script and function is declared.
if ( !document.getElementById('playerScript') ) {
// Create script
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var playerDiv = document.getElementById('player');
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Create global function that their API calls back
window.playerReady = ko.observable(false);
window.onYouTubeIframeAPIReady = function() {
window.playerReady(true);
};
}
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = valueAccessor(),
id = value.id(),
height = ko.unwrap(value.height) || '250',
width = ko.unwrap(value.width) || '444'
;
if ( !value.id()) {
return;
}
if ( !window.playerReady() ) {
// YT hasn't invoked global callback. Subscribe to update
var subscription;
subscription = window.playerReady.subscribe( function(newValue) {
if ( newValue ) {
subscription.dispose();
// Just get this binding to fire again
value.id.notifySubscribers(value.id());
}
});
} else {
var player = new YT.Player( element, {
height: height,
width: width,
videoId: id
});
}
},
}
Now change your player div to
<div data-bind="player: { id: id, height: height, width: width }"></div>
Finally bind
var vm = {
id: 'sIFYPQjYhv8',
height: '250',
width: '444'
};
ko.applyBindings( vm )
EDIT
To remove the reliance on window, put your script tag that adds the new script element back, tweek as below, modify their callback and use a setTimeout instead of the "playerReady" observable
HTML Script
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
tag.setAttribute('id', 'playerScript');
tag.setAttribute('data-ready', 'false');
...
function onYouTubeIframeAPIReady = function() {
document.getElementById('playerScript').setAttribute('data-ready', 'true');
};
Player Binding
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = valueAccessor(),
id = value.id(),
height = ko.unwrap(value.height) || '250',
width = ko.unwrap(value.width) || '444',
playerScript = document.getElementById('playerScript')
;
if ( !value.id()) {
return;
}
if ( !playerScript || playerScript.getAttribute('data-ready') !== 'true' ) ) {
// YT hasn't invoked global callback.
setTimeout( function() {
value.id.notifySubscribers(value.id());
}, 50);
} else {
var player = new YT.Player( element, {
height: height,
width: width,
videoId: id
});
}
}

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