Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 96 additions & 76 deletions content-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,90 +11,110 @@
3. The timeout is just an added assurance that the playbackspeed input element is created and that the speed is changed to the stored speed from previous sessions
*/
/* ======== Start of code string literal ======== */
var code = `
var base = document.createElement; /* A backup reference to the browser's original document.createElement */
var VideoElementsMade = []; /* Array of video/audio elements made by spotify's scripts */
// console.log("outside of the code")
// var code = `
// console.log("inside the code");
var base = document.createElement; /* A backup reference to the browser's original document.createElement */
var VideoElementsMade = []; /* Array of video/audio elements made by spotify's scripts */
function debugLog(text) {
console.log(text)
}

/* Replacing the DOM's original reference to the browser's createElement function */
document.createElement = function(message) {
/* base.apply is calling the backup reference of createElement with the arguments sent to our function and assigning it to our variable named element */
var element = base.apply(this, arguments);

/* Replacing the DOM's original reference to the browser's createElement function */
document.createElement = function(message) {
/* base.apply is calling the backup reference of createElement with the arguments sent to our function and assigning it to our variable named element */
var element = base.apply(this, arguments);

/* we check the first argument sent to us Examp. document.createElement('video') would have message = 'video' */
/* ignores the many document.createElement('div'), document.createElement('nav'), ect... */
if(message == 'video' || message == 'audio'){ /* Checking if spotify scripts are making a video or audio element */
VideoElementsMade.push(element); /* Add a reference to the element in our array. Arrays hold references not copies by default in javascript. */
/* we check the first argument sent to us Examp. document.createElement('video') would have message = 'video' */
/* ignores the many document.createElement('div'), document.createElement('nav'), ect... */
if(message === 'video' || message === 'audio'){ /* Checking if spotify scripts are making a video or audio element */
VideoElementsMade.push(element); /* Add a reference to the element in our array. Arrays hold references not copies by default in javascript. */
}
return element /* return the element and complete the loop so the page is allowed to be made */
};

/* When the page is loaded completely... */
window.onload = function() {
function getStoredSpeed(){ /* Gets stored speed between refreshes*/
return localStorage.getItem('speed');
}
// locally cache the speed, so getStoredSpeed does not need to be called repetitively
var lastSpeed = getStoredSpeed() || 1.0; /* if stored speed is null make lastSpeed 1.0 */

function setStoredSpeed(value){ /* Sets variable in the site's cookie along-side spotify's stuff */
localStorage.setItem('speed',value);
lastSpeed = value;
}
/* Building our playback speed input element */
var input = document.createElement('input');
input.type = 'number';
input.id = 'speed-extension-input';
input.style = 'background-color: #08080859;'
+ 'border: #823333;'
+ 'width: 45px;'
+ 'margin: 5px;';
input.value = lastSpeed * 100;
input.onkeypress = function(e){ /* What happens when we change the number in our input box element */
if (e.code === "Enter") {
validateAndChangeSpeed();
}
return element /* return the element and complete the loop so the page is allowed to be made */
};
input.onblur = function() {
// "onfocusout" event not working
validateAndChangeSpeed();
};

/* When the page is loaded completely... */
window.onload = function() {
function getStoredSpeed(){ /* Gets stored speed between refreshes*/
return localStorage.getItem('speed');
function validateAndChangeSpeed(value){
var val = parseFloat( value || (input.value / 100));
if (!isNaN(val) && (val !== lastSpeed)) {
/* only change if input is valid and it changed */
setStoredSpeed(val);
videosChangeSpeed(val);
}
var lastSpeed = getStoredSpeed() || 1.0; /* if stored speed is null make lastSpeed 1.0 */
}

function setStoredSpeed(value){ /* Sets variable in the site's cookie along-side spotify's stuff */
localStorage.setItem('speed',value);
}

/* Building our playback speed input element */
var input = document.createElement('input');
input.type = 'number';
input.id = 'speed-extension-input';
input.style = 'background-color: #08080859;'
+ 'border: #823333;'
+ 'width: 45px;'
+ 'margin: 5px;';
input.value = lastSpeed * 100;
input.oninput = function(e){ /* What happens when we change the number in our input box element */
validateAndChangeSpeed(); /* We call our function */
};

function validateAndChangeSpeed(value){
var val = parseFloat( value || (input.value / 100)); /* val must be in format 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */
if(!isNaN(val)){ /* check if val is a number */
changeSpeed(val);
function videosChangeSpeed(val) {
for(var i = 0; i < VideoElementsMade.length; i++){ /* change speed for all elements found (i havent seen this be more than 1 but you never know) */
/* val is clamped to range 0.0625 - 16.0 https://stackoverflow.com/a/32320020 */
if (VideoElementsMade[i].playbackRate !== val) {
debugLog("changing playback rate from " + VideoElementsMade[i].playbackRate + " to " + val)
}
VideoElementsMade[i].playbackRate = val; /* set the playback rate here */
VideoElementsMade[i].defaultPlaybackRate = val;
}

function changeSpeed(val) {
for(var i = 0; i < VideoElementsMade.length; i++){ /* change speed for all elements found (i havent seen this be more than 1 but you never know) */
VideoElementsMade[i].playbackRate = val; /* set the playback rate here */
if(val != lastSpeed){ /* update the lastSpeed if the speed actually changed */
lastSpeed = val;
setStoredSpeed(val);
}
}
}

function timeout() { /* This function is called by itself over and over */
if(document.getElementById('speed-extension-input') == null) /* check if our input element doesnt exist */
{
try {
document.getElementsByClassName('now-playing-bar__right')[0].appendChild (input); /* make our input exist on page */
}catch{
setTimeout(timeout, 100);/*now-playing-bar__right doesnt exist yet so lets try again in 100ms*/
return;
}
}
setTimeout(function () { /* setTimeout is a delayed call(500 milliseconds) to the code below */
try {
validateAndChangeSpeed(lastSpeed); /* this is in a try/catch because if an error happens timeout wouldnt be called again. */
}catch{

}
timeout(); /* call timeout again which starts the loop and eventually it will come back here */
}, 500); /* 500ms */
}

function addSpeedInput() { /* adds speed input next to volume bar */
debugLog("Adding speed input");
try {
document.getElementsByClassName('volume-bar')[0].appendChild(input); /* make our input exist on page */
debugLog("Added speed input");
}catch{
setTimeout(addSpeedInput, 1000);/*volume-bar doesnt exist yet so lets try again in 1 second*/
return;
}

timeout(); /* starts the loop to check and create our inputbox and to set the playback speed without having to mess with input box(by refreshing and having it load from cookie) */
}

addSpeedInput();
function ensureSpeedNotChanged() {
/* sometimes playbackRate is set back to 1.0 by spotify's code so timeout just ensures it goes the speed the user desires */
};`;
setTimeout(function () { /* setTimeout is a delayed call(500 milliseconds) to the code below */
try {
/* this is in a try/catch because if an error happens timeout wouldnt be called again. */
validateAndChangeSpeed(lastSpeed);
}catch{

}
ensureSpeedNotChanged(); /* call timeout again which starts the loop and eventually it will come back here */
}, 500); /* 500ms */
}
ensureSpeedNotChanged();
}
// };`;
/* ======== End of code string literal ======== */
var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */
script.textContent = code; /* insert our code as the contents of the script */
document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */
(document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */
script.remove(); /* idk why i do this */
// Moved to inject.js
// var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */
// script.textContent = code; /* insert our code as the contents of the script */
// document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */
// (document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */
// script.remove(); /* idk why i do this */
27 changes: 27 additions & 0 deletions inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// inject.js
// var script = document.createElement('script'); /* Create our dummy script to be inserted with our code variable */
// script.textContent = code; /* insert our code as the contents of the script */
// document.body.appendChild(script); /* make our script exist on the page as, hopefully, the first script to execute. */
// (document.head||document.documentElement).appendChild(script); /* appends script again(not good practice) as close to top as possible */
// script.remove(); /* idk why i do this */

const nullthrows = (v) => {
if (v == null) throw new Error("it's a null");
return v;
}

function injectCode(src) {
const script = document.createElement('script');
script.src = src;
script.onload = function() {
console.log("chrome extension injected");
this.remove();
};

// This script runs before the <head> element is created,
// so we add the script to <html> instead.
nullthrows(document.head || document.documentElement).appendChild(script);
}


injectCode(chrome.runtime.getURL('/content-script.js'));
29 changes: 21 additions & 8 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
{
"name": "Spotify Playback Speed Access",
"description": "Adds ability to change song speed with an input next to the volume slider.",
"version": "1.6",
"permissions": [
"storage"
"version": "1.7",
"manifest_version": 3,
"host_permissions":[
"https://open.spotify.com/*"
],
"icons": { "16": "speed16.png",
"48": "speed48.png",
"128": "speed128.png" },
"content_scripts": [
{
"matches": ["https://open.spotify.com/*"],
"js": ["content-script.js"]
"run_at": "document_start",
"js": ["inject.js"]
}
],
"web_accessible_resources": [
{
"resources": ["content-script.js"],
"matches": ["https://open.spotify.com/*"]
}
],
"manifest_version": 2
"permissions": [
"storage",
"activeTab"
],

"icons": { "16": "speed16.png",
"48": "speed48.png",
"128": "speed128.png" }

}