Youtube iframe API with javascript tabs - javascript

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.

Related

Mute audio created in iframe with Audio object

I am working with an iframe that contains code that we receive from a third party. This third party code contains a Canvas and contains a game created using Phaser.
I am looking for a way to mute the sound that this game does at some point.
We usually do it this way:
function mute(node) {
// search for audio elements within the iframe
// for each audio element,(video, audio) attempt to mute it
const videoEls = node.getElementsByTagName('video');
for (let i = 0; i < videoEls.length; i += 1) {
videoEls[i].muted = true;
}
const audioEls = node.getElementsByTagName('audio');
for (let j = 0; j < audioEls.length; j += 1) {
audioEls[j].muted = true;
}
}
After some research I found out that you can play sound in a web page using new Audio([url]) and then call the play method on the created object.
The issue with the mute function that we use is that, if the sound is created with new Audio([url]), it does not pick it up.
Is there a way from the container to list all the Audio elements that have been created within a document or is it just impossible, and that creates a way to play audio without the possibility for iframe container to mute it?
No, there is no way.
Not only can they use non appended <audio> elements like you guessed, but they can also use the Web Audio API (which I think phaser does) and for neither you have a way of accessing it from outside if they didn't expose such an option.
Your best move would be to ask the developer of this game that it exposes an API where you would be able to control this.
For instance, it could be some query-parameter in the URL ( https://thegame.url?muted=true) or even an API based on the Message API, where you'd be able to do iframe.contentWindow.postMessage({muted: true}) from your own page.

Throttling HTML5 Video currentTime

I have an app that tracks video views and integrates it with other marketing activities. In doing so, I needed to keep track of how long a person watches a html5 video and post it back to my app (via an API). I'm using videojs player, but really this is just a wrapper around the HTML5's api for this attribute. This is in an app with various videos can be loaded based on what page they are watching, so I needed a solution that tracked regardless of video length.
The problem I had, as a video plays the API reports back every ~300MS and I didn't want to hit my API that often. So I needed a solution to keep track of last time I posted. After digging around, I couldn't find an answer, so in case someone else with a similar need, my solution to this problem is below.
We've decided that I wanted to post my video viewing results every 5 seconds, but since we have no guarantee that the currentTime will report back at exactly 5 seconds, so we just need to round to closest whole integer value.
On my video wrapper div, I've added a data attribute called data-last-time-push. I post the rounded time every time I push and check to see if we have exceed the interval before we post again.
HTML
<div id="video-wrapper" data-time-last-push="0">
Javascript
Bind the videojs container to the timeupdate property.
var vid = videojs("video-container", {}, function() {
this.on('timeupdate', videoTracker);
});
function for posting ajax...
var videoTracker = function() {
var player = this;
var last_push, wrapper, current;
wrapper = $('#video-wrapper');
last_push = wrapper.attr("data-time-last-push");
current = Math.round(player.currentTime());
//you could make the 5 here to be a variable or your own interval...
if (current%5 === 0) {
if (current > last_push) {
//do your AJAX post here...
wrapper.attr("data-time-last-push", current);
console.log('currentTime = ' + player.currentTime());
console.log(' duration: ' + player.duration());
}
}
};
Note, I tried to do a jsfiddle to show it working, but ended up running into HTTPS videos because the sample videos don't work through secure connections.

Youtube - YT Object Has No Method PauseVideo()

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.

Interact with Vimeo player on vimeo.com using Javascript

I have successfully used the Froogaloop library to monitor embedded Vimeo video players on various sites (for a feature of my Chrome extension).
I'd like to do the same thing on vimeo.com itself, but the video players on vimeo.com pages are not embedded within iframes, so Froogaloop isn't able to interface with them.
Can anybody tell me how to get the current playing time and wire up play/pause event listeners for a (non-iframed) video on vimeo.com?
After some further research I came up with the following basic solution:
// Once the vimeo.com player <object> is ready:
// find vimeo player(s)
var vimeoPagePlayers = document.querySelectorAll('object[type*=flash][data*=moogaloop]');
if (vimeoPagePlayers.length == 0) {
return;
}
// attach event listeners to player(s)
for (var i = 0; i < vimeoPagePlayers.length; i++) {
var player = vimeoPagePlayers[i];
// sometimes .api_addEventListener is not available immediately after
// the <object> element is created; we do not account for this here
player.api_addEventListener('onProgress', 'onVimeoPageProgress');
player.api_addEventListener('onPause', 'onVimeoPagePause');
}
function onVimeoPagePause() {
// video has been paused or has completed
}
function onVimeoPageProgress(data) {
// video is playing
var seconds;
if (typeof(data) == 'number') {
// on vimeo.com homepage, data is a number (float)
// equal to current video position in seconds
seconds = data;
}
else {
// on vimeo.com/* pages, data is a hash
// of values { seconds, percent, duration }
seconds = data.seconds;
}
// do something with seconds value
}
This may not work for HTML5-mode videos on vimeo.com (untested).
It also does not work when a vimeo.com page replaces the existing player with another one via DOM manipulation. I believe those situations could be handled by something like a MutationObserver.

mediaElementjs: how to get instance of the player

I'm stuck with a little problem with MediaElement.js player.
To get the instance of the player, I do this (works with html5 compatible browser):
// Get player
this.playerId = $('div#shotlist-player video').attr('id');
this.player = window[this.playerId];
But it's not working as soon as it fallback in flash. In fact, it's not working because I'm not calling an instance of MediaElement itself. But I don't see how I can call it.
The player is created with
$('video').mediaelementplayer({....});
How can I get the mediaelement object?
------------EDIT----------------
Ok I finally found how to make it works:
// Get player
mePlayer = $('div#shotlist-player video.video-js')[0];
this.player = new MediaElementPlayer(mePlayer);
Now I can user mediaElement instance correctly.
This post is a lot of speculation, but may be correct. Docs are lacking (;
The answer by sidonaldson is perfectly acceptable if you wish to create a new MediaElement instance and get a handle on it. If there's one already present, it seems to try to reinitialize another instance on that element and freaks out.
I am pretty sure mediaelement.js augments the builtin HTML5 controls by providing a JavaScript API to manipulate Flash/Silverlight players via those elements. I may be wrong, but other advice I've seen on this issue in multiple places is to do something like:
$playButton.click(function() {
$('video, audio').each(function() {
$(this)[0].player.play();
});
});
To create a play button as an external DOM element which will fire off all players on the page. This indicates to me that the implementation is something like I've described.
Try:
var player = $('video').mediaelementplayer({
success: function (me) {
me.play();
}
});
// then you can use player.id to return the id
// or player.play();

Categories