Reputation: 1965
I want to write a web extension that will be able to detect playback status of any youtube video a user visits. I looked into the youtube API but it seems like I can only access youtube videos that I've embedded myself. However in this situation, I am not embedding them.
Is there any way to do this?
Upvotes: 3
Views: 902
Reputation: 13667
I've been intrigued by this question since it was asked, and tonight at our local GDG we had a Chrome extension hackathon where I put some experiments to the test. Here's what I've discovered.
A) the extension sandbox really is tough; nothing I tried could get at the javascript coded into the page (if it existed).
B) one solution that works pretty well under certain circumstances is to use a content-script to do code injection, and set up your own listeners on an existing iframe embed. Something like this works:
var regex = /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w]*(?:['"][^<>]*>|<\/a>))[?=&+%\w]*/ig;
var node, nodes = document.evaluate("(//iframe/@src)",document,null,XPathResult.UNORDERED_NODE_ITERATOR_TYPE,null);
var hasvid=false,vids = [];
while (node=nodes.iterateNext()) { // uses regex to run through DOM, looking for embeds
if (regex.test(node.nodeValue)) {
hasvid=true;
vids.push(node.ownerElement);
}
}
// Now inject a script that sets up a YT.player object and bind it to our extension's functions
if (hasvid) {
playerlines=['function onYouTubePlayerAPIReady() {'];
for (i=0;i<vids.length; i++) {
playerlines.push('player = new YT.Player(\''+vids[i].id+'\', { events: {\'onStateChange\': onPlayerStateChange}});}');
}
callbacklines=['function onPlayerStateChange(event) {',
'if (event.data == YT.PlayerState.ENDED) { alert(\'video is over\'); }',
'else if (event.data == YT.PlayerState.PAUSED) { alert(\'player is paused\'); }',
'else if (event.data == YT.PlayerState.PLAYING) { alert(\'player is playing\'); } }'];
// notify the background page to actually do the injecting of the script
chrome.extension.sendRequest({}, function(response) {});
codelines=playerlines.concat(callbacklines);
var actualCode=codelines.join('\n');
var script = document.createElement('script');
script.textContent = actualCode;
var jsapi = document.createElement('script');
jsapi.src = "http://www.youtube.com/player_api";
(document.head||document.documentElement).appendChild(jsapi);
(document.head||document.documentElement).appendChild(script);
With something like that, you could not just run alerts, but theoretically send all events back to your background page so it could do what it wants based on what the events are.
C) This is of no use if the video wasn't embedded with an iFrame; I tried to employ a similar solution when it was an or element, and while I could detect it just fine, I couldn't get listeners set up to capture the events. I may have been overlooking something, or it may be that the events are fired differently so I couldn't get the binding I needed to.
D) One less-than-useful workaround would be to manipulate the DOM and remove an embedded video, re-embedding it yourself with more direct bindings and listeners set up (as another answer suggested). However, if the original page had been using the API itself, such an action would break its listeners, so there could be unintended consequences if the video you're seeing is part of a API-based web app.
Hope this is useful info.
Upvotes: 1
Reputation: 13260
Well, so embed the videos yourself! You can use content-scripts to modify the youtube frame before it loads, or even better... I'm not sure how youtube api works, but you can probably intercept video request with Chrome's Web Request API and alter the request data to get what you want.
If you provide more info, may I can give you more details on how do that...
Good luck!
Upvotes: 0