YouTube API Target (multiple) existing iframe(s) - javascript

I'm trying to understand how to target an existing iframe using the YouTube API (i.e. without constructing an iframe with the script).
As usual, Google does not give enough API examples, but explains that it IS possible, here http://code.google.com/apis/youtube/iframe_api_reference.html
Here is an example of what I'm trying to do - the video underneath the thumbnail should play. I am almost there, but only the first video plays...
http://jsfiddle.net/SparrwHawk/KtbYR/2/

TL;DR: DEMO: http://jsfiddle.net/KtbYR/5/
YT_ready, getFrameID and onYouTubePlayerAPIReady are functions as defined in this answer. Both methods can be implemented without any preloaded library. In my previous answer, I showed a method to implement the feature for a single frame.
In this answer, I focus on multiple frames.
HTML example code (important tags and attributes are capitalized, <iframe src id>):
<div>
<img class='thumb' src='http://i2.cdnds.net/11/34/odd_alan_partridge_bio_cover.jpg'>
<IFRAME ID="frame1" SRC="http://www.youtube.com/embed/u1zgFlCw8Aw?enablejsapi=1" width="640" height="390" frameborder="0"></IFRAME>
</div>
<div>
<img class='thumb' src='http://i2.cdnds.net/11/34/odd_alan_partridge_bio_cover.jpg'>
<IFRAME ID="frame2" SRC="http://www.youtube.com/embed/u1zgFlCw8Aw?enablejsapi=1" width="640" height="390" frameborder="0"></IFRAME>
</div>
JavaScript code (YT_ready, getFrameID, onYouTubePlayerAPIReady and the YouTube Frame API script loader are defined here)
var players = {}; //Define a player storage object, to expose methods,
// without having to create a new class instance again.
YT_ready(function() {
$(".thumb + iframe[id]").each(function() {
var identifier = this.id;
var frameID = getFrameID(identifier);
if (frameID) { //If the frame exists
players[frameID] = new YT.Player(frameID, {
events: {
"onReady": createYTEvent(frameID, identifier)
}
});
}
});
});
// Returns a function to enable multiple events
function createYTEvent(frameID, identifier) {
return function (event) {
var player = players[frameID]; // Set player reference
var the_div = $('#'+identifier).parent();
the_div.children('.thumb').click(function() {
var $this = $(this);
$this.fadeOut().next().addClass('play');
if ($this.next().hasClass('play')) {
player.playVideo();
}
});
}
}
In my previous answer, I bound the onStateChange event. In this example, I used the onReady event, because you want to call the functions only once, at initialization.
This example works as follows:
The following methods are defined in this answer.
The YouTube Frame API is loaded from http://youtube.com/player_api.
When this external script has finished loading, onYoutubePlayerAPIReady is called, which in his turn activates all functions as defined using YT_ready
The declaration of the following methods are shown here, but implemented using this answer. Explanation based on the example:
Loops through each <iframe id> object, which is placed right after <.. class="thumb">.
At each frame element, the id is retrieved, and stored in the identifier variable.
The internal ID of the iframe is retrieved through getFrameID. This ensures that the <iframe> is properly formatted for the API. In this example code, the returned ID is equal to identifier, because I have already attached an ID to the <iframe>.
When the <iframe> exists, and a valid YouTube video, a new player instance is created, and the reference is stored in the players object, accessible by key frameID.
At the creation of the player instance, a **onReady* event is defined. This method will be invoked when the API is fully initialized for the frame.
createYTEvent
This method returns a dynamically created function, which adds functionality for separate players. The most relevant parts of the code are:
function createYTEvent(frameID, identifier) {
return function (event) {
var player = players[frameID]; // Set player reference
...
player.playVideo();
}
}
frameID is the ID of the frame, used to enable the YouTube Frame API.
identifier is the ID as defined in YT_ready, not necessarily an <iframe> element. getFrameID will attempt to find the closest matching frame for a given id. That is, it returns the ID of a given <iframe> element, or: If the given element is not an <iframe>, the function looks for a child which is a <iframe>, and returns the ID of this frame. If the frame does not exists, the function will postfix the given ID by -frame.
players[playerID]` refers to the initialized player instance.
Make sure that you also check this answer, because the core functionality of this answer is based on that.
Other YouTube Frame API answers. In these answers, I showed various implementations of the YouTube Frame/JavaScript API.

Related

Trouble updating API object once instantiated in Javascript

I'm instantiating a new Vimeo object when clicked. This allows me to use the event target to grab the videoUrl depending on which element is clicked. Then the vimeo api automagically creates an iframe with the video embed. The problem is that once the Vimeo player is created, I can't destroy it and recreate it with another videoUrl. It stays stuck on the first element I clicked. If I refresh and click another element, it works with the new videoUrl, so it means that it works on any element I choose but only the first click. I'm guessing this is a JS issue that I'm not familiar with. I'm used to C++ where we can use pointers to address this kind of thing. I would appreciate any suggestions.
function openModal(e) {
var modal = document.getElementById('Modal');
var videoUrl = e.target.dataset.videoLink;
//JS Player Code
alert(e.target.dataset.videoLink);
var options = {
url: videoUrl,
width: 640,
loop: false
};
var player = new Vimeo.Player('Modal', options);
modal.style.display = "block";
//Doesn't seem to do anything
delete player;
}
Unlike C++, JavaScript has a garbage collector in which you don't have to manage your memory. The delete operator in JS is completely different than the one you have in C++. Instead of pointers, JS uses "references" which you can't destroy manually.
Now, I don't quite understand what you expect to happen when you do delete player. But I assume you want to remove the iframe (which doesn't really make any sense.)
But anyway, you have two choices. You either
Hide it with CSS via display: none
Replace the element with a new one.
By the way, the delete operator in JS is equivalent to the std::map::erase method in C++.

how to share audio element among several pages in javascript

I have a website with an index page which only:
contains an iFrame
launches an audio player and starting to play tracks.
The different links are open in the iFrame, so I hide everything under index.html.
I create the audio player via the line:
var audio = document.createElement('audio");
I am confused with the scoping. I want to access this audio variable from other documents which will open in the iFrame.
Is there a way to store this audio element so any page can access it? Or is there a function allowing to retrieve it by type, like getElement('audio') ?
My only goal is start playing music on one page, and be able to control it from another (to be able to call audio.play() and audio.pause() from another.
You can do this by accessing the parent document context and getting the audio element from there.
In the parent
// Method 1
var audio = document.createElement('audio')
audio.id = 'my-specific-audio'
document.body.appendChild(audio)
// Method 2
window['my-audio-key'] = audio
In the child
// Method 1
var audio = window.parent.document.getElementById('my-specific-audio')
// Method 2
var audio = window.parent['my-audio-key']
One caveat: both frames must be of the same origin (domain). The browser does not allow cross-origin context access.
If you are using frames and you would like to control a single element, then you can declare the element and access it using parent. object. So, you have the:
var audio = document.createElement('audio");
Above code in the index.htm and in each of the page, you can use:
parent.audio.play();
parent.audio.pause();
Note: This works only if both the frames are from the same domain.

Froogaloop Vimeo API -- Can't call API methods outside of Ready Event

I've scoured the docs in SO and Vimeo and can't seem to figure out how to call an API method outside of the Ready Event in Vimeo. I have created my Vimeo player, embedding it into teacher.js:
$(".video-player").prepend('<iframe id="player" src="//player.vimeo.com/video/'+video_id+'?api=1&player_id=player&badge=0&title=0&byline=0&portrait=0" width="100%" height="100%" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
player = $('iframe#player')[0];
vimeoSetup(player);
Which then calls vimeoSetup in a different script:
function vimeoSetup (player) {
$f(player).addEvent('ready', ready);
function addEvent(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
}
else {
element.attachEvent('on'+eventName, callback);
}
}
function ready(player_id) {
// Keep a reference to Froogaloop for this player
container = document.getElementById(player_id).parentNode.parentNode;
froogaloop = $f(player_id);
var buttons = container.querySelector('div#video-player-controls-holder-teacher div'),
playBtn = document.querySelector('#play-pause');
//play-pause -- I wanted to have a single play-pause button, which is working fine
addEvent(playBtn, 'click', function() {
froogaloop.api('paused', function(paused) {
if(paused) {
froogaloop.api('play');
} else {
froogaloop.api('pause');
}
});
});
...
Now, if I want to say call $f(player).api('pause'); in teacher.js, I get this error: Unable to post message to http://player.vimeo.com. Recipient has origin mydomain. It seems like such a simple problem--I'm not sure if it involves usage of 'this' that is currently beyond me, or if I'm grabbing the Vimeo player embed incorrectly--I did get a lot of "no method .api for this object" in experimenting.
The end goal is that I can create a vimeo player, provide controls (both of these are good), and then use the API to call methods that feed into backbone, including pause, play, and time.
Are there other events, aside from 'click' and user-generated events I can use? Like an event that says another function was called? Seems circuitous...My backbone view looks like this:
pause: function () {
this.player.pauseVideo(); //this is for a YouTube API which works great
//I want to be able to similarly call something like froogaloop.api('pause');
},
Thanks so much--StackOverflow has taught me an amazing amount.
So, if 'ready' event is working, then everything must be setup right. But here is one possible problem.
In:
player = $('iframe#player')[0];
vimeoSetup(player);
You are getting an Object by id 'player' and then, in vimeoSetup(player); passing an Object into $f(player) which is working.
Yet, further down the code in ready function you are passing in to $f(player_id) just an id, not an Object, therefore api calls not working.
You just need to get Object again by player_id and pass it into $f(), or save player = $('iframe#player')[0]; as global var and then call all API methods on it (though can be not a good option, if You want to make something like few dinamicly spawning players each one with own controls, or if You just one of those, who scared of global variables)

mediaElementjs: how to get instance of the player

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

Pass value to iframe from a window

I need to send a value to an iframe.
The iframe is present within the current window. How can I achieve this?
I need to do it with javascript in the parent window that contains the iframe.
First, you need to understand that you have two documents: The frame and the container (which contains the frame).
The main obstacle with manipulating the frame from the container is that the frame loads asynchronously. You can't simply access it any time, you must know when it has finished loading. So you need a trick. The usual solution is to use window.parent in the frame to get "up" (into the document which contains the iframe tag).
Now you can call any method in the container document. This method can manipulate the frame (for example call some JavaScript in the frame with the parameters you need). To know when to call the method, you have two options:
Call it from body.onload of the frame.
Put a script element as the last thing into the HTML content of the frame where you call the method of the container (left as an exercise for the reader).
So the frame looks like this:
<script>
function init() { window.parent.setUpFrame(); return true; }
function yourMethod(arg) { ... }
</script>
<body onload="init();">...</body>
And the container like this:
<script>
function setUpFrame() {
var frame = window.frames['frame-id'].contentWindow;
frame.yourMethod('hello');
}
</script>
<body><iframe name="frame-id" src="..."></iframe></body>
Depends on your specific situation, but if the iframe can be deployed after the rest of the page's loading, you can simply use a query string, a la:
<iframe src="some_page.html?somedata=5&more=bacon"></iframe>
And then somewhere in some_page.html:
<script>
var params = location.href.split('?')[1].split('&');
data = {};
for (x in params)
{
data[params[x].split('=')[0]] = params[x].split('=')[1];
}
</script>
Here's another solution, usable if the frames are from different domains.
var frame = /*the iframe DOM object*/;
frame.contentWindow.postMessage({call:'sendValue', value: /*value*/}, /*frame domain url or '*'*/);
And in the frame itself:
window.addEventListener('message', function(event) {
var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object.
if (origin !== /*the container's domain url*/)
return;
if (typeof event.data == 'object' && event.data.call=='sendValue') {
// Do something with event.data.value;
}
}, false);
Don't know which browsers support this, though.
Use the frames collection.
From the link:
var frames = window.frames; // or // var frames = window.parent.frames;
for (var i = 0; i < frames.length; i++) {
// do something with each subframe as frames[i]
frames[i].document.body.style.background = "red";
}
If the iframe has a name you may also do the following:
window.frames['ponies'].number_of_ponies = 7;
You can only do this if the two pages are served from the same domain.
Two more options, which are not the most elegant but probably easier to understand and implement, especially in case the data that the iframe needs from its parent is just a few vars, not complex objects:
Using the URL Fragment Identifier (#)
In the container:
<iframe name="frame-id" src="http://url_to_iframe#dataToFrame"></iframe>
In the iFrame:
<script>
var dataFromDocument = location.hash.replace(/#/, "");
alert(dataFromDocument); //alerts "dataToFrame"
</script>
Use the iFrame's name
(I don't like this solution - it's abusing the name attribute, but it's an option so I'm mentioning it for the record)
In the container:
<iframe name="dataToFrame" src="http://url_to_iframe"></iframe>
In the iFrame:
<script type="text/javascript">
alert(window.name); // alerts "dataToFrame"
</script>
We can use "postMessage" concept for sending data to an underlying iframe from main window.
you can checkout more about postMessage using this link
add the below code inside main window page
// main window code
window.frames['myFrame'].contentWindow.postMessage("Hello World!");
we will pass "Hello World!" message to an iframe contentWindow with iframe id="myFrame".
now add the below code inside iframe source code
// iframe document code
function receive(event) {
console.log("Received Message : " + event.data);
}
window.addEventListener('message', receive);
in iframe webpage we will attach an event listener to receive event and in 'receive' callback we will print the data to console
Incase you're using angular and an iframe inside, you'll need need to listen to the iframe to finish loading. You can do something like this:
document.getElementsByTagName("iframe")[0].addEventListener('load', () => {
document.getElementsByTagName("iframe")[0].contentWindow.postMessage(
{
call: 'sendValue',
value: 'data'
},
window.location.origin)
})
You will have to get the iframe one way or another (there are better ways to do it in angular) and then wait for it to load. Or else the listener won't be attached to it even if you do it inside lifecycle methods like ngAfterViewInit()
Have a look at the link below, which suggests it is possible to alter the contents of an iFrame within your page with Javascript, although you are most likely to run into a few cross browser issues. If you can do this you can use the javascript in your page to add hidden dom elements to the iFrame containing your values, which the iFrame can read.
Accessing the document inside an iFrame
Just another way.
From iframe you can add event listeners and dispatch events into parent document:
parent.document.addEventListener('iframe-event', (e) => {
console.log('iframe-event', e.detail);
});
setTimeout(() => {
console.log('dispatchEvent from iframe');
const event = new CustomEvent('frame-ready', { detail: 'parent event dispatched from iframe' });
parent.document.dispatchEvent(event);
}, 1000);
And from parent you can add event listeners and dispatch events in its own document:
document.addEventListener('frame-ready', (e) => {
const event = new CustomEvent('iframe-event', { detail: 'iframe event dispatched from parent' });
document.dispatchEvent(event);
});
Also if in any case you need to open this frame in a new tab you can still use events for communication.
From your frame/tab:
if (opener) {
const event = new CustomEvent('frame-ready', { detail: 'parent event dispatched from new tab' });
opener.document.dispatchEvent(event);
}
From your parent/opener:
window.open('my-frame.html', '_blank');
document.addEventListener('frame-ready', (e) => {
const event = new CustomEvent('iframe-event', { detail: 'iframe event dispatched from parent' });
document.dispatchEvent(event);
});
Just be aware that window.open will expose your DOM to next page it opens, so if you use it to open any third part url, you must always use rel=noopener to avoid security issues:
window.open('third-part-url.html', '_blank', 'noopener');
In your main homepage, add this line-
window.postMessage("Hello data from Homepage");
Inside your iframe , add this line-
window.addEventListener("message", receiveDataFromWeb);
const receiveDataFromWeb= (data)=> {
console.log(data);
//this should print Hello data from Homepage
}
What you have to do is to append the values as parameters in the iframe src (URL).
E.g. <iframe src="some_page.php?somedata=5&more=bacon"></iframe>
And then in some_page.php file you use php $_GET['somedata'] to retrieve it from the iframe URL. NB: Iframes run as a separate browser window in your file.

Categories