Before you answer, I know about the load event.
I need to check if an iframe has loaded or not. With images I can do it easy with the HTMLImageElement.complete property, but the iframe doesn't seem to have that... so... does it have something else one can use?
How can you check if an <iframe> has completed loading?
Reason: I'm currently looping through images in a setInterval loop to check if they all have loaded, and when they have, I fade them in one by one. Looks very nice. And I'd like to add iframes to that fanciness, but can't figure out how to check if the iframes have loaded... 😕
If all your use cases are for youtube players than you can use youtube's iframe player api to get onReady events that let you know when the player is ready to be used.
After making sure the iframe api has loaded, onYoutubePlayerAPIReady() is called when it is, you can call YT.Player() to create or operate on an iframe that links to a youtube video
function onPlayerReady(){
//do work
}
player = new YT.Player('container', {
height: '390',
width: '640',
videoId: 'Video's Youtube Id',
events: {
'onReady': onPlayerReady
}
});
The onReady property will let the api know which callback to use.
Now you do not have to load/use the iframe api to get these to work. You just have to implement the correct postMessage calls and message event callbacks yourself.
To do this you first have to make sure the url that is being used has enablejsapi=1 as a parameter. It tells youtube to load the necessary api libraries within the iframe. for instance:
https://www.youtube.com/embed/VgC4b9K-gYU
would become
https://www.youtube.com/embed/VgC4b9K-gYU?enablejsapi=1
You then have to send a listening and an addEventListener postMessage to the iframe's window. This tells the api that there is something wanting to listen to events and which event you are wanting to listen for.
var frame = document.querySelector('iframe');
var listenEvent = {"event":"listening","id":1,"channel":"test"};
frame.contentWindow.postMessage(JSON.stringify(listenEvent),'*');
var listenerEvent = {
"event":"command",
"func":"addEventListener",
"args":["onReady"],
"id":1,
"channel":"test"
};
frame.contentWindow.postMessage(JSON.stringify(listenerEvent),'*');
Then you just need to add an message event handler to handle any incoming messages.
window.addEventListener('message',function(data,origin){
data = JSON.parse(data);
if(data.event == 'onReady'){
//do work
}
});
Now if all your cases do not involve youtube players then you may be out of luck, as cross-origin rules do not let you access the dom/events of the iframe content. The source would have to implement something similar to the postMessage / message system like youtube does above.
Demo
window.addEventListener('message', function(event, origin) {
var data = JSON.parse(event.data);
if (data.event == 'onReady') {
onReady();
}
});
function onReady() {
console.log("Player ready");
}
var frame = null;
window.addEventListener('load', function() {
frame = document.querySelector('iframe');
var command = {
"event": "listening",
"id": 1,
"channel": "test"
};
frame.contentWindow.postMessage(JSON.stringify(command), '*');
command = {
"event": "command",
"func": "addEventListener",
"args": ["onReady"],
"id": 1,
"channel": "test"
};
frame.contentWindow.postMessage(JSON.stringify(command), '*');
});
<iframe src="https://www.youtube.com/embed/VgC4b9K-gYU?enablejsapi=1"></iframe>
Please try below:
<iframe id="my-iframe"></iframe>
<script>
var iframe_document = document.querySelector('#my-iframe').contentDocument;
if (iframe_document.readyState !== 'loading') onLoadingCompleted();
else iframe_document.addEventListener('DOMContentLoaded', onLoadingCompleted);
function onLoadingCompleted(){
console.log('iFrame loaded!');
}
</script>
Related
I couldn't find any way to listen to time changes (so I can show a current time/duration of the video in my UI) on the YouTube IFrame Player API documentation. Is there a way to do this without polling getCurrentTime()?
The YouTube IFrame Player API doesn't expose any way to listen for time change updates, but since internally it uses postMessage events to communicate between the IFrame and the main window, you can add a listener to your window to listen to them and react only to the time change ones:
// Instantiate the Player.
function onYouTubeIframeAPIReady() {
var player = new YT.Player("player", {
height: "390",
width: "640",
videoId: "dQw4w9WgXcQ"
});
// This is the source "window" that will emit the events.
var iframeWindow = player.getIframe().contentWindow;
// So we can compare against new updates.
var lastTimeUpdate = 0;
// Listen to events triggered by postMessage.
window.addEventListener("message", function(event) {
// Check that the event was sent from the YouTube IFrame.
if (event.source === iframeWindow) {
var data = JSON.parse(event.data);
// The "infoDelivery" event is used by YT to transmit any
// kind of information change in the player,
// such as the current time or a playback quality change.
if (
data.event === "infoDelivery" &&
data.info &&
data.info.currentTime
) {
// currentTime is emitted very frequently,
// but we only care about whole second changes.
var time = Math.floor(data.info.currentTime);
if (time !== lastTimeUpdate) {
lastTimeUpdate = time;
console.log(time); // update the dom, emit an event, whatever.
}
}
}
});
}
See it live.
Note that this relies on a private API that may change at anytime without previous notice.
I use object tag to embed a youtube video.
<object data="http://www.youtube.com/embed/QwievZ1Tx-8" width="100%" height="315"></object>
I would like to trigger an Ajax request (So the server can update the database's view count on the video) everytime the video play.
Anybody know how to do it ?,
NOTE : Let say the user click 'play' on the video, AJAX REQUEST CALLED. But when they pause and resume the video, AJAX REQUEST DID NOT CALLED.
you can do it in script:
$("object")[0].onplay = function () {
//AJAX REQUEST CALLED
};
OR:
<object... onplay="myFunction()">
and in the script
myFunction(){
//AJAX REQUEST CALLED
}
It happend only when the video start to play...
You can use youtube iframe api and the callback onPlayerStateChange which calling with the event object which have the exact event type for it. Also, keep a flag if the video is already started and call the ajax only if it didn't.
Like this:
var isStarted = false;
function onPlayerStateChange(event) {
if (event.data == 1 && !isStarted) {
// call ajax
isStarted = true;
}
}
I posted this question to the PhantomJS mailing list a week ago, but have gotten no response. Hoping for better luck here...
I've been trying to use PhantomJS to scrape information from YouTube, but haven't been able to get it working.
Consider a YouTube video embedded into a web page via an iframe element. If you load the URL referenced by the src attribute directly into a browser, you get a full-page version of the video, where the video is encapsulated in an embed element. The embed element is not present in the initial page content; rather, some script tags on the page cause some Javascript to be evaluated which eventually adds the embed element to the DOM. I want to be able to access this embed element when it appears, but it never appears when I load the page in PhantomJS.
Here's the code I'm using:
var page = require("webpage").create();
page.settings.userAgent = "Mozilla/5.0 (X11; rv:24.0) Gecko/20130909 Firefox/24.0";
page.open("https://www.youtube.com/embed/dQw4w9WgXcQ", function (status) {
if (status !== "success") {
console.log("Failed to load page");
phantom.exit();
} else {
setTimeout(function () {
var size = page.evaluate(function () {
return document.getElementsByTagName("EMBED").length;
});
console.log(size);
phantom.exit();
}, 15000);
}
});
I only ever see "0" printed to the console, no matter how long I set the timeout. If I look for "DIV" elements I get "3", and if I look for "SCRIPT" elements I get "5", so the code seems to be sound. I just never find any "EMBED" tags, even though if I load the URL above in my browser I do find one soon after page-load.
Does anyone have any idea what the problem might be? Thanks in advance for any help.
Patrick's answer got me on the right track, but the full story is as follows.
Youtube's Javascript probes the browser's capabilities before deciding whether to create some kind of video element. After trawling through the minified code, I was eventually able to fool Youtube into thinking PhantomJS supported HTML5 video by wrapping document.createElement in the page's onInitialized callback.
page.onInitialized = function () {
page.evaluate(function () {
var create = document.createElement;
document.createElement = function (tag) {
var elem = create.call(document, tag);
if (tag === "video") {
elem.canPlayType = function () { return "probably" };
}
return elem;
};
});
};
However, this was a misstep; to get the <embed> tag I was originally after, I needed to make Youtube's code think PhantomJS supports Flash, not HTML5 video. That's also doable:
page.onInitialized = function () {
page.evaluate(function () {
window.navigator = {
plugins: { "Shockwave Flash": { description: "Shockwave Flash 11.2 e202" } },
mimeTypes: { "application/x-shockwave-flash": { enabledPlugin: true } }
};
});
};
So that's how it's done.
phantomjs does not support flash, or the html5 video element.
As on option - try to build phantomjs with video/audio support by yourself.
Original answer link: https://github.com/ariya/phantomjs/issues/10839#issuecomment-331457673
I am building an extension (some injected js) that adds some buttons to a youtube page, which works fine. My issue is that when a user clicks on another video (say in the right hand side list), the next video is loaded without an actual page reload. Is there any way to detect this change so that I can rerun my code?
I have already tried things like binding to the hashchange event, to no avail.
The idea is simple:
Use background.js to listen for url changes to a specific youtube tab using chrome.tabs.onUpdated
Once tab change is detected, send the new URL to the content-script running in that tab
The background page listens for URL changes to other tabs also but I'm pretty sure you can figure out how to fix that.
manifest.json
{
"manifest_version": 2,
"name": "Tab url change detection",
"version": "1.0",
"background": {
"persistent":true,
"page":"bg.html"
},
"content_scripts": [{
"matches": ["http://www.youtube.com/*"],
"js": ["app.js"]
}
],
"permissions": [
"tabs",
"http://www.youtube.com/*"
]
}
background.html
<script src="background.js"></script>
background.js
//Listen for when a Tab changes state
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
if(changeInfo && changeInfo.status == "complete"){
console.log("Tab updated: " + tab.url);
chrome.tabs.sendMessage(tabId, {data: tab}, function(response) {
console.log(response);
});
}
});
app.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
//here we get the new
console.log("URL CHANGED: " + request.data.url);
});
I know it's an old thread but I hope someone else will be able to use this information.
I had a similar issue building a chrome extension for youtube. To be more specific I needed to detect the navigation within youtube.
TL;DR;
After going over most of the StackOverflow, I've ended with the following in the context.js file.
function run(){
// your youtube extention logic
console.log('change');
}
window.onload = run;
window.addEventListener('yt-navigate-start', run, true);
Explanation
In case you wish to see all of the window events you can run getEventListeners(window).
youtube has a costume event when changing pages, the event is yt-navigate-start.
For the first time youtube loads, I used the window.onload and after that, add the event listener window.addEventListener('yt-navigate-start', run, true);
Good luck, I hope this could help.
I was recently struggling with this and I figured out what to do. I'm not sure if this is the best way to do it, but it doesn't rely on a background.js file.
var oldURL= ""
//window.setInterval takes a function and executes it after
//a given time (defined by the second parmeter)in miliseconds
var urlChangeHandler = window.setInterval(checkUrlChange, 500)
function checkURLChange(){
newURL = document.URL;
if(newURL !== oldURL){
doSomething();
oldURL = newURL;
}
}
I hope this will be helpful for somebody.
var old_url = '';
var mutationObserver = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (location.href != old_url) {
old_url = location.href;
console.log('URL was changed');
}
});
});
mutationObserver.observe(document.documentElement, {childList: true, subtree: true});
YouTube's iFrame embed JS player API allows you to add callbacks to certain events. I want to add a callback for when a related video at the end of a video is selected.
To be more specific, when viewing a video in an embed, at the end it displays related videos within the embed. I want to run some code when one of those is selected. How can this be accomplished? I see that there is an onStateChange but none of the states are related to related videos. Do I need to add an onStateChange for YT.PlayerState.PLAYING and then compare the playing video to the original video to see if they're different somehow?
That seems like a reasonable solution to me.
The only point worth mentioning (which you've noted) is that you will be not able to tell if the change in the video is due to the user clicking on a related video, but if you're not interacting with the player dynamically, comparing the previous VideoID should suffice.
Just a heads up, if you're interested in a jQuery plugin that simplifies some of the callback/event handling work check out: https://github.com/nirvanatikku/jQuery-TubePlayer-Plugin
I ended up doing it basically the way I described. Here's the code I used:
started = false;
var onYouTubeIframeAPIReady = function(id) {
var player = new YT.Player('player', {
videoId: id,
events: {
'onStateChange': function (event) {
if (event.data == 1) { // The video started playing
started = true;
}
if (started && event.data == -1) {
// Video had already started playing before and is now in
// "unstarted" state so it must be a new video.
var video_url = event.target.i.videoUrl;
var video_id = video_url.replace('http://www.youtube.com/watch?v=', '').replace('&feature=player_embedded', '');
window.location = '#/view/' + video_id;
}
}
}
});
}
So basically, when a video starts playing, you set a "started" variable to true. Then, if/when the video enters the "unstarted" state, if "started" is true, then you know that it's a new video that just started playing. In that case, grab its video ID from the event.target object and do whatever you want with it.
My full commit is here if anyone wants to see the context and you can see it in action on http://toogl.es.