YouTube API getElementById Error

Hello there,

I want to create a Firefox-Add-on which set the default Play-Seed, Quality, etc. from YouTube

function editVideo() {
	document.cookie="PREF=f6=9&f5=30030;path=/;domain=.youtube.com"; //WORKING
}

function playSpeed(){
	document.getElementsByTagName("video")[0].playbackRate = 2; //WORKING
	document.getElementById('movie_player').setPlaybackRate(2); //NOT WORKING
	document.getElementById("movie_player").stopVideo(); //NOT WORKING
	document.getElementById("movie_player").setPlaybackQuality('hd720'); //NOT WORKING
	document.getElementById("movie_player").playVideo(); //NOT WORKING
}

editVideo();
var intervalId = setInterval(function(){
	var elm = document.getElementsByTagName("video")[0];
	if(elm != null){
		playSpeed();
		clearInterval(intervalId);
	}
}, 250);

As you can see, all commands with “getElementById” are not working, but commands with “getElementByTagName” seems to work without any problems.
If I took a look at the console, FireFox tells me that it doesn’t know the function setPlaybackRate, setPlaybackQuality etc.
But why does “playbackRate” with “getElementById” work and the other commands not?

If I type those commands directly in the console, it works perfectly!

I would appreciate your help! (PS: This is my first Add-on and first JavaScript, so I am pretty inexperienced.

getElementById("movie_player") doesn’t return a <video> element.

@evilpie I don’t think so. If I check the console, it seems like it gets the video. But like I already mentioned, it seems like it doesn’t know the function “setPlaybackRate”.

@evilpie I think I know what you mean.
getElememtById("movie_palyer") returns: object HTMLDicElement
document.getElementsByTagName("video")[0] returns: object HTMLVideoElement
So what should I do? Is that the problem?

Yes.

Get the proper element that you’re looking for. Simplest way, if there’s just one <video> Element on the page is document.querySelector('video').

@freaktechnik Well, okay. But

document.querySelector('video') = document.getElementsByTagName("video")[0]
And with this, commands like setPlaybackRate/setPlaybackQuality/stopVideo/playVideo/setVolume doesn’t work! I need the YouTube API.

And like I mentioned before, commands like: document.getElementById('movie_player').setPlaybackRate(2);
works perfectly if I type them directly into the console, but not in the AddOn-Code.

So with the Video, I can’t do anything. Why are thoose commands not working with the AddOn but working if I type them directly into the console?

Ok, what is happening is that your add-on code and the page do not run in the same environment. This is why you don’t have directly access to the YouTube API.

When the page loads, it runs its own javascript context, where the YouTube scripts add dynamically their API like, like function setPlaybackRate(), to an object associated to DOM DIV element #move_player (by default this object is a HTMLDivElement which does not support function setPlaybackRate()).

When you use the page console, you run in this same page environment. This is why you can call document.getElementById("movie_player").setPlaybackRate().

However, the content code your add-on injects into the page (using browser.tabs.executeScript() or content_scripts in manifest.json) runs in a separate environment. This one accesses the same DOM structure, but in a different javascript context. This is why when you call document.getElementById("movie_player"), you get a plain HTMLDivElement that has not been modified by the YouTube scripts to add functions like setPlaybackRate().

Disclaimer: i’m not 100% sure the method below will work with WebExtensions, but i know i used it successfully in the former add-on SDK API.

What you should try is, from the add-on content code, to inject into the page environment a script that does what you want. You should be able to do so, by creating a <script> element that contains the code you want to execute.

For instance:

// code to be executed by the add-on once you are sure 
// the page DOM loaded and YouTube scripts did their
// job installing the API
var script = document.createElement("script");
var code = `if(typeof chrome==="undefined")
  document.getElementById("movie_player").setPlaybackRate(2);`;
var codeElement = document.createTextNode(code);
script.appendChild(codeElement);
document.querySelector("head").appendChild(script);

So the add-on code modifies the DOM, and as this DOM is shared with the page environment, this last one will execute the code inside the <script> element. Of course the add-on content environment will also try to execute that code, this is the reason for the if(typeof chrome==="undefined") test.

Obviously, if you plan to give control of the YouTube API to the add-on, this solution is not very convenient (but still worth the try to validate the concept).

I would suggest using the HTML5 Window messaging API to make the 2 environments to communicate with each other (use window.addEventListener("message",...) and window.postMessage(...)), so that requests from the add-on can be proxied to your code in the page (that you injected with the method above) which will execute them (and possibly will return a result by sending a message back).

I hope this makes sense.

1 Like

@mig WOW, first at all: THANK YOU! I know that “thanks-postings” aren’t allowed, but you are the first person, who really understand my problem and helped me, even though I am a bloody beginner. And your explanation was pretty good to understand as a beginner. I was on so many forums and it seems like nobody knows what’s wrong (or wasn’t interested).

Your solution works! But I have a few problems and I hope you could maybe help me.

  1. I understand what you mean with DOM, that’s why I set "run_at": "document_idle"
    But the problem now is, that I have to refresh YouTube every time I visit it, to enable the AddOn. It means, that YouTube starts automatically with Speed = 1x. And I have to refresh it, to enable speed = 2. I also have to refresh YouTube, to inject the cookies…
    So I tried to set "run_at": "document_start" But that doesn’t work of course, because the DOM isn’t loaded when the Cookie and the JS get injected. So what can I do, that this works automatically without refreshing?

  2. How can I modify the websites, on which this AddOn runs. So, I don’t want that live-videos play with double speed, because that won’t obviously work. But everything else (setVolume, inject Cookie etc.) should work on live-videos as well. Do I have to create two .js files for that?

  3. Well, this question is actually about YouTube-API. How to change the default quality? I want that it always uses the highest resolution.
    document.getElementById("movie_player").setPlaybackQuality('hd1080'); doesn’t work, also not with the console.

  4. The Firefox-Console give me following Error, if I start the AddOn:

Warning: attempting to write 8135 bytes to preference browser.uiCustomization.state. This is bad for general performance and memory usage. Such an amount of data should rather be written to an external file. This preference will not be sent to any content processes.

I wasn’t able to fix this. Seems like the AddOn still works, but it wouldn’t be great if it took to much resources.

I hope you or someone else could help me. Thanks!

1 Like

That warning is not related to your extension, but instead the amount of buttons etc. in the toolbar of your profile.

I understand what you mean with DOM, that’s why I set “run_at”: "document_idle"
But the problem now is, that I have to refresh YouTube every time I visit it, to enable the AddOn. It means, that YouTube starts automatically with Speed = 1x. And I have to refresh it, to enable speed = 2. I also have to refresh YouTube, to inject the cookies…
So I tried to set “run_at”: “document_start” But that doesn’t work of course, because the DOM isn’t loaded when the Cookie and the JS get injected. So what can I do, that this works automatically without refreshing?

I’m not sure what you mean here and what this cookie is for, but since you cannot know when YouTube scripts actually install the API, the code you inject into the page context should poll for 1/ having an element with id movie_player and 2/ make sure the YT API is installed by checking the element setPlaybackRate property.

How can I modify the websites, on which this AddOn runs. So, I don’t want that live-videos play with double speed, because that won’t obviously work. But everything else (setVolume, inject Cookie etc.) should work on live-videos as well. Do I have to create two .js files for that ?

My guess is that if a web site embeds YT videos and you inject your code into the YT iframe, it will work.

Well, this question is actually about YouTube-API. How to change the default quality? I want that it always uses the highest resolution.
document.getElementById(“movie_player”).setPlaybackQuality(‘hd1080’); doesn’t work, also not with the console.

The best is probably to check the YouTube embedding API.