javascriptjquerygoogle-chrome-extensionjavascript-injectionjquery-events

Control YouTube player from a Chrome extension


I am developing a Google Chrome extension that needs to take over all the controls of a YouTube video (play, pause, next, previous, change the volume, start video at X seconds, etc). I tried the following approaches:

First approach: with content_scripts

My manifest.json contains the following:

"content_scripts": [
    {
        "matches": ["https://www.youtube.com/*"],
        "js": ["scripts/jquery.js", "scripts/in_page.js"]
    }
],

I'm loading the scripts for every YouTube link, and not only /watch* because now YouTube navigation is fully using AJAX, therefore the page is not entirely refreshed, and if I was on a search page and then clicked on a video, the extension wouldn't have loaded my content_scripts.

How I took over the controls

I successfully managed to trigger simple click events on the YouTube player, such as Play, Pause, Previous video, Next video.

For instance, this works to pause the video:

$("#player-api .html5-video-controls .ytp-button-pause").trigger("click");

But it seems that I can't trigger events such as clicking on the progress bar to play from a specific moment of the video or restart the video.

I tried this piece of code without success (using jQuery's $.Event):

var click = $.Event("click");
click.clientX = 0; // Beginning of the video
click.clientY = 0; // I also tried several other coordinates
$("#player-api .html5-video-controls .html5-progress-bar").trigger(click);

(And I also tried clicking on every child of the .html5-progress-bar, but nothing worked.)

Second approach: with an injected script

As I seemed to have encountered a dead-end with my first approach, I then tried something else: injecting a script directly inside the page.

My manifest.json contains the following:

"content_scripts": [
    {
        "matches": ["https://www.youtube.com/*"],
        "js": ["scripts/jquery.js", "scripts/isolated.js"]
    }
],

"web_accessible_resources": [
    "scripts/injected.js"
],

Contents of isolated.js

var s = document.createElement("script");
s.src = chrome.extension.getURL("scripts/injected.js");
s.onload = function() {
    this.parentNode.removeChild(this);
};
(document.head || document.documentElement).appendChild(s);

Contents of injected.js

Well, this is where I encountered my second dead-end. I may have overlooked some things, but I searched in every object inside the page, I found yt, ytplayer, player, and others. But none of them seem to have proper functions to trigger changes on the player such as play/pause/next, etc.

I then downloaded the html5player.js from YouTube (available here) and beautified it to take a peek at what was under the hood. I found some interesting function names such as playVideoAt, playVideo, pauseVideo around the lines 20235-20315 (once beautified).

That would be exactly the functions I would want to call/invoke, but I don't know where they are, and how to call them.

I also know that YouTube has a JavaScript API to control the player, but it's only for embedded players (iframes), so it's not useful in my case.


Solution

  • It sounds like you're using YouTube's HTML5 video player. In that case you can just interface with the <video> DOM element. It happens to be the only <video> element on the page, so just do:

    $('video').play()
    $('video').pause()
    $('video').currentTime = 100
    

    The interface is here.

    As for previous and next videos, you're almost certainly right that the easiest thing to do is:

    $('.ytp-button-next').click()
    $('.ytp-button-prev').click()
    

    Since that obviously isn't a published API you have no guarantee those class names will stay the same in future.