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
56 changes: 35 additions & 21 deletions js&css/extension/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ function finishPageWorldInit() {
extension.events.trigger('init');
}

function syncPageWorldAutoplayDisable(callback) {
chrome.storage.local.get('player_autoplay_disable', function (items) {
if (items.player_autoplay_disable === true) {
localStorage['it-player-autoplay-disable'] = 'true';
} else {
localStorage.removeItem('it-player-autoplay-disable');
}

callback();
});
}

const pageWorldFiles = [
'/js&css/web-accessible/core.js',
'/js&css/web-accessible/functions.js',
Expand All @@ -89,27 +101,29 @@ const pageWorldFiles = [
'/js&css/web-accessible/init.js'
];

if ((navigator.userAgent.indexOf('Safari') !== -1
|| (typeof browser !== 'undefined' && browser.runtime?.getURL('')?.startsWith('safari-')))
&& (!/Chrom|Android|Windows|Linux/.test(navigator.userAgent)
|| /iPhone|iPad/.test(navigator.userAgent)
)
) {

chrome.runtime.sendMessage({
action: 'inject-main-world',
files: pageWorldFiles
}, function (response) {
if (response && response.ok) {
finishPageWorldInit();
} else {
console.warn('Falling back to DOM injection for page-world scripts', chrome.runtime.lastError?.message || response?.error);
extension.inject(pageWorldFiles.slice(), finishPageWorldInit);
}
});
} else {
extension.inject(pageWorldFiles.slice(), finishPageWorldInit);
}
syncPageWorldAutoplayDisable(function () {
if ((navigator.userAgent.indexOf('Safari') !== -1
|| (typeof browser !== 'undefined' && browser.runtime?.getURL('')?.startsWith('safari-')))
&& (!/Chrom|Android|Windows|Linux/.test(navigator.userAgent)
|| /iPhone|iPad/.test(navigator.userAgent)
)
) {

chrome.runtime.sendMessage({
action: 'inject-main-world',
files: pageWorldFiles
}, function (response) {
if (response && response.ok) {
finishPageWorldInit();
} else {
console.warn('Falling back to DOM injection for page-world scripts', chrome.runtime.lastError?.message || response?.error);
extension.inject(pageWorldFiles.slice(), finishPageWorldInit);
}
});
} else {
extension.inject(pageWorldFiles.slice(), finishPageWorldInit);
}
});

document.addEventListener('DOMContentLoaded', function () {
extension.domReady = true;
Expand Down
49 changes: 49 additions & 0 deletions js&css/web-accessible/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ var ImprovedTube = {
defaultApiKey: 'AIzaSyCXRRCFwKAXOiF1JkUBmibzxJF1cPuKNwA'
};

ImprovedTube.syncAutoplayDisableLocalStorage = function () {
if (ImprovedTube.storage.player_autoplay_disable === true) {
localStorage['it-player-autoplay-disable'] = 'true';
} else {
localStorage.removeItem('it-player-autoplay-disable');
}
};

ImprovedTube.shouldPreventInitialAutoplay = function (video) {
if (ImprovedTube.user_interacted || !location.href.includes('/watch?') || location.href.includes('list=')) {
return false;
}

if (localStorage['it-player-autoplay-disable'] !== 'true' && ImprovedTube.storage.player_autoplay_disable !== true) {
return false;
}

var player = video.closest && (video.closest('.html5-video-player') || video.closest('#movie_player'));

return !player || !player.classList || !player.classList.contains('ad-showing');
};

/*--------------------------------------------------------------
CODEC || 30FPS
----------------------------------------------------------------
Expand Down Expand Up @@ -125,6 +147,29 @@ if (localStorage['it-codec'] || localStorage['it-player30fps']) {
}
};

HTMLMediaElement.prototype.play = (function (original) {
if (original.improvedTubeInitialAutoplayGuard) {
return original;
}

function play() {
if (ImprovedTube.shouldPreventInitialAutoplay(this)) {
try {
this.pause();
} catch (error) {
}

return Promise.resolve();
}

return original.apply(this, arguments);
}

play.improvedTubeInitialAutoplayGuard = true;

return play;
})(HTMLMediaElement.prototype.play);

/*--------------------------------------------------------------
# MESSAGES
----------------------------------------------------------------
Expand Down Expand Up @@ -182,6 +227,7 @@ document.addEventListener('it-message-from-extension', function () {

if (message.action === 'storage-loaded') {
ImprovedTube.storage = message.storage;
ImprovedTube.syncAutoplayDisableLocalStorage();

if (ImprovedTube.storage.block_vp9 || ImprovedTube.storage.block_av1 || ImprovedTube.storage.block_h264) {
let atlas = { block_vp9: 'vp9|vp09', block_h264: 'avc1', block_av1: 'av01' },
Expand Down Expand Up @@ -233,6 +279,9 @@ document.addEventListener('it-message-from-extension', function () {
localStorage.removeItem('it-player30fps');
}
}
if (message.key === 'player_autoplay_disable') {
ImprovedTube.syncAutoplayDisableLocalStorage();
}
switch (camelized_key) {
case 'blocklist':
case 'blocklistActivate':
Expand Down
75 changes: 75 additions & 0 deletions tests/unit/autoplay-disable.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
describe('initial autoplay guard', () => {
let originalPlay;
let video;

function loadCore() {
jest.resetModules();

const storage = {};
storage.removeItem = key => {
delete storage[key];
};

global.localStorage = storage;
global.location = {
href: 'https://www.youtube.com/watch?v=abcdefghijk'
};
global.window = {};
global.CustomEvent = function CustomEvent(type) {
this.type = type;
};
global.document = {
addEventListener: jest.fn(),
createElement: jest.fn(() => ({style: {}})),
dispatchEvent: jest.fn(),
documentElement: {
appendChild: jest.fn()
},
querySelector: jest.fn()
};

originalPlay = jest.fn(() => 'played');
global.HTMLMediaElement = function HTMLMediaElement() {};
global.HTMLMediaElement.prototype.play = originalPlay;

require('../../js&css/web-accessible/core.js');

video = {
closest: jest.fn(() => ({
classList: {
contains: jest.fn(() => false)
}
})),
pause: jest.fn()
};
}

beforeEach(() => {
loadCore();
});

afterEach(() => {
delete global.CustomEvent;
delete global.document;
delete global.HTMLMediaElement;
delete global.localStorage;
delete global.location;
delete global.window;
});

test('prevents the first direct watch-page play when autoplay is disabled', async () => {
localStorage['it-player-autoplay-disable'] = 'true';

await expect(HTMLMediaElement.prototype.play.call(video)).resolves.toBeUndefined();

expect(video.pause).toHaveBeenCalledTimes(1);
expect(originalPlay).not.toHaveBeenCalled();
});

test('allows playback when autoplay is not disabled', () => {
expect(HTMLMediaElement.prototype.play.call(video)).toBe('played');

expect(video.pause).not.toHaveBeenCalled();
expect(originalPlay).toHaveBeenCalledTimes(1);
});
});