add click event on img with data from a json (twitch api) - javascript

the problem I have is that after creating a list with images that come from the preview of specific streams, I would like to be able to click on these images and replace the stream playing with the stream that comes from the image.
In the code below I get the streamers that play GTA V and do GTA RP. With this info, I get the nickname of the players to create the url with the preview image of their stream.
...
function streamData(auth){
$.ajax({
type: 'GET',
url: 'https://api.twitch.tv/helix/search/channels?
query=FailyV&live_only=true', /** gta V id = 32982 */
dataType:'json',
headers: {
"Client-ID": 'id',
"Authorization": "Bearer "+auth
},
success: function(streamList) {
for (let i = 0; i < streamList['data'].length; i++){
if(streamList['data'][i]['game_id'] == 32982){
gtaList.push(streamList['data'][i]['display_name'])
$('#twitch-embed-low').append('<img id="thumbnail'+i+' " src="https://static-cdn.jtvnw.net/previews-ttv/live_user_'+streamList['data'][i]['display_name']+'-300x200.jpg">')
.on("click", function(e){
console.log(gtaList[i])
})
}
}
new Twitch.Embed("twitch-embed-main", {
width: '80%',
height: '80%',
channel: gtaList[0],
autoplay: false,
layout: "video",
// only needed if your site is also embedded on embed.example.com and othersite.example.com
parent: ["embed.example.com", "othersite.example.com"]
});
let test = $('#twitch-embed-low')
console.log(test[0])
},
error: function(data){
info = document.getElementById("info")
info.innerHTML = "ERROR: " + data["responseJSON"]["message"] + ". Make sure your CID and corresponding secret are set in the JavaScript."
}
})
}
...
As you can see, I have tested this
...
$('#twitch-embed-low')
.append('<img id="thumbnail'+i+' " src="https://static-cdn.jtvnw.net/previews-
ttv/live_user_'+streamList['data'][i]['display_name']+'-300x200.jpg">')
.on("click", function(e){console.log(gtaList[i])})
...
but as you can imagine, it doesn't work (as I suspected) and each time I click on an image, it makes the complete loop and so it doesn't put the right stream in reading but systematically the last one and I'm not talking about the performances which are disastrous since it loads each stream 1 by 1.
So the idea is that when you click on the image, it puts the right stream in reading but I don't see how to do that.

There's a couple of improvments you can make. The first one is you are binding the click handler to the parent element and not to the img element, meaning it will be called (n) times (the length of streamList['data']). This is because you bind the click handler multiple times to the same html element. You want to bind this event handler just to the img element.
Secondly, when the event handler is called, it will be referencing the value of i which will always be the last value of i in the loop iteration. If you want to log the value of i at the point when you create the img element, you will need to use a closure to save the state of i, for example:
var img = $('<img id="thumbnail'+i+' " src="https://static-cdn.jtvnw.net/previews-
ttv/live_user_'+streamList['data'][i]['display_name']+'-300x200.jpg">')
.on("click", (function (index) {
return function (e) {
console.log(gtaList[index]);
};
})(i));
$('#twitch-embed-low').append(img)

Related

trigger functions no longer working after implement ajax scroll pagination

I have developed an audio platform similar to Soundcloud and it all works(ed) perfectly! Until I decided to create an Ajax scroll pagination.
Both the pagination and Ajax work fine. However, I have noticed some JavaScript that used to work before implementing Ajax, which no longer does.
The script has a pretty simple duty; play a track when the user clicks the play button (or pause when the user clicks on it again). Then, once the track has finished, move on to the next track until it finally reaches the end.
What happens now is, when the page first loads up (along with the 10 tracks that load with the page), the script will work as it is supposed to. But, when the user scrolls down to get more results to load, if the user clicks on one of the newly loaded tracks play button, the track either won't play, or it will play over the other track which is supposed to pause (and then all the buttons just completely stop working).
Here is all of the feed.js (removed as much bloat code as possible, and placed comments):
$(document).ready(function(){ // on page load
var tp = 1; // set track page equal to one
loadTracks(tp); // then load all of the tracks
jQuery(function($) {
$('.f-outer-container').on('scroll', function() { // when the user scrolls to the bottom of the page load more tracks
if($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
tp++;
loadTracks(tp);
}
});
});
function loadTracks(track_page){
$.post('/spectrum-rr/core/_func/functions/loadTrack.php', {'page': tp}, function(data){ // get send and get data from loadTrack.php
$("#f-ap__aj").append(data); // append those tracks in the feed
// player functions
$(".track-w__trigger").click(function(){ // when the play button is clicked
var tid = $(this).attr('aria-trackid'), // get its track id
tiW = 'w' + tid + 'w',
tiW = eval(tiW); // set the waveform object
playPauseButton(this, tiW, tid);
});
});
}
});
// player functionality
function playPauseButton(button, wave, trackID){ // once the function has been called
pausePrevious(button); // pause the previous track (this doesn't work when more ajax results are loaded)
var button = $(button);
if(wave.isPlaying()){ // if the wave is playing; stop it
button.removeClass("playing");
wave.pause();
} else { // vice versa
button.addClass("playing");
wave.play();
}
var waveDuration = wave.getDuration();
var nextTrack = ++trackID;
var checkAudioFinished = setInterval(function(){ // check if the audio has finished playing every second
if(wave.getCurrentTime() >= waveDuration){ // if it has
button.removeClass("playing"); // remove it's buttons "playing" class
$('#w' + nextTrack + 'w-trigger').trigger('click'); // play the next song on the playlist by incrementing the id
clearInterval(checkAudioFinished);
}
}, 1000);
}
function pausePrevious(b){
$(".playing").not(b).each(function(){ // when this function is triggered
$(".playing").trigger('click'); // pause all of the other tracks (by simulating the click of their pause buttons
$(".playing").removeClass("playing"); // remove it's class too
});
}
I feel these problems are occurring due to the use of $(document).ready();. Forcing these functions to only be available to those tracks that were already loaded. However, I am not sure.
Here is the HTML that gets sent back from each request (in 10s):
<div class="f-wave-send f-waveform-container">
<div aria-trackid="1" class="track-w__trigger" id="w1w-trigger"></div> <!-- the "1" is generated by PHP. it is incremented for every div !-->
<div class="f-waveform-outer-container">
<div aria-trackid="1" class="track-w__waveform" id="w1-w"></div>
<script>
var w1w = WaveSurfer.create({ // wavesurfer script (this is the "wave" object that is being triggered inside the playPauseButton() function !-->
container: '#w1-w',
barWidth: 2,
});
w1w.load('./player/audio/sampleaudio.mp3');
</script>
</div>
</div>
If anyone could give me insight as to what might be going on (or any tips in improving my code for that matter), it would be greatly appreciated!
Try this,
You should not bind an click event each time load happens and it should be moved out of your loadTracks, instead you shuold apply event delegation.
// player functions
$(document).on('click', '.track-w__trigger', function(){ // when the play button is clicked
var tid = $(this).attr('aria-trackid'), // get its track id
tiW = 'w' + tid + 'w',
tiW = eval(tiW); // set the waveform object
playPauseButton(this, tiW, tid);
});
Change your code to this
$(document).ready(function(){ // on page load
var tp = 1; // set track page equal to one
loadTracks(tp); // then load all of the tracks
jQuery(function($) {
$('.f-outer-container').on('scroll', function() { // when the user scrolls to the bottom of the page load more tracks
if($(this).scrollTop() + $(this).innerHeight() >= $(this)[0].scrollHeight) {
tp++;
loadTracks(tp);
}
});
});
function loadTracks(track_page){
$.post('/spectrum-rr/core/_func/functions/loadTrack.php', {'page': tp}, function(data){ // get send and get data from loadTrack.php
$("#f-ap__aj").append(data); // append those tracks in the feed
});
}
// player functions
$(".track-w__trigger").on("click",function(){ // when the play button is clicked
var tid = $(this).attr('aria-trackid'); // get its track id
tiW = 'w' + tid + 'w';
tiW = eval(tiW); // set the waveform object
playPauseButton(this, tiW, tid);
});
});
Just move out $(".track-w__trigger").click outside $.post and change it to $(".track-w__trigger").on("click",function()

Photoswipe pswp class Not Clearing After Closing Image

I have a Photoswipe (http://photoswipe.com) image gallery on my site, and the css class is not resetting/clearing to remove the view after I close a gallery for the second time.
ex.
User opens item 1, AJAX populates the figure(s) into the picture div.
User clicks an image from item 1 and Photoswipe opens the image properly (setting the following class):
class="pswp pswp--supports-fs pswp--open pswp--animate_opacity pswp--notouch pswp--css_animation pswp--svg pswp--animated-in pswp--visible"
User closes the image from item 1, class resets as normal:
class="pswp"
User closes item 1 and JS/JQuery clears all html in picture div. User opens item 2, AJAX populates the figure into the picture div. User clicks an image from item 2 and Photoswipe opens the image properly setting the same class as before.
class="pswp pswp--supports-fs pswp--open pswp--animate_opacity pswp--notouch pswp--css_animation pswp--svg pswp--animated-in pswp--visible"
This is where the problem occurs. User closes the image from item 2 and the only thing that changes is:
aria-hidden="true"
but the class does not clear, it remains:
class="pswp pswp--supports-fs pswp--open pswp--animate_opacity pswp--notouch pswp--css_animation pswp--svg pswp--animated-in pswp--visible"
when it should change to:
class="pswp"
This disables all interaction on the website since there is an invisible div/class on top of everything. The class needs to be changed back to pswp somehow.
AJAX/JS To Populate picture div (I added an id to the div):
if (i == 0) {
$('#listing_photos_container').append('<figure itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject"><img src="' + json[i].image_url + '" height="400" width="600" itemprop="thumbnail" alt="listingPhoto" class="listing-photo"></figure>');
} else {
$('#listing_photos_container').append('<figure itemprop="associatedMedia" itemscope itemtype="http://schema.org/ImageObject" class="listing-photo-holder"><img src="' + json[i].image_url + '" height="400" width="600" itemprop="thumbnail" alt="listingPhoto" class="listing-photo-holder"></figure>');
}
JS/JQuery to clear photo div:
$('#listing_photos_container').html('');
EDIT: The click listener function is running twice when a users clicks the photo to bring full screen. This is the code for the listener:
$.ajax({
type: "POST",
url: 'http://example.com/action?action=photos',
data: {id: id},
success: function (data) {
console.log('API Call - Photos');
json = JSON.parse(data);
$('#listing_photos_container').html('');
for (var i = 0; i < json.length; i++) {
// Styling code here
}
$('#list_header').html(
(function($) {
$('.picture').each( function() {
var $pic = $(this),
getItems = function() {
var items = [];
$pic.find('a').each(function() {
var $href = $(this).attr('href'),
$size = $(this).data('size').split('x'),
$width = $size[0],$height = $size[1];
var item = {
src : $href,
w : $width,
h : $height
}
items.push(item);
});
return items;
}
var items = getItems();
console.log('Items for PSWP' + items);
alert('Alert Point 1'); // This is called once, (as it should).
var $pswp = $('.pswp')[0];
$pic.on('click', 'figure', function(event) {
// This block is called twice..
alert('Click Funct');
event.preventDefault();
var $index = $(this).index();
var options = {
index: $index,
bgOpacity: 0.7,
showHideOpacity: true
}
// Initialize PhotoSwipe
alert('Setting new PhotoSwipe');
var lightBox = new PhotoSwipe($pswp, PhotoSwipeUI_Default, items, options);
lightBox.init();
}); // End $pic.on
});// End .picture each
})(jQuery)
); // End list_header.html
} // End AJAX Success
}); // End AJAX
You may have already fixed this, but in case someone else falls upon this.
This can happen if you trigger opening the gallery more than once without closing it. It may be that you have registered multiple click handlers to open the gallery or for some reason the event is being fired twice.
It happens because in the init function the current class name of the pswp element is retrieved and cached, then on destroy the class name is restored. When the second open occurs without destroy being called _initialClassName will be set to class="pswp pswp--supports-fs pswp--open pswp--animate_opacity pswp--notouch pswp--css_animation pswp--svg pswp--animated-in pswp--visible" as your are seeing
Line 776 of photoswipe.js where initialclass is set
_initalClassName = template.className;
Breakpoint this in your browser to see if it is called multiple times when opening
Line 942 onwards destroy function
destroy: function() {
_shout('destroy');
Breakpoint this in your browser to ensure it is being called for every time open is called
Final Solution
The problem is that when opening the popup and loading the images you are filling #listing_photos_container with your photos, then adding a click handler to open photoswipe. This click handler is added to the top element, so will remain when the popup is closed, then the next time it is opened a new click handler will be added.
To fix this you just need to unbind the click handler when closing the popup, you can do this with $(".picture").off('click'); somewhere inside your closeListing() function
It's quite simple, before every .click(...) you need to write .unbind('click').
Example:
$('a#open-photoswipe').unbind('click').click(function() {
// open photoswipe here
});
Was having similar problem - just define lightBox as global variable. And on destroy define it as null. And in beginning of function where You initialize lightBox just check if lighBox is already defined, then do return.

Can I generate IDs for table cells based on number of rows being returned to view?

Long story short. I'm attempting to implement a file upload feature in my C# MVC 3 application.
I have a fileuploader.js file which I beleive was taken was taken from http://github.com/valums/file-uploader (this is not my application originally).
I'd like to make the file upload feature available to reach row on the table in my view, number of rows will be based on the number of entries received from the database.
The problem I'm encountering at present is that the Upload btn is only appearing on the first row.
I'm certain this is because this feature begins with finding the div with an id of "file-uploader-attachment"
I tried changing the javascript to document.getElementsByClass and giving the div a class instead of an ID but it was unsuccessful.
So I need to either generate and assign ID's on the fly or of another way to id elements in each row and allow the javascript to find them.
I will put on more code as needed, but I don't want to fill the page with code.
If someone can point me in the right the direction or knows of a similar solution.
Again, I need a file upload btn/feature for each row in a table whose size is changeable.
cell in table
<td id="file-uploader-attachment"></td>
start of the createUploader() function in associated javascript code
var uploader = new qq.FileUploader({
element: document.getElementById('file-uploader-attachment'),
script in view:
<script type="text/javascript">
$(document).ready(function () {
// requests attachments from server, clears existing attachments and
// replaces with most up to date attachments
function LoadAttachments() {
$('.loading').show();
$.post('/CalibrationViewer/GetAttachments', { calibrationId: CAL_ID }, function (data) {
$('#attachment').empty();
$('#attachment').append(data);
$('.loading').hide();
});
}
function handleCheckbox() {
if ($(this).find(':checkbox').is(':checked')) {
//$(this).find('#file-uploader-attachment').html($('#myHTML').html());
createUploader();
$(this).find('file-uploader-attachment-Class').removeClass("upload-placeholder-unchecked");
$(".qq-upload-button").css("margin-top", "-6px");
$(".qq-upload-button").css("margin-bottom", "-20px");
}
else {
$(this).find('.file-uploader-attachment-Class').addClass("upload-placeholder-unchecked");
$(this).find('.file-uploader-attachment-Class').html($('#myHTML2').html());
}
}
$('tr').each(handleCheckbox);
$('tr').on('click', handleCheckbox);
function createUploader() {
$(".file-uploader-attachment-Class").each(function (element) {
var uploader = new qq.FileUploader({
element: element,
sizeLimit: 2147483647, // max size
action: '/Controller/Action',
allowedExtensions: ['xls', 'xlsx', 'pdf', 'doc', 'docx', 'csv', 'txt', 'rtf', 'zip', 'zipx', '7z'],
params: {
customer: CUST_NAME,
calibrationId: CAL_ID
},
multiple: false,
debug: false,
onComplete: function (id, fileName, responseJson) {
var resultMessage = document.getElementById('resultMessage');
alert(responseJson.msg);
$('#resultMessage').addClass('greenText');
resultMessage.innerHTML = responseJson.msg;
$('#attachment').prepend('<p><a class="attachment-file" href="' + responseJson.fileId + '">' + responseJson.fileName + '</a>');
$('#removeAttachment').prepend('<p><a class="attachment-delete" href="' + responseJson.fileId + '">X</a></p>');
$('#no-attachments').remove();
}
});
});
}
});
The reason is that you can have an id (file-uploader-attachment) only once in your html page (they should be unique). If they are not unique the first one is taken (if this is by standard or common sense, I do not know).
What you want to achive is something like this:
<td class="file-uploader-attachment"></td>
and
var elements = document.getElementsByClassName('file-uploader-attachment');
Array.prototype.filter.call(elements, function(element){
var uploader = new qq.FileUploader({
element: element, ...
});
or even simpler with jquery (if already in use):
$(".file-uploader-attachment).each(function (element) {
var uploader = new qq.FileUploader({
element: element, ...
});

jquery what the best way to reset every positioning to default?

I have a few thumbnails upon clicked, it will display all the works of the user, for example user 1 have 1 work, user 2 have 6 works. By right i will be showing the div accordingly to how many works the user have. If i click on user 1, it show 1 work, i exit and click on user 2, instead of 6 works it show 1 only, because the function takes on what was set on user1, as i didn't reset.. So i wonder if there is a best practices for resetting the show/hide div back to default upon exit, the only solution i know of now is calling the .show each time the function activates, but doesn't seem to be a good way to do things. Please advise
Example
$(function() {
$(".img_thumb_holder").on('click', function() {
var index = $(this).index(); // get current index of clicked thumbnail, so i can call out the related name from captionArray via same index
// what i did to solve the reseting, forcing it to show out each time the function runs
$(".portfolio_thumbImg_holder").show();
$.ajax({
type: "POST",
dataType: "json",
data: ({id: idArray[index]}), // pass the name of clicked holder into php to query
url: "CMS/PHP/retrieveAuthorWorks.php",
success: function(data) {
console.log("Array consist of " + data)
$('.portfolio_thumbImg_holder img').removeAttr("src");
linksArray=[];
titleArray=[];
descArray=[];
$(".full_image_desc .title").html(data[0].title);
$(".full_image_desc .info").html(data[0].desc);
$('.portfolio_thumbImg_holder img').each(function(index, element) {
if (data[index]) {
linksArray.push("http://localhost/testdatabase/cms/" + data[index].links);
$(element).attr("src", linksArray[index]);
titleArray.push(data[index].title);
$(element).attr("title", titleArray[index]);
descArray.push(data[index].desc);
$(element).attr("desc", descArray[index]);
}
});
// check how many divs without the image data, and hide it
$(".portfolio_thumbImg_holder img:not([src])").parent().hide();
}
});
});
});

YouTube iframe API: how do I control an iframe player that's already in the HTML?

I want to be able to control iframe based YouTube players. This players will be already in the HTML, but I want to control them via the JavaScript API.
I've been reading the documentation for the iframe API which explain how to add a new video to the page with the API, and then control it with the YouTube player functions:
var player;
function onYouTubePlayerAPIReady() {
player = new YT.Player('container', {
height: '390',
width: '640',
videoId: 'u1zgFlCw8Aw',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
That code creates a new player object and assigns it to 'player', then inserts it inside the #container div. Then I can operate on 'player' and call playVideo(), pauseVideo(), etc. on it.
But I want to be able to operate on iframe players which are already on the page.
I could do this very easily with the old embed method, with something like:
player = getElementById('whateverID');
player.playVideo();
But this doesn't work with the new iframes. How can I assign a iframe object already on the page and then use the API functions on it?
Fiddle Links: Source code - Preview - Small version
Update: This small function will only execute code in a single direction. If you want full support (eg event listeners / getters), have a look at Listening for Youtube Event in jQuery
As a result of a deep code analysis, I've created a function: function callPlayer requests a function call on any framed YouTube video. See the YouTube Api reference to get a full list of possible function calls. Read the comments at the source code for an explanation.
On 17 may 2012, the code size was doubled in order to take care of the player's ready state. If you need a compact function which does not deal with the player's ready state, see http://jsfiddle.net/8R5y6/.
/**
* #author Rob W <gwnRob#gmail.com>
* #website https://stackoverflow.com/a/7513356/938089
* #version 20190409
* #description Executes function on a framed YouTube video (see website link)
* For a full list of possible functions, see:
* https://developers.google.com/youtube/js_api_reference
* #param String frame_id The id of (the div containing) the frame
* #param String func Desired function to call, eg. "playVideo"
* (Function) Function to call when the player is ready.
* #param Array args (optional) List of arguments to pass to function func*/
function callPlayer(frame_id, func, args) {
if (window.jQuery && frame_id instanceof jQuery) frame_id = frame_id.get(0).id;
var iframe = document.getElementById(frame_id);
if (iframe && iframe.tagName.toUpperCase() != 'IFRAME') {
iframe = iframe.getElementsByTagName('iframe')[0];
}
// When the player is not ready yet, add the event to a queue
// Each frame_id is associated with an own queue.
// Each queue has three possible states:
// undefined = uninitialised / array = queue / .ready=true = ready
if (!callPlayer.queue) callPlayer.queue = {};
var queue = callPlayer.queue[frame_id],
domReady = document.readyState == 'complete';
if (domReady && !iframe) {
// DOM is ready and iframe does not exist. Log a message
window.console && console.log('callPlayer: Frame not found; id=' + frame_id);
if (queue) clearInterval(queue.poller);
} else if (func === 'listening') {
// Sending the "listener" message to the frame, to request status updates
if (iframe && iframe.contentWindow) {
func = '{"event":"listening","id":' + JSON.stringify(''+frame_id) + '}';
iframe.contentWindow.postMessage(func, '*');
}
} else if ((!queue || !queue.ready) && (
!domReady ||
iframe && !iframe.contentWindow ||
typeof func === 'function')) {
if (!queue) queue = callPlayer.queue[frame_id] = [];
queue.push([func, args]);
if (!('poller' in queue)) {
// keep polling until the document and frame is ready
queue.poller = setInterval(function() {
callPlayer(frame_id, 'listening');
}, 250);
// Add a global "message" event listener, to catch status updates:
messageEvent(1, function runOnceReady(e) {
if (!iframe) {
iframe = document.getElementById(frame_id);
if (!iframe) return;
if (iframe.tagName.toUpperCase() != 'IFRAME') {
iframe = iframe.getElementsByTagName('iframe')[0];
if (!iframe) return;
}
}
if (e.source === iframe.contentWindow) {
// Assume that the player is ready if we receive a
// message from the iframe
clearInterval(queue.poller);
queue.ready = true;
messageEvent(0, runOnceReady);
// .. and release the queue:
while (tmp = queue.shift()) {
callPlayer(frame_id, tmp[0], tmp[1]);
}
}
}, false);
}
} else if (iframe && iframe.contentWindow) {
// When a function is supplied, just call it (like "onYouTubePlayerReady")
if (func.call) return func();
// Frame exists, send message
iframe.contentWindow.postMessage(JSON.stringify({
"event": "command",
"func": func,
"args": args || [],
"id": frame_id
}), "*");
}
/* IE8 does not support addEventListener... */
function messageEvent(add, listener) {
var w3 = add ? window.addEventListener : window.removeEventListener;
w3 ?
w3('message', listener, !1)
:
(add ? window.attachEvent : window.detachEvent)('onmessage', listener);
}
}
Usage:
callPlayer("whateverID", function() {
// This function runs once the player is ready ("onYouTubePlayerReady")
callPlayer("whateverID", "playVideo");
});
// When the player is not ready yet, the function will be queued.
// When the iframe cannot be found, a message is logged in the console.
callPlayer("whateverID", "playVideo");
Possible questions (& answers):
Q: It doesn't work!
A: "Doesn't work" is not a clear description. Do you get any error messages? Please show the relevant code.
Q: playVideo does not play the video.
A: Playback requires user interaction, and the presence of allow="autoplay" on the iframe. See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes and https://developer.mozilla.org/en-US/docs/Web/Media/Autoplay_guide
Q: I have embedded a YouTube video using <iframe src="http://www.youtube.com/embed/As2rZGPGKDY" />but the function doesn't execute any function!
A: You have to add ?enablejsapi=1 at the end of your URL: /embed/vid_id?enablejsapi=1.
Q: I get error message "An invalid or illegal string was specified". Why?
A: The API doesn't function properly at a local host (file://). Host your (test) page online, or use JSFiddle. Examples: See the links at the top of this answer.
Q: How did you know this?
A: I have spent some time to manually interpret the API's source. I concluded that I had to use the postMessage method. To know which arguments to pass, I created a Chrome extension which intercepts messages. The source code for the extension can be downloaded here.
Q: What browsers are supported?
A: Every browser which supports JSON and postMessage.
IE 8+
Firefox 3.6+ (actually 3.5, but document.readyState was implemented in 3.6)
Opera 10.50+
Safari 4+
Chrome 3+
Related answer / implementation: Fade-in a framed video using jQuery
Full API support: Listening for Youtube Event in jQuery
Official API: https://developers.google.com/youtube/iframe_api_reference
Revision history
17 may 2012
Implemented onYouTubePlayerReady: callPlayer('frame_id', function() { ... }).
Functions are automatically queued when the player is not ready yet.
24 july 2012
Updated and successully tested in the supported browsers (look ahead).
10 october 2013
When a function is passed as an argument, callPlayer forces a check of readiness. This is needed, because when callPlayer is called right after the insertion of the iframe while the document is ready, it can't know for sure that the iframe is fully ready. In Internet Explorer and Firefox, this scenario resulted in a too early invocation of postMessage, which was ignored.
12 Dec 2013, recommended to add &origin=* in the URL.
2 Mar 2014, retracted recommendation to remove &origin=* to the URL.
9 april 2019, fix bug that resulted in infinite recursion when YouTube loads before the page was ready. Add note about autoplay.
Looks like YouTube has updated their JS API so this is available by default! You can use an existing YouTube iframe's ID...
<iframe id="player" src="http://www.youtube.com/embed/M7lc1UVf-VE?enablejsapi=1&origin=http://example.com" frameborder="0"></iframe>
...in your JS...
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
events: {
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerStateChange() {
//...
}
...and the constructor will use your existing iframe instead of replacing it with a new one. This also means you don't have to specify the videoId to the constructor.
See Loading a video player
You can do this with far less code:
function callPlayer(func, args) {
var i = 0,
iframes = document.getElementsByTagName('iframe'),
src = '';
for (i = 0; i < iframes.length; i += 1) {
src = iframes[i].getAttribute('src');
if (src && src.indexOf('youtube.com/embed') !== -1) {
iframes[i].contentWindow.postMessage(JSON.stringify({
'event': 'command',
'func': func,
'args': args || []
}), '*');
}
}
}
Working example:
http://jsfiddle.net/kmturley/g6P5H/296/
My own version of Kim T's code above which combines with some jQuery and allows for targeting of specific iframes.
$(function() {
callPlayer($('#iframe')[0], 'unMute');
});
function callPlayer(iframe, func, args) {
if ( iframe.src.indexOf('youtube.com/embed') !== -1) {
iframe.contentWindow.postMessage( JSON.stringify({
'event': 'command',
'func': func,
'args': args || []
} ), '*');
}
}
Thank you Rob W for your answer.
I have been using this within a Cordova application to avoid having to load the API and so that I can easily control iframes which are loaded dynamically.
I always wanted the ability to be able to extract information from the iframe, such as the state (getPlayerState) and the time (getCurrentTime).
Rob W helped highlight how the API works using postMessage, but of course this only sends information in one direction, from our web page into the iframe. Accessing the getters requires us to listen for messages posted back to us from the iframe.
It took me some time to figure out how to tweak Rob W's answer to activate and listen to the messages returned by the iframe. I basically searched through the source code within the YouTube iframe until I found the code responsible for sending and receiving messages.
The key was changing the 'event' to 'listening', this basically gave access to all the methods which were designed to return values.
Below is my solution, please note that I have switched to 'listening' only when getters are requested, you can tweak the condition to include extra methods.
Note further that you can view all messages sent from the iframe by adding a console.log(e) to the window.onmessage. You will notice that once listening is activated you will receive constant updates which include the current time of the video. Calling getters such as getPlayerState will activate these constant updates but will only send a message involving the video state when the state has changed.
function callPlayer(iframe, func, args) {
iframe=document.getElementById(iframe);
var event = "command";
if(func.indexOf('get')>-1){
event = "listening";
}
if ( iframe&&iframe.src.indexOf('youtube.com/embed') !== -1) {
iframe.contentWindow.postMessage( JSON.stringify({
'event': event,
'func': func,
'args': args || []
}), '*');
}
}
window.onmessage = function(e){
var data = JSON.parse(e.data);
data = data.info;
if(data.currentTime){
console.log("The current time is "+data.currentTime);
}
if(data.playerState){
console.log("The player state is "+data.playerState);
}
}
I was having issues with the above examples so instead, I just inserted the iframe on click in with JS with autoplay in the source and it works fine for me. I also had the possibility for Vimeo or YouTube so I needed to be able to handle that.
This solution isn't amazing and could be cleaned up but this worked for me. I also don't like jQuery but the project was already using it and I was just refactoring existing code, feel free to clean up or convert to vanilla JS :)
<!-- HTML -->
<div class="iframe" data-player="viemo" data-src="$PageComponentVideo.VideoId"></div>
<!-- jQuery -->
$(".btnVideoPlay").on("click", function (e) {
var iframe = $(this).parents(".video-play").siblings(".iframe");
iframe.show();
if (iframe.data("player") === "youtube") {
autoPlayVideo(iframe, iframe.data("src"), "100%", "100%");
} else {
autoPlayVideo(iframe, iframe.data("src"), "100%", "100%", true);
}
});
function autoPlayVideo(iframe, vcode, width, height, isVimeo) {
if (isVimeo) {
iframe.html(
'<iframe width="' +
width +
'" height="' +
height +
'" src="https://player.vimeo.com/video/' +
vcode +
'?color=ff9933&portrait=0&autoplay=1" frameborder="0" allowfullscreen wmode="Opaque"></iframe>'
);
} else {
iframe.html(
'<iframe width="' +
width +
'" height="' +
height +
'" src="https://www.youtube.com/embed/' +
vcode +
'?autoplay=1&loop=1&rel=0&wmode=transparent" frameborder="0" allowfullscreen wmode="Opaque"></iframe>'
);
}
}
One quick solution, if requests are not an issue, and you're wanting this behavior for something like a show/hide video, is to remove/add the iframe, or cleaning and filling the src.
const stopPlayerHack = (iframe) => {
let src = iframe.getAttribute('src');
iframe.setAttribute('src', '');
iframe.setAttribute('src', src);
}
The iframe will be removed, stop to play and will be loaded right after that. In my case I've improved the code to only set the src again on lightbox open, so the load will only happen if user demands to see the video.

Categories

Resources