Toggle Play/Pause Youtube Video with external button - javascript

I have a template I'm building out using fullPage js to create sliding sections. The page supports video, but scroll and gestures are disabled when users scroll with the cursor on top of the video itself. Likewise, this behavior is observed on mobile.
To work around this, I'm using an empty div overlaying the youtube iframe. This solves the scrolling behavior, but I also lack the ability to directly control the youtube player. I've tried using the youtube API and jquery to fake being able to toggle play/pause with the empty div I have on top of the youtube player, but it's not working.
http://codepen.io/lumpeter/pen/XpayeB
The primary code for youtube is:
function onYouTubePlayerReady(playerId) {
ytplayer = document.getElementById("player");
ytplayer.addEventListener("onStateChange", "onPlayerStateChange");
}
function onPlayerReady(event) {
$('#video-button').click(function(event){
ytplayer.playVideo();
});
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
$('#video-button').click(function(event){
ytplayer.pauseVideo();
});
}
else {
$('#video-button').click(function(event){
ytplayer.playVideo();
});
}
}
Can anybody tell why this isn't working? I have event listeners looking for changes and using a boolean to detect the state of the video to toggle play/pause, but for some reason this doesn't work like I anticipated.
===============UPDATE=================
I've also tried the following:
$(document).on('click', '#pauseVideo', function(){
player = new YT.Player('myVideo')
if (event.data == player.PlayerState.PAUSED) {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}','*');
}
if (event.data == player.PlayerState.PLAYING) {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
}
});
However, I get the error "Uncaught ReferenceError: YT is not defined."

As fullpage.js already does part of the job by adding ?enablejsapi=1 and loading the JS script of the API for you, then you only have to do this:
var player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('myVideo');
}
$(document).on('click', '#pauseVideo', function(){
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
});
Reproduction online

Thanks Alvaro! Yeah I was overthinking the solution a bit with the youtube api. I just needed to use a boolean with a binary true/false statement. Super easy, should have seen it before. Again thanks for your help!
var player;
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('myVideo',{
events: { 'onStateChange': onPlayerStateChange }
});
}
var isPlaying = true;
$(document).on('click', '#pauseVideo', function(){
if ( isPlaying == true){
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}','*');
isPlaying = false;
} else {
$('#myVideo').get(0).contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}','*');
isPlaying = true;
}
});

Related

Youtube player.destroy(); throws 'this.a is null', even when validating player

So I've got a small app with two panels. Using the iframe API. Clicking on one panel will expand the panel full screen, as well as showing a 'play video' button with some additional information. Clicking a button in the top left will return the UI to it's standard state, closing down the video and shrinking the panels back to fit 50/50.
Now as we've got two videos, I've defined the videos as such, #vidPlayer2 being the second trigger.
$('#vidPlayer1').on('click', function(){
player = new YT.Player('player', {
height: '100%',
width: '100%',
videoId: '(video id here)',
controls: 0,
showinfo: 0,
autoplay: 0,
rel: 0,
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
Similarly, we've got the default demo code, with a small modification:
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
done = true;
}
if (event.data == YT.PlayerState.ENDED) {
resetView();
}
}
function stopVideo() {
player.stopVideo();
}
Then, we're trying to get the button to work. In some circumstances, not having clicked one of the vidPlayer buttons, no player is defined, so I threw in an if statement with some validation.
var resetView = function() {
// If a Youtube player is active, make sure we stop it.
if (player === undefined || !player || null) {
console.log("Player could not be found.");
} else {
player.stopVideo();
player.destroy();
}
// Additional code to reset the UI removed below. Works no matter what if the above code is removed.
};
Now for the most part, things work well UNTIL I try to go into a panel, play a video, reset UI, then try to enter and leave the next panel without playing a video. When I follow this exact series of steps, regardless of what panel starts first, I get a TypeError: this.a is null in console. I would've assumed that the validation would've done the trick, but apparently not.
So what I can distinguish from this is it works fine when initialized - i.e. var player is initialized. The return button works through just going back and forth without playing a video. The return button works when a video is actively playing, but the function fails if we try to use the return directly after the player is stopped and destroyed. It does work if we simply pop open another video, however.
Is there something I'm missing when I'm trying to reset the view? Does the youtube player have to be reinitialized? Any help or thoughts would be greatly appreciated. Thanks!
Edit: This is the note that's being thrown by the console. Something to note is main.js:44:5 is the player.stopVideo(); call, and main.js:70:3 is when resetView(); is called on a button click.
TypeError: this.a is null
www-widgetapi.js:120:73
f.C
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:120:73
V
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:112:97
Nb/</this[a]
https://s.ytimg.com/yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:130:124
resetView
file:///Users/cipher/Desktop/ERHS_video/js/main.js:44:5
<anonymous>
file:///Users/cipher/Desktop/ERHS_video/js/main.js:70:3
dispatch
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:10264
add/q.handle
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:8326
The problem here is player is NOT undefined. What's happening is you have a global player reference, and you're doing the following with it:
Creating a player in the first panel
Destroying it when the first panel closes
Calling player.stopVideo() on the already destroyed player (from the first panel) when the second panel closes
Currently, player holds a reference to whatever the last YouTube player you were using is, even if that player has already been destroyed.
What you should be doing is clearing out your reference to the player when you destroy it. Destroy won't (and can't) do that. You can also simplify your if condition since !player will check for null and undefined on its own:
var resetView = function() {
// If a Youtube player is active, make sure we stop it.
if (!player) {
console.log("Player could not be found.");
} else {
player.stopVideo();
player.destroy();
player = null; // Clear out the reference to the destroyed player
}
Highly inspired from IkeDoud's above answer and some others like this one, one year later, here is my way to avoid other video suggestions on play end in embedded API player:
<div id="player"></div>
<style>
#player{
min-width:auto;
min-height:auto;
}
</style>
<script>
// Load 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);
// Initialise the player with options
var player;
var playerOptions = {
videoId: 'ViDeOiD_HeRe',
events: {
'onStateChange': onPlayerStateChange
}
};
// Load the video whern player ready
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', playerOptions);
}
// On video end, reset the player back to poster image
function onPlayerStateChange(event) {
if(event.data === 0) {
$(PaReNt_sElEcToR).find("#player").replaceWith('<div id="player"></div>');
player = null;
player = new YT.Player('player', playerOptions);
}
}
</script>
No more irrelevant suggestions playable from the embedded video container.
And no autoplay (mobile forbids it anyway) to have the poster (thumbnail) image.
On event.data === 0, which is video end... Destroy and reload the Iframe.

multiple audio, auto stop other when current is playing with javascript

I have more than 5 audio players with audioplayer.js on a html5 page.
Does anyone has a simple script in js to pause all other players when the current player is playing ?
jQuery:
$( function(){
$("audio").audioPlayer();
});
Library
https://osvaldas.info/examples/audio-player-responsive-and-touch-friendly/audioplayer.js
I have tried as below
window.player = $('audio')[0];
$('.audioplayer-playpause').click(function () {
if (player.paused) {
player.play();
} else {
player.pause();
}
});
I have made a jsFiddle just to easeout things and below is the link:
JS Fiddle for Audio Player
Try the below code.I have tested it in JS Fiddle & it is working :) :)
Updated JSFiddle
Put this code in Javascript & remove yours.
document.addEventListener('play', function(e){
// get all <audio> tag elements in the page.
var allAudios = document.getElementsByTagName('audio');
// Iterate through all players and pause them, except for
// the one who fired the "play" event ("target")
for(var i = 0; i<allAudios.length; i++){
if(allAudios[i] != e.target){
allAudios[i].pause();
}
}
}, true);

Getting an embedded YouTube video to play after it's been closed & re-opened on iOS7 Safari

Relatively new to JS here. I've created a button that embeds a YouTube video on the page when you click it, and the user has the ability to close it. On desktop and Android OS, it can be closed and re-opened as many times as the user wants. However, on iOS, the video only plays the first time.
I believe this issue is caused by the fact that Apple doesn't want the video to play without the user's permission, so when you click the "play" button it brings up the video - but the default YouTube play button itself is not there when the video is closed and re-opened.
This should be most of the relevant code.
VideoPlayer = new VideoPlayer('Header', 'xi2-7FCuDdg');
$("#HomePlayButton").on('click', function () {
disable_scroll();
VideoPlayer.play();
$("#Header").append($('<img src="/images/close.png" />'));
$("#VideoCloseButton").on('click', function () {
VideoPlayer.stop();
VideoPlayer.hide();
enable_scroll();
$("#VideoCloseButton").unbind('click');
$("#VideoCloseButton").remove();
return false;
});
});
this.play = function () {
if (!Initialized) {
// This autoplays the video
Init();
Initialized = true;
} else {
if ($("#" + YouTubeElementId).css('display') == "none") {
$("#" + YouTubeElementId).css('display', 'block');
}
YouTubePlayer.playVideo();
}
}
this.stop = function () {
YouTubePlayer.stopVideo();
}
this.isPlaying = function () {
return (Initialized) ? YouTubePlayer.getPlayerState() == 1 : false;
}
this.hide = function () {
$("#" + YouTubeElementId).css('display', 'none');
}
this.show = function () {
$("#" + YouTubeElementId).css('display', 'block');
}
I'm assuming the easiest way to solve this would be to entirely destroy the iframe and make a new one every time the video is closed, but I was unable to successfully implement that.
Glad to post more code if it's necessary.

Keeping two YouTube videos in sync with each other

I've got two identical YouTube videos embedded on the same page. I'd like them to both be in sync, here are my requirements / notes:
Both videos must start at the same time
When a video is played / paused by the user the other video does the same
This is quite easy via the API
When one video buffers the other stops to wait, and starts when they are both ready
I only need audio from one video
Sync accuracy doesn't have to be millisecond perfect, just reliable
One video will be used as a background video
This video will be slightly blurred (using CSS3 blur), so quality not super essential
I've tried using the YouTube JS API to listen for player state changes and attempt to keep both videos in sync, however it wasn't as reliable as I'd hoped for. I'll post part of the code for this below.
One caveat is that one video will appear larger than the other, so the YouTube might provide a higher quality video for that.
Because I'm using CSS3 blur I can only use recent Webkit browsers, so a solution that works on these alone (and not FF/IE) is not a problem.
My question is this, for the requirements above, is there any way to keep these two videos in sync? I did consider if it was possible to use the canvas API to "redraw" the video, but after researching figured this wasn't going to be possible.
buffering = false;
var buffer_control = function(buffering_video, sibling_video, state) {
switch ( state ) {
case 1: // play
if ( buffering === true ) {
console.error('restarting after buffer');
// pause both videos, we want to make sure they are both at the same time
buffering_video.pauseVideo();
sibling_video.pauseVideo();
// get the current time of the video being buffered...
var current_time = buffering_video.getCurrentTime();
// ... and pull the sibling video back to that time
sibling_video.seekTo(current_time, true);
// finally, play both videos
buffering_video.playVideo();
sibling_video.playVideo();
// unset the buffering flag
buffering = false;
}
break;
case 3: // buffering
console.error('buffering');
// set the buffering flag
buffering = true;
// pause the sibling video
sibling_video.pauseVideo();
break;
}
}
Your project is kinda interesting, that's why I decided to try to help you. I have never used the youtube API but I have tried some coding and it might be a start for you.
So here is the code I have tried and it seems to work quite well , it certainly needs some improvements ( I haven't tried to calculate the offset between the two played videos but letting them unmute shows the mismatch and it sounds legit)
Here we go :
Let's start with some html basics
<!DOCTYPE html>
<html>
<head>
We add an absolute positionning for the foreground player so it overlays the one playing the background video (for testing)
<style>
#player2{position:absolute;left:195px;top:100px;}
</style>
</head>
<body>
jQuery is used here to fade in/out the players (you'll see why below) but you can use basic JS
<script src="jquery-1.10.2.min.js"></script>
The < iframes> (and video players) will replace these < div> tags.
<div id="player1"></div> <!-- Background video player -->
<div id="player2"></div> <!-- Foreground video player -->
<script>
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);
This function creates the < iframes> (and YouTube players) after the API code downloads. Note the callback functions : onPlayer1Ready and onPlayer1StateChange
var player1;
var player2;
function onYouTubeIframeAPIReady() {
player1 = new YT.Player('player1', {
height: '780',
width: '1280',
videoId: 'M7lc1UVf-VE',
playerVars: { 'autoplay': 0, 'controls': 0 },
events: {
'onReady': onPlayer1Ready,
'onStateChange': onPlayer1StateChange
}
});
player2 = new YT.Player('player2', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayer2Ready,
'onStateChange': onPlayer2StateChange
}
});
}
var player1Ready = false;
var player2Ready = false;
So now is the interesting part of the code. The main issue in your project of sync is linked to the fact that videos need to be buffered before launching them. Actually the API doesn't provide any kind of intuitive function to preload the videos (due to bandwidth issues (client and server side). So we have to get a bit tricky.
The steps to preload a video are the following:
Hide the player so the next steps aren't visible for the user);
Mute the player ( player.mute():Void );
Emulate a jump in timeline to start the buffering ( player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void );
Wait for a state change event equal to YT.PlayerState.PLAYING;
Pause the video ( player.pauseVideo():Void );
Rewind the video with player.seekTo(seconds:Number, allowSeekAhead:Boolean):Void ;
Unmute the player ( player.unMute():Void );
Show the player.
You have to preload your two videos.
var preloading1 = false;
var preloading2 = false;
The API will call these functions when the video players are ready.
function onPlayer1Ready(event)
{
player1Ready = true;
preloading1 = true; // Flag the player 1 preloading
player1.mute(); // Mute the player 1
$( "#player1" ).hide(); // Hide it
player1.seekTo(1); // Start the preloading and wait a state change event
}
function onPlayer2Ready(event) {
player2Ready = true; // The foreground video player is not preloaded here
}
The API calls this function when the background video player's state changes.
function onPlayer1StateChange(event)
{
if (event.data == YT.PlayerState.PLAYING ) {
if(preloading1)
{
prompt("Background ready"); // For testing
player1.pauseVideo(); // Pause the video
player1.seekTo(0); // Rewind
player1.unMute(); // Comment this after test
$( "#player1" ).show(); // Show the player
preloading1 = false;
player2Ready = true;
preloading2 = true; // Flag for foreground video preloading
player2.mute();
//$( "#player2" ).hide();
player2.seekTo(1); // Start buffering and wait the event
}
else
player2.playVideo(); // If not preloading link the 2 players PLAY events
}
else if (event.data == YT.PlayerState.PAUSED ) {
if(!preloading1)
player2.pauseVideo(); // If not preloading link the 2 players PAUSE events
}
else if (event.data == YT.PlayerState.BUFFERING ) {
if(!preloading1)
{
player2.pauseVideo(); // If not preloading link the 2 players BUFFERING events
}
}
else if (event.data == YT.PlayerState.CUED ) {
if(!preloading1)
player2.pauseVideo(); // If not preloading link the 2 players CUEING events
}
else if (event.data == YT.PlayerState.ENDED ) {
player2.stopVideo(); // If not preloading link the 2 players ENDING events
}
}
The API calls this function when the foreground video player's state changes.
function onPlayer2StateChange(event) {
if (event.data == YT.PlayerState.PLAYING ) {
if(preloading2)
{
//prompt("Foreground ready");
player2.pauseVideo(); // Pause the video
player2.seekTo(0); // Rewind
player2.unMute(); // Unmute
preloading2 = false;
$( "#player2" ).show(50, function() {
Here is a part of the code that acts strangely. Uncommenting the line below will make the sync quite bad,but if you comment it, you will have to click twice on the PLAY button BUT the sync will look way better.
//player2.playVideo();
});
}
else
player1.playVideo();
}
else if (event.data == YT.PlayerState.PAUSED ) {
if(/*!preloading1 &&*/ !preloading2)
player1.pauseVideo();
}
else if (event.data == YT.PlayerState.BUFFERING ) {
if(!preloading2)
{
player1.pauseVideo();
//player1.seekTo(... // Correct the offset here
}
}
else if (event.data == YT.PlayerState.CUED ) {
if(!preloading2)
player1.pauseVideo();
}
else if (event.data == YT.PlayerState.ENDED ) {
player1.stopVideo();
}
}
</script>
</body>
</html>
Note that the views might not be counted with this code.
If you want the code without the explanations you can go here : http://jsfiddle.net/QtBlueWaffle/r8gvX/1/
2016 Update
Live Preview
Hope this helps.

Youtube API calling player functions after player is initated

This is my first use of the youtube api and I'm finding it hard understanding the documentation. Basically I have multiple videos on the page, when you click one it pops up in a lightbox and plays the correct video. All of this works fine.
The client now wants all the videos to be automatically muted on start, and once you finish the video they want the frame to skip to the right place.
If I use the youtube iframe api i can't seem to be able to update the videoId at all. Below is my code:
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('player', {
height: '323',
width: '576',
videoId: 'Fy7Li0dMRSY',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(event) {
event.target.mute(); // this does not do anything
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
//setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
function loadVid(vidID, title){
// loads youtube video with video title & youtube video ID
$('.lightbox .title').html(title);
$('.lightbox iframe').prop('src','https://www.youtube.com/embed/'+vidID+'?rel=0&wmode=transparent&enablejsapi=1');
//player.mute() does not work here
showLightbox();
}
My problem is trying to call the player function to mute inside the loadVid function, it throws back an error (any function throws the same error) and I can't figure out how to use functions such as player.
The error is player.stopVideo is not a function
So how can I mute the video on start? Adding it to onPlayerReady does not work either.
Move the event.target.mute(); to the onPlayerStateChange function like this:
var done = false;
function onPlayerStateChange(event) {
event.target.mute();
if (event.data == YT.PlayerState.PLAYING && !done) {
//setTimeout(stopVideo, 6000);
done = true;
}
}
When you have multiple videos on the page, you still have to call onYouTubePlayerAPIReady for each one. You can pack all your player handlers in there at the same time, but depending on how you have to do it, that's not possible. Instead, you have to figure a way to trick the API. Take a look at my answer here loading multiple video players with youtube api

Categories