From 21967a2e473304e7424a2afeec949f92bcbbe708 Mon Sep 17 00:00:00 2001 From: Gerben Date: Thu, 21 Nov 2019 21:48:31 +0100 Subject: [PATCH] Workaround for audio from 'file:' URLs. --- app/audio-player/background.js | 74 +++++++++++++++++++++++----------- app/manifest.json | 1 + 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/app/audio-player/background.js b/app/audio-player/background.js index 5ee8f3c..b637516 100644 --- a/app/audio-player/background.js +++ b/app/audio-player/background.js @@ -3,8 +3,7 @@ import delay from 'delay'; async function tryUntilItWorks(func, delayBetweenTries = 1, maxTries = 10) { for (let i = 0; i < maxTries; i++) { try { - await func(); - return; + return await func(); } catch (err) { await delay(delayBetweenTries); continue; @@ -12,33 +11,38 @@ async function tryUntilItWorks(func, delayBetweenTries = 1, maxTries = 10) { } } +async function insertOurAudioPlayer(tabId) { + // Avoid any short glitch of sound (observed in Firefox) before our injected script executes. + await browser.tabs.update(tabId, { muted: true }); + + // Too quickly executing the script may fail (bug in Firefox), so we retry if necessary. + await tryUntilItWorks(() => browser.tabs.executeScript(tabId, { + runAt: 'document_end', + file: '/scripts/contentscript.js', + })); + + // Unmute again. + await browser.tabs.update(tabId, { muted: false }); + + await tryUntilItWorks(() => browser.tabs.insertCSS(tabId, { + runAt: 'document_end', + file: '/assets/waveform-playlist.css', + })); + + await tryUntilItWorks(() => browser.tabs.insertCSS(tabId, { + runAt: 'document_end', + file: '/assets/main.css', + })); +} + +// Load the player on any file with an audio/* mime type. async function onHeadersReceived({ responseHeaders, url, tabId }) { const isAudio = responseHeaders.some(header => header.name.toLowerCase() === 'content-type' && header.value.toLowerCase().startsWith('audio/') ); if (isAudio) { - // Avoid any short glitch of sound (observed in Firefox) before our injected script executes. - await browser.tabs.update(tabId, { muted: true }); - - // Too quickly executing the script may fail (bug in Firefox), so we retry if necessary. - await tryUntilItWorks(() => browser.tabs.executeScript(tabId, { - runAt: 'document_end', - file: '/scripts/contentscript.js', - })); - - // Unmute again. - await browser.tabs.update(tabId, { muted: false }); - - await tryUntilItWorks(() => browser.tabs.insertCSS(tabId, { - runAt: 'document_end', - file: '/assets/waveform-playlist.css', - })); - - await tryUntilItWorks(() => browser.tabs.insertCSS(tabId, { - runAt: 'document_end', - file: '/assets/main.css', - })); + insertOurAudioPlayer(tabId); } } @@ -50,3 +54,27 @@ browser.webRequest.onHeadersReceived.addListener( }, ['responseHeaders'], ); + +// As files loaded through file://… URLs do not have headers, we run a separate check for these. +async function onVisitFileUrl({ url, tabId }) { + // I don’t know how we could neatly test whether the file is an audio file. This expression is a + // pragmatic solution working for (at least) the current versions of Firefox and Chromium. + const isAudioPlayerExpression = + `document.body.childElementCount === 1 + && document.body.firstElementChild instanceof HTMLVideoElement + && document.body.firstElementChild.videoHeight === 0 + && document.body.firstElementChild.videoWidth === 0 + `; + const [isAudio] = await tryUntilItWorks(() => browser.tabs.executeScript(tabId, { + code: isAudioPlayerExpression, + runAt: 'document_idle', // (at document_end it’s not clear yet whether it’s audio or video..) + })); + if (isAudio) { + insertOurAudioPlayer(tabId); + } +} + +browser.webNavigation.onCommitted.addListener( + onVisitFileUrl, + { url: [{ schemes: ['file'] }] }, +); diff --git a/app/manifest.json b/app/manifest.json index d5782c7..3f118c4 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -13,6 +13,7 @@ "clipboardWrite", "bookmarks", "tabs", + "webNavigation", "webRequest" ] }