BackboneJS Youtube player only renders once - javascript

I have a BackboneJS App where I display Youtube Videos on my View. So when user clicks on a thumbnail the Youtube-iframe opens and plays the video. When user closes it and wants to play it again, nothing happens. To show it again, the user has to refresh the page which is not good.
My View looks like this:
function (App, Backbone, ArtistVideos, Artistimg, Artistname) {
var ArtistVideoPopup = App.module();
ArtistVideoPopup.View = Backbone.View.extend({
template: 'artistYoutubeVideo',
initialize: function () {
},
beforeRender: function() {
$("#vid").before("<div id='vidoverlay'></div>");
$("#vid").after("<div id='closeDiv'><button title='Luk (Esc)' type='button' class='mdk-close'>×</button></div>")
var artistimgCollection = new Artistimg.ArtistimgCollection();
artistimgCollection.artist_id = this.artist_id;
this.insertView('.artistImage', new Artistimg.View({collection: artistimgCollection}));
artistimgCollection.fetch();
var artistnameCollection = new Artistname.ArtistnameCollection();
artistnameCollection.artist_id = this.artist_id;
this.insertView('.artistName', new Artistname.View({collection: artistnameCollection}));
artistnameCollection.fetch();
var artistvideosModel = new ArtistVideos.ArtistVideosModel();
artistvideosModel.artist_id = this.artist_id;
artistvideosModel.video_youtube_id = this.video_youtube_id;
artistvideosModel.fetch();
},
afterRender: function() {
$('ul.acmenu li a.selected').removeClass('selected');
$('ul.acmenu li a.artistVideos').addClass('selected');
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 player;
window.onYouTubeIframeAPIReady = _.bind(function() {
player = new YT.Player('vid', {
height: '480',
width: '853',
videoId: this.video_youtube_id,
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
})
}, this);
function onPlayerReady(event) {
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();
}
$(".mdk-close").click(function(){
$(this).hide();
$("#vidoverlay").fadeOut(500);
$("#vid").fadeOut(500);
var video = $("#vid").attr("src");
$("#vid").attr("src","");
$("#vid").attr("src",video);
});
}
});
return ArtistVideoPopup;
}
Like mentioned before, it only renders once! As soon I close it, the video-iframe does not appear before i reload the whole page again.
So does anyone know what might be the issue here? Please help...
Thanks in advance...

I haven't really tested yet, but give this a shot:
function (App, Backbone, ArtistVideos, Artistimg, Artistname) {
var ArtistVideoPopup = App.module();
// Create a new Deferred for the YouTube IFrame API
var loadYouTubeAPI = $.Deferred();
// When the API is ready then resolve the Deferred's promise()
window.onYouTubeIframeAPIReady = loadYouTubeAPI.resolve;
// Convert the Deferred into its own promise() so nothing else can
// resolve or reject it before onYouTubeIframeAPIReady is called
loadYouTubeAPI = loadYouTubeAPI.promise();
// Begin download of YouTube IFrame API
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// Helpful function to get the player state as a string from an int
var playerState = function(data){
switch (data) {
case -1: return 'unstarted';
case 0: return 'ended';
case 1: return 'playing';
case 2: return 'paused';
case 3: return 'buffering';
case 5: return 'cued';
default: return 'quantum';
}
}
ArtistVideoPopup.View = Backbone.View.extend({
template: 'artistYoutubeVideo',
initialize: function(){
_.bindAll(this, 'onAPIReady', 'closePlayer');
this.$iframe = $('#vid');
this.iframe = this.$iframe.get(0);
loadYouTubeAPI.then(this.onAPIReady);
},
onAPIReady: function(){
this.player = new YT.Player(this.iframe, {
height: '480',
width: '853',
videoId: this.video_youtube_id,
events: {
onReady: this.onPlayerReady,
onStateChange: this.onPlayerStateChange
}
});
},
onPlayerReady: function(e){
e.target.playVideo();
},
onPlayerStateChange: function(e){
console.log(playerState(e.data));
},
closePlayer: function(e){
$(e.currentTarget).hide();
$("#vidoverlay").fadeOut(500);
this.$iframe.fadeOut(500).done(this.onPlayerClose);
},
onPlayerClose: function(){
if (this.player) {
this.player.stopVideo();
}
},
beforeRender: function() {
this.$iframe
.before("<div id='vidoverlay'></div>")
.after("<div id='closeDiv'><button title='Luk (Esc)' type='button' class='mdk-close'>×</button></div>")
var artistimgCollection = new Artistimg.ArtistimgCollection();
artistimgCollection.artist_id = this.artist_id;
this.insertView('.artistImage', new Artistimg.View({collection: artistimgCollection}));
artistimgCollection.fetch();
var artistnameCollection = new Artistname.ArtistnameCollection();
artistnameCollection.artist_id = this.artist_id;
this.insertView('.artistName', new Artistname.View({collection: artistnameCollection}));
artistnameCollection.fetch();
var artistvideosModel = new ArtistVideos.ArtistVideosModel();
artistvideosModel.artist_id = this.artist_id;
artistvideosModel.video_youtube_id = this.video_youtube_id;
artistvideosModel.fetch();
},
afterRender: function() {
this.$iframe.show();
$('ul.acmenu li a.selected').removeClass('selected');
$('ul.acmenu li a.artistVideos').addClass('selected');
$("#closeDiv").on('click', '.mdk-close', this.onPlayerClose);
}
});
return ArtistVideoPopup;
}

Related

Accessing lazy global variable from API in stimulus framework

I'm trying to make a Stimulus controller which would access global Youtube Player API variable
Youtube Player API is lazy loading - it's class is loaded asynchronously
// 2. 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);
The problem is, that Stimulus doesn't want to access YT variable.
I tried to create a function, that loads script, and after it, runs given function, and imports it to stimulus
export function loadScript (url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
if (script.readyState) { // only required for IE <9
script.onreadystatechange = function () {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
But as you can see, it only creates new script object, so Stimulus still won't be able to access it.
Is there a way in which I could make Stimulus access loaded script, and variables that are declared in it?
Here is a full controller code (don't mind bad practices, it's just a fast try):
import {
Controller
} from 'stimulus';
import $ from 'jquery';
export default class extends Controller {
static targets = ['collapseButton', 'player']
connect() {
this.initPlayer();
}
initPlayer() {
let player = $('#youtubePlayer');
// Code taken straight from https://developers.google.com/youtube/iframe_api_reference?hl=pl
player = new YT.Player(player, {
videoId: 'Va297erJjJ4',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
function onPlayerReady(event) {
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();
}
}
}
<div class="youtube-player-modal" data-controller="youtubeplayer">
<div id="youtubePlayer"></div>
</div>
I wrote a solution to your problem here, there you must first call the ready function and then give the callback.
https://codesandbox.io/s/funny-browser-n3m3j?file=/src/Player.js
// loadScript.js
import Player from "./Player";
function loadScript(url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
if (script.readyState) {
// only required for IE <9
script.onreadystatechange = function () {
if (script.readyState === "loaded" || script.readyState === "complete") {
script.onreadystatechange = null;
callback();
}
};
} else {
//Others
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadScript("https://www.youtube.com/iframe_api", new Player().initPlayer);
// Player.js
import { Controller } from "stimulus";
import $ from "jquery";
export default class extends Controller {
static targets = ["collapseButton", "player"];
connect() {
this.initPlayer();
}
initPlayer() {
let player = $("#youtubePlayer");
window.YT.ready(function () {
player = new window.YT.Player("youtubePlayer", {
height: "360",
width: "640",
videoId: "Va297erJjJ4",
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange
}
});
});
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data === window.YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
}
}
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div
class="youtube-player-modal"
data-controller="youtubeplayer"
>
<div id="youtubePlayer"></div>
</div>
<script src="src/loadScript.js"></script>
</body>
</html>

Youtube Iframe API multiple video's start each video with a custom button

I have a script that loads multiple videos. Each video has an overlay and a custom button.
I'm currently struggling with the following.
Each video has an .icon--play and A .video-overlay element. When I click on one of the .icon-play buttons I want the video to start.
I also want the .video-overlay to disappear. I have this working when it's just one video.
But I can't figure out how to get this working with multiple video's. The video's are not static and are loaded dynamically.
There is an array with all the players. That should help me control them individually. But I'm not that good at js to understand how I must do this. Any help would be much appreciated.
At the moment, with the below code all video's start simultaneously.
import YouTubePlayer from 'youtube-player';
export default class ytPlayer {
constructor() {
const videoOverlay = document.querySelector('.player-overlay');
const videoButton = document.querySelector('.icon--play');
/**
* #see https://developers.google.com/youtube/iframe_api_reference#Events
*/
const stateNames = {
'-1': 'unstarted',
0: 'ended',
1: 'playing',
2: 'paused',
3: 'buffering',
5: 'video cued',
};
if (videoButton) {
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
const players = {};
window.onYouTubeIframeAPIReady = function () {
console.log('YouTube Iframe API Ready');
$('.video').each(function () {
const videoButton = document.querySelector('.icon--play');
// videoButton.addEventListener('click', () => {
// videoOverlay.classList.add('fade');
// });
players[$(this).attr('id')] = new YT.Player($(this).attr('id'), {
videoId: $(this).attr('data-video'),
rel: 1,
host: 'https://www.youtube-nocookie.com',
events: {
'onStateChange': onPlayerStateChange($(this).attr('id'))
}
});
});
};
function onPlayerStateChange(player_id) {
$(".icon--play").click(function () {
$(".video-overlay").hide();
$(players[player_id])[0].playVideo();
});
return function (event) {
if (players[player_id].getPlayerState() == 3 || players[player_id].getPlayerState() == 1) {
//stop the auto scrolling
console.log("You clicked " + player_id);
players[player_id].playVideo();
}
if (players[player_id].getPlayerState() == 0 || players[player_id].getPlayerState() == 2) {
//start auto scrolling.
console.log("the video is paused " + player_id);
videoOverlay.classList.remove('fade');
}
if (players[player_id].getPlayerState() == -1 || players[player_id].getPlayerState() == 2) {
//start auto scrolling.
console.log("the video is playing or paused " + player_id);
$(videoButton ).on('hidden.bs.modal', function (e) {
players[player_id].stopVideo();
})
}
};
}
}
}
}

Move <Script> content to a separate JS file

I have working code to update a video id. However, when I moved all my JS code from the script tag to a JS file, the code breaks. The code does not load, so iframe api does not load and the on click events do not execute.
<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);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: '{{no-video-id}}',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
$('.open-popup').click(function() {
event.target.playVideo();
});
$('.close-popup').click(function(e) {
player.stopVideo();
});
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if(event.data === 0) {
$('.close.close-popup').click();
}
}
function stopVideo() {
player.stopVideo();
}
$(function () {
$('#my_modal').on('show.bs.modal', function(e) {
var bookId = $(e.relatedTarget).data('book-id');
$(e.currentTarget).find('input[name="bookId"]').val(bookId);
var x = new String(bookId);
player.loadVideoById(x);
});
$('#video-player-1').click(function(e) {
var bookId = $(e.relatedTarget).data('book-id');
var x = new String(bookId);
player.loadVideoById(x);
alert("HEELO");
});
$( "#video-player-2" ).click(function() {
alert( "Handler for .click() called." );
});
});
</script>
This is the JS fiddle showing the code moved into a separate file.
https://jsfiddle.net/k7FC2/7313/
When I click the tag, I get "TypeError: undefined is not an object (evaluating 'player.loadVideoById')" in the console
UPDATE Solution: Add <script src="youtube.com/iframe_api"> in html https://jsfiddle.net/k7FC2/7325/
Please call onYouTubeIframeAPIReady method after loading complete, html takes time to load. and your player is undefined. check here
$(function () {
// call onYoutubeIframeAPIReady method when you have access to all html
onYouTubeIframeAPIReady(); // add this line
$('#my_modal').on('show.bs.modal', function(e) {
var bookId = $(e.relatedTarget).data('book-id');
$(e.currentTarget).find('input[name="bookId"]').val(bookId);
var x = new String(bookId);
player.loadVideoById(x);
});
$('#video-player-1').click(function(e) {
var bookId = $(e.relatedTarget).data('book-id');
var x = new String(bookId);
player.loadVideoById(x);
alert("HEELO");
});
$( "#video-player-2" ).click(function() {
alert( "Handler for .click() called." );
});
});
and add script tag
<script src="https://www.youtube.com/iframe_api"></script>

Hard to resolve issue! :: TypeError: player.loadVideoById is not a function

When $('#myModal').on('show.bs.modal' gets called. I get this:
TypeError: player.loadVideoById is not a function. (In 'player.loadVideoById(x)', 'player.loadVideoById' is undefined)
https://jsfiddle.net/rdbj3ty1/5/
In JS Fiddle the code works, but when I add it to my code base, it stops working. I have a lot of other js code and CSS code that it must be interfering with. Is there a way to prevent the interference? Maybe wrap my js code with a block or add more specific references? Is namespacing the issue? If so, how do I do that?
I don't really know what to try. I have spent 2 days on this and getting nowhere.
JS
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 player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'novideoid',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
$('.open-popup').click(function() {
event.target.playVideo();
});
$('.close-popup').click(function(e) {
player.stopVideo();
});
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if(event.data === 0) {
$('.close.close-popup').click();
}
}
function stopVideo() {
player.stopVideo();
}
$(function () {
onYouTubeIframeAPIReady();
$('#myModal').on('show.bs.modal', function(e) {
var videoId = $(e.relatedTarget).data('video-id');
var x = new String(videoId);
player.loadVideoById(x);
});
});

youtube play video on click doesnt work on ios devices

apple blocks auto play of videos. And I get it. But really isn't there something I can do to my code to make it play on of an outside div? I am having a hard time accepting the defeat on this one. Don't like the idea "It cant be done". My users are making a conscious choice when they are clicking that play button any way. So its not really a random auto play on page load. Here is my code. Works like charm on a desktop browser. But really would like to make it work on an ios device
callMain ();
function playerApp(inputVid) {
var player = new YT.Player('player', {
videoId: inputVid,
width:'100%',
height:'90%',
playerVars: {
rel: 0,
controls : 2,
enablejsapi : 1,
origin : document.domain,
wmode : "transparent"
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerReady(e) {
e.target.playVideo();
}
function onPlayerStateChange(e) {
//this removes the player once the video is done
if(e.data == 0){
backgroundOverlay(false);
$('.videoplayer-wrapper').remove();
}
}
function backgroundOverlay(state){
var pageHeight = $( document ).height();
var overlay = '';
var wrapperHTML = ''
+ '<div class="videoplayer-wrapper">'
+ '<div id="player" class="video-player"></div>'
+ '<div class="videoplayer-close videoplayer-toggle">Close</div>'
+ '</div>';
if(state){
overlay = $('body').append('<div class="video-overlay-background" style="height:'+pageHeight+'px;"></div>').append(wrapperHTML);
}else{
overlay = $('.video-overlay-background').remove();
}
return overlay;
}
function callMain () //$(function(){
{
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 inputVid = '';
$('.videoplayer-toggle').on('click', function(){
inputVid = $(this).attr('data-youtubeid');
backgroundOverlay(true);
playerApp(inputVid)
$('.videoplayer-close').on('click', function(){
backgroundOverlay(false);
$('.videoplayer-wrapper').remove()
})
});
}
Here is my jsfiddle
http://jsfiddle.net/sghoush1/zmqj6h1b/

Categories