Youtube - YT Object Has No Method PauseVideo() - javascript

I have embedded iFrame Youtube videos onto my page and I want the video to pause when the user clicks on the 'next' button to cycle through the carousels of videos. Before I cycle to the next video, I create a YT Object for the active video and try to run the pauseVideo() function. The console reports the error: Uncaught TypeError: Object #<S> has no method 'pauseVideo'.
What is puzzling is that I have console.log the player before calling the pauseVideo() function and I do see the pauseVideo() method available in the object. Does anyone have any idea why I am able to see the method in the YT Object, but the console keeps reporting that the method does not exist?
JS-Fiddle: http://jsfiddle.net/7WPe9/
// Load the IFrame Player API code asynchronously on DOM load
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Triggered when user clicks 'next slide'
pauseActiveYoutubeVideos : function() {
var player = new YT.Player( $('#media .item.active iframe.ytplayer')[0] );
console.log( player ); //pauseVideo() is defined in this player object
player.pauseVideo(); //console reports 'Uncaught TypeError: Object #<S> has no method 'pauseVideo''
}

Lots of little things here adding up to your problem. First of all, you have to send in an ID as an argument rather than a jquery object (as pointed out in one of the comments). More importantly, the YT iframe API has trouble when you bind an iFrame to a player object and then remove it ... you can't then rebind to that same iFrame again. So your nested loops need to be replaced. One strategy that generally works is to set up a separate tracking object where you create a YT.Player object for each video right at the beginning, and then just keep track of which one should be the active one (so you know which one to pause). Try something like this (and it should just be in a <script> tag, rather than in a jQuery 'ready' function, so as to avoid problems with the iFrame API's asynchronous load):
// Load the IFrame Player API code asynchronously on DOM load
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Sets up player tracker, and init the carousel
var players={}, activePlayerId;
function onYouTubeIframeAPIReady() {
$('iframe.ytplayer').each(function() {
players[$(this).attr('id')]=new YT.Player($(this).attr('id'));
});
activePlayerId=$('iframe.ytplayer').first().attr('id');
$("#"+activePlayerId).addClass('active');
// Click on carousel next button
$('#next').on('click', function() {
players[activePlayerId].pauseVideo();
$("#"+activePlayerId).removeClass('active');
if ($("#"+activePlayerId).next().is('iframe')) {
activePlayerId=$("#"+activePlayerId).next().attr('id');
}
else {
activePlayerId=$('iframe.ytplayer').first().attr('id');
}
$("#"+activePlayerId).addClass('active');
});
}

When you create player object, make sure that 'onReady' event happened before you call 'pauseVideo' method.
For example, you can stop the video this way:
var player = new window.YT.Player(yourIframeElement, {
events: {
'onReady': function(event) {
event.target.pauseVideo();
}
}
});
Notice: if you try to create the object with the same video element again, 'onReady' event won't happen anymore.

Related

Youtube iframe API with javascript tabs

I'm building an online 'TV' which will use YouTube live-streams for multiple channels.
The channels are contained within tabs. The videos need to be stopped when changing tabs otherwise you can hear the audio in the background.
Here's a link to the JSFiddle: https://jsfiddle.net/matlow/08k4csuh/
I've managed to turn the 'Channel 1' off when changing to another channel with:
var iframe = document.getElementsByClassName("tvscreen")[0].contentWindow;
and
iframe.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
In the tab javascript for loop which also handles the tabcontent[i].style.display = "none";
I think I need to use the for loop to call each instance of the iframe... but I'm quite new to javascript so I'm not quite sure how to achieve this.
It will also help to use iframe.postMessage('{"event":"command","func":"playVideo","args":""}', '*'); so the video plays automatically again when clicking on the relevant tab... but again I'm not quite sure how to implement this.
I've been working on this for a few days so if anyone had any tips or pointers I would really appreciate it!
Thanks for reading! :)
You are not using YouTube's API properly. See https://developers.google.com/youtube/iframe_api_reference
In your fiddle, programmatic play is not possible, because you can't know when the YouTube player is ready, as you are not the one initialising it. Your attempts to play the video might take place too early.
Programmatic pause (you managed to pause the first video) is possible thanks to enablejsapi=1 in the iframe src and the fact that the player is ready at that point.
Here's a fork of your fiddle - https://jsfiddle.net/raven0us/ancr2fgz
I added a couple of comments. Check those out.
// load YouTube iframe API as soon as possible, taken from their docs
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);
// initialised players are kept here so we don't have to destroy and reinit
var ytPlayers = {};
function mountChannel(channel) {
var player;
var iframeContainer = document.querySelectorAll('#' + channel + ' iframe');
// if the channel & iframe we want to "mount" exist, check for playing iframes before doing anything else
if (iframeContainer.length > 0) {
// Object.keys() is ECMA 5+, sorry about this, but no easy to check if an object is empty
// alternatively, you could have an array, but in that case, you won't be able to fetch a specific player as fast
// if you don't need that functionality, array is as good cause you will just loop through active players and destroy them
var activePlayersKeys = Object.keys(ytPlayers);
if (activePlayersKeys.length > 0) { // if players exist in the pool, destroy them
for (var i = 0; i < activePlayersKeys.length; i++) {
var activeChannel = activePlayersKeys[i];
var activePlayer = ytPlayers[activeChannel];
activePlayer.getIframe().classList.remove('playing'); // mark pause accordingly, by removing class, not necessary
activePlayer.pauseVideo();
}
}
// check if player already initialised and if player exists, check if it has resumeVideo as a function
if (ytPlayers.hasOwnProperty(channel)) {
ytPlayers[channel].playVideo();
} else {
var iframe = iframeContainer[0];
player = new YT.Player(iframe, {
events: {
'onReady': function (event) {
// event.target is the YT player
// get the actual DOM node iframe nad mark it as playing via a class, styling purposes, not necessary
event.target.getIframe().classList.add('playing');
// play the video
event.target.playVideo();
// video may not autoplay all the time in Chrome, despite its state being cued and this event getting triggered, this happens due to a lot of factors
},
// you should also implement `onStateChange` in order to track video state (as a result of user actions directly via YouTube controls) - https://developers.google.com/youtube/iframe_api_reference#Events
}
});
// append to the list
ytPlayers[channel] = player;
}
}
}
// Get the element with id="defaultOpen" and click on it
function onYouTubeIframeAPIReady() {
// YouTube API will call this when it's ready, only then attempt to "mount" the initial channel
document.getElementById("defaultOpen").click();
}
This is the first time I worked with YouTube's iframe API, but it seems reasonable.

youtube API playVideo, pauseVideo and stopVideo not working

I'm attempting to use the YouTube API to control a set of players in a slideshow. I want to be able to stop and play videos depending on which frame the slideshow is on. So I'm pushing the players into an array based on the id of the frame. When a fame changes I call stop on the current one and start on the new one. However even in the most basic case I'm getting this error:
Uncaught TypeError: Object #<S> has no method 'playVideo'
here is the code for the simple test
<!-- WIDGET YOUTUBE VIDEO -->
<div class="widget_youtube_video" id="wyv_1-5">
<iframe id="ytplayer_1-5" width="560" height="315" src="//www.youtube.com/embed/CxW8TLtUMfM?autoplay=0&enablejsapi=1&origin=http://mmgi.localhost/" frameborder="0" allowfullscreen></iframe>
<script type="text/javascript">
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var yt_players = {};
function onYouTubeIframeAPIReady() {
yt_players['1-5'] = new YT.Player('ytplayer_1-5');
yt_players['1-5'].playVideo();
}
</script>
</div><!-- end widget_youtube_video -->
I've already tried removing the origin tag from the url to check if that was causing the issues, but it still gave me the same error. Any help would be greatly appreciated as I have no idea where to go from here.
Thanks in advance.
EDIT:
I also tried to put the player in a non-arrayed object and it also did not work.
A couple of things I see going on that may be of use. First of all, removing the origin parameter will help during development, as it prevents access to the API in general if A) it doesn't match exactly, and B) sometimes for no reason when on localhost.
As you note, though, even when removing it in your case the API isn't responding. This is because creating a YT.player object consumes a bit of time, and so you then are trying to trigger a playVideo method before the object is fully initialized. Instead you should utilize the onReady callback parameter of the YT.Player object, like this:
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var yt_players = {};
function onYouTubeIframeAPIReady() {
yt_players['1-5'] = new YT.Player('ytplayer_1-5', {
events: {'onReady': onPlayerReady} // bind to callback for when object is ready
});
}
function onPlayerReady(event) {
event.target.playVideo(); // this is kept generic so the same callback can be used with any player object
}
Here's a fiddle with the working code:
http://jsfiddle.net/jlmcdonald/dEjXL/

Froogaloop Vimeo API -- Can't call API methods outside of Ready Event

I've scoured the docs in SO and Vimeo and can't seem to figure out how to call an API method outside of the Ready Event in Vimeo. I have created my Vimeo player, embedding it into teacher.js:
$(".video-player").prepend('<iframe id="player" src="//player.vimeo.com/video/'+video_id+'?api=1&player_id=player&badge=0&title=0&byline=0&portrait=0" width="100%" height="100%" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
player = $('iframe#player')[0];
vimeoSetup(player);
Which then calls vimeoSetup in a different script:
function vimeoSetup (player) {
$f(player).addEvent('ready', ready);
function addEvent(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
}
else {
element.attachEvent('on'+eventName, callback);
}
}
function ready(player_id) {
// Keep a reference to Froogaloop for this player
container = document.getElementById(player_id).parentNode.parentNode;
froogaloop = $f(player_id);
var buttons = container.querySelector('div#video-player-controls-holder-teacher div'),
playBtn = document.querySelector('#play-pause');
//play-pause -- I wanted to have a single play-pause button, which is working fine
addEvent(playBtn, 'click', function() {
froogaloop.api('paused', function(paused) {
if(paused) {
froogaloop.api('play');
} else {
froogaloop.api('pause');
}
});
});
...
Now, if I want to say call $f(player).api('pause'); in teacher.js, I get this error: Unable to post message to http://player.vimeo.com. Recipient has origin mydomain. It seems like such a simple problem--I'm not sure if it involves usage of 'this' that is currently beyond me, or if I'm grabbing the Vimeo player embed incorrectly--I did get a lot of "no method .api for this object" in experimenting.
The end goal is that I can create a vimeo player, provide controls (both of these are good), and then use the API to call methods that feed into backbone, including pause, play, and time.
Are there other events, aside from 'click' and user-generated events I can use? Like an event that says another function was called? Seems circuitous...My backbone view looks like this:
pause: function () {
this.player.pauseVideo(); //this is for a YouTube API which works great
//I want to be able to similarly call something like froogaloop.api('pause');
},
Thanks so much--StackOverflow has taught me an amazing amount.
So, if 'ready' event is working, then everything must be setup right. But here is one possible problem.
In:
player = $('iframe#player')[0];
vimeoSetup(player);
You are getting an Object by id 'player' and then, in vimeoSetup(player); passing an Object into $f(player) which is working.
Yet, further down the code in ready function you are passing in to $f(player_id) just an id, not an Object, therefore api calls not working.
You just need to get Object again by player_id and pass it into $f(), or save player = $('iframe#player')[0]; as global var and then call all API methods on it (though can be not a good option, if You want to make something like few dinamicly spawning players each one with own controls, or if You just one of those, who scared of global variables)

VideoJS - unable to destroy and initialize

On VideoJS website you state that support was moved to StackOverflow, so let's try it here. I've got the following code:
var player = _V_('the_id', {}, function(){
jQuery('.remove').on('click.destroyvideojs', function(){
player.destroy();
jQuery(this).unbind('click.destroyvideojs');
});
});
It initializes video at first and it destroys it.
But when I want to initialize it again using the same exact piece of code, it doesn't work. It doesn't initialize the script on the same element ID (when it was removed from DOM and added again with correct initialization call after it's been added). I'm wondering why this might be happening?
Another try today:
var the_id = 'my_id';
var player = _V_(the_id, {}, function(){
player.destroy();
_V_(the_id, {}, function(){
alert('reinit');
});
});
So, re-initialization of VideoJS simply doesn't work. Furthermore, it removed controls from the video now.
In case this helps anyone, it looks like it's dispose in Version 4:
var player = videojs('my-video');
player.dispose();
Having looked at the source for Video.js 5.0.0. #l:17236 You can just do the following:
if(videojs.getPlayers()[id]) {
delete videojs.getPlayers()[id];
}
It seems that player hasn't been defined when the callback executes.
Take a look at this js fiddle http://jsfiddle.net/gaboesquivel/BA8Pm/
destroy(); works for me. this how the function looks like
destroy: function () {
this.stopTrackingProgress();
this.stopTrackingCurrentTime();
_V_.players[this.id] = null;
delete _V_.players[this.id];
this.tech.destroy();
this.el.parentNode.removeChild(this.el)
}
check this solution too
http://help.videojs.com/discussions/problems/861-how-to-destroy-a-video-js-object#comment_13544514
I pulled some of My hare from My head, very hard to find response fore such questions...
So here is My solution, with JQuery... the solution was born from the question how to destroy uninitialised player objects, you guys save my day in the end, cos We can destroy only players that are shown, and even if I clean the HTML and reinitialise dynamically playerJs the flash fall back will not work for unshowed undestroyed media-player.
so here is the solution:
$.each(_V_.players, function (key, player) {
if (player.isReady) { player.destroy(); }
else { delete _V_.players[player.id]; }
});
a little messy but will do just fine.
cheers!
The api reference your looking for is .dispose(); however it does not remove settings from the dom. If you have third party plugins other items may litter your DOM after the dispose is run. To run dispose and clean up your dom use a code like this
dispose = function() {
if (settings.debug) {
console.info('place.videojs_element.dispose()');
} /*Target the Player Element*/
var player = videojs(settings.element.id + '_player'); /*Pause Video*/
player.pause(); /*Wait for third party scripts to stop listening!!! <-- Important*/
setTimeout(function() { /*Dispose of the player*/
player.dispose();
/*I have a new video element waiting to be placed ( this code is proprietary )*/
var epi = new EPI();
epi.place.videojs_element(settings, data); /*Wait time 600ms*/
}, 600); /*Destroy the old video element <--- Important */
$('#' + settings.element.id).empty();
}
See a working example in action: http://codepen.io/JaminQuimby/pen/yNaOwz/
The above will give you a clean DOM and completely remove the video player.

YouTube API Target (multiple) existing iframe(s)

I'm trying to understand how to target an existing iframe using the YouTube API (i.e. without constructing an iframe with the script).
As usual, Google does not give enough API examples, but explains that it IS possible, here http://code.google.com/apis/youtube/iframe_api_reference.html
Here is an example of what I'm trying to do - the video underneath the thumbnail should play. I am almost there, but only the first video plays...
http://jsfiddle.net/SparrwHawk/KtbYR/2/
TL;DR: DEMO: http://jsfiddle.net/KtbYR/5/
YT_ready, getFrameID and onYouTubePlayerAPIReady are functions as defined in this answer. Both methods can be implemented without any preloaded library. In my previous answer, I showed a method to implement the feature for a single frame.
In this answer, I focus on multiple frames.
HTML example code (important tags and attributes are capitalized, <iframe src id>):
<div>
<img class='thumb' src='http://i2.cdnds.net/11/34/odd_alan_partridge_bio_cover.jpg'>
<IFRAME ID="frame1" SRC="http://www.youtube.com/embed/u1zgFlCw8Aw?enablejsapi=1" width="640" height="390" frameborder="0"></IFRAME>
</div>
<div>
<img class='thumb' src='http://i2.cdnds.net/11/34/odd_alan_partridge_bio_cover.jpg'>
<IFRAME ID="frame2" SRC="http://www.youtube.com/embed/u1zgFlCw8Aw?enablejsapi=1" width="640" height="390" frameborder="0"></IFRAME>
</div>
JavaScript code (YT_ready, getFrameID, onYouTubePlayerAPIReady and the YouTube Frame API script loader are defined here)
var players = {}; //Define a player storage object, to expose methods,
// without having to create a new class instance again.
YT_ready(function() {
$(".thumb + iframe[id]").each(function() {
var identifier = this.id;
var frameID = getFrameID(identifier);
if (frameID) { //If the frame exists
players[frameID] = new YT.Player(frameID, {
events: {
"onReady": createYTEvent(frameID, identifier)
}
});
}
});
});
// Returns a function to enable multiple events
function createYTEvent(frameID, identifier) {
return function (event) {
var player = players[frameID]; // Set player reference
var the_div = $('#'+identifier).parent();
the_div.children('.thumb').click(function() {
var $this = $(this);
$this.fadeOut().next().addClass('play');
if ($this.next().hasClass('play')) {
player.playVideo();
}
});
}
}
In my previous answer, I bound the onStateChange event. In this example, I used the onReady event, because you want to call the functions only once, at initialization.
This example works as follows:
The following methods are defined in this answer.
The YouTube Frame API is loaded from http://youtube.com/player_api.
When this external script has finished loading, onYoutubePlayerAPIReady is called, which in his turn activates all functions as defined using YT_ready
The declaration of the following methods are shown here, but implemented using this answer. Explanation based on the example:
Loops through each <iframe id> object, which is placed right after <.. class="thumb">.
At each frame element, the id is retrieved, and stored in the identifier variable.
The internal ID of the iframe is retrieved through getFrameID. This ensures that the <iframe> is properly formatted for the API. In this example code, the returned ID is equal to identifier, because I have already attached an ID to the <iframe>.
When the <iframe> exists, and a valid YouTube video, a new player instance is created, and the reference is stored in the players object, accessible by key frameID.
At the creation of the player instance, a **onReady* event is defined. This method will be invoked when the API is fully initialized for the frame.
createYTEvent
This method returns a dynamically created function, which adds functionality for separate players. The most relevant parts of the code are:
function createYTEvent(frameID, identifier) {
return function (event) {
var player = players[frameID]; // Set player reference
...
player.playVideo();
}
}
frameID is the ID of the frame, used to enable the YouTube Frame API.
identifier is the ID as defined in YT_ready, not necessarily an <iframe> element. getFrameID will attempt to find the closest matching frame for a given id. That is, it returns the ID of a given <iframe> element, or: If the given element is not an <iframe>, the function looks for a child which is a <iframe>, and returns the ID of this frame. If the frame does not exists, the function will postfix the given ID by -frame.
players[playerID]` refers to the initialized player instance.
Make sure that you also check this answer, because the core functionality of this answer is based on that.
Other YouTube Frame API answers. In these answers, I showed various implementations of the YouTube Frame/JavaScript API.

Categories