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>
Related
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>
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);
});
});
I have this script that I need as a .js file, so I can use it multiple times without having multiple copies of the script everywhere, like calliing the script YTiFramePlayer.js. But, maybe I want to change promoVideo, or videoId. How would I do that?
//YouTube iframe player
//Load the iframe Player 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);
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('promoVideo', {
height: '390',
width: '640',
videoId: '00000',
events: {
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.STOPPED) {
stopVideo();
}
}
function startVideo() {
$("#video").css("height", "calc(100vh - 48px)");
player.playVideo();
}
function stopVideo() {
player.stopVideo();
$("#video").css("height", "0px");
}
You can save this as a .js file as mentioned previously, but wrap the functionality in a global variable that you can access elsewhere. Example:
script.js:
//YouTube iframe player
//Load the iframe Player API
var ytPlayer = (function() {
var tag = document.createElement('script');
var videoId;
var promoVideo = 'promoVideo';
var player;
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
return {
init(id, promoVideo) {
videoId = id;
promoVideo = promoVideo;
onYouTubeIframeAPIReady();
}
}
function onYouTubeIframeAPIReady() {
console.log('Setting up:', videoId, promoVideo)
player = new YT.Player(promoVideo, {
height: '390',
width: '640',
videoId: videoId,
events: {
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.STOPPED) {
stopVideo();
}
}
function startVideo() {
$("#video").css("height", "calc(100vh - 48px)");
player.playVideo();
}
function stopVideo() {
player.stopVideo();
$("#video").css("height", "0px");
}
})();
HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<h1>Hello Plunker!</h1>
<script>
ytPlayer.init('a8a83sda', 'someOtherId')
</script>
</body>
</html>
See the revealing module pattern for more information.
just save as with filename.js extension
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;
}
I'm building a website witch contains a 15 youtube embeds on 1 page, and I have a youtube-api javascript code witch plays them continuously, and changes a class that surrounds the embed. The problem is that the code occasionally doesn't fire, and this only happens online, not on my computer. I think this is because it sometimes takes too mutch time to load the 15 embeds with a slow connection, maybe the code fires when the players aren't ready, or too late? well... I dont know. I tried wrapping the whole code in a SetTimout() thing, but no luck. Anybody have a solution? Help will be greatly appreciated!
CODE (PARTIALLY):
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 player1;var player2;
function onYouTubeIframeAPIReady() {
player1 = new YT.Player('player1', {
events: {
'onStateChange': onPlayerStateChange1
}
});
player2 = new YT.Player('player2', {
events: {
'onStateChange': onPlayerStateChange2
}
});
---- etc... (untill player 15)
function onPlayerStateChange1(event) {
if (event.data == 0) {
player2.playVideo();
};
if (event.data == 1) {
document.getElementById("redlow1").setAttribute("class", "hero-unit77");
document.getElementById("redhigh1").setAttribute("class", "hero-unit88");
};
if (event.data == 2 || event.data == 0 || event.data == 5) {
document.getElementById("redlow1").setAttribute("class", "hero-unit7");
document.getElementById("redhigh1").setAttribute("class", "hero-unit8");
};
}
function onPlayerStateChange2(event) {
if (event.data == 0) {
player3.playVideo();
};
if (event.data == 1) {
document.getElementById("redlow2").setAttribute("class", "hero-unit77");
document.getElementById("redhigh2").setAttribute("class", "hero-unit88");
};
if (event.data == 2 || event.data == 0 || event.data == 5) {
document.getElementById("redlow2").setAttribute("class", "hero-unit7");
document.getElementById("redhigh2").setAttribute("class", "hero-unit8");
};
}
---- etc... (untill player 15)
One thing that may help you is to not use a different state change event listener for each player object. Instead, use a single listener for all of your players, as any state change event will also include a reference to the player that raised the event (in the 'target' attribute of the event object). First, in the 'events' parameter of each player creation function, bind the 'onReady' to a function that will load a reference to that player into a global object, and bind the 'onStateChange' all to the same function. Something like this (note: untested code written very late at night ... if you get errors you can't figure out, we'll work through them!):
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 player1, player2;
var players={};
function onYouTubeIframeAPIReady() {
player1 = new YT.Player('player1', {
events: {
'onReady': function() {
players.['player1']=player1;
}
'onStateChange': onPlayerStateChange
}
});
player2 = new YT.Player('player2', {
events: {
'onReady': function() {
players['player2']=player2;
}
'onStateChange': onPlayerStateChange;
}
});
---- etc... (untill player 15)
Then, you can define the state change listener function, and use the event.target for reference:
onPlayerStateChange = function(event) {
if (event.data == 0) {
players[event.target.a.id].playVideo();
};
if (event.data == 1) {
document.getElementById(event.target.a.id.replace("player","redlow")).setAttribute("class", "hero-unit77");
document.getElementById(event.target.a.id.replace("player","redhigh")).setAttribute("class", "hero-unit88");
};
if (event.data == 2 || event.data == 0 || event.data == 5) {
document.getElementById(event.target.a.id.replace("player","redlow")).setAttribute("class", "hero-unit7");
document.getElementById(event.target.a.id.replace("player","redhigh")).setAttribute("class", "hero-unit8");
};
};
Got it to work finnaly, the solution was using the onload function of the Yutube iframe like so:
<iframe onload="floaded1()" id="player1">
with this script:
function floaded1() {
player1 = new YT.Player('player1', {
events: {
'onStateChange': function (event) {
if (event.data == 0) {
player2.playVideo();
};
if (event.data == 1) {
document.getElementById("redlow1").setAttribute("class", "hero-unit77");
document.getElementById("redhigh1").setAttribute("class", "hero-unit88");
};
if (event.data == 2 || event.data == 0 || event.data == 5) {
document.getElementById("redlow1").setAttribute("class", "hero-unit7");
document.getElementById("redhigh1").setAttribute("class", "hero-unit8");
};
}
}
});
}