fix(player): lock WebView muted/volume until gesture (kill audio-during-scrape)
Some checks are pending
Backend tests / test (push) Waiting to run

Przy scrape ścieżce (WebView otwiera stronę hostera żeby przechwycić stream)
player hostera po autostarcie ustawiał video.muted=false / volume=1 i dźwięk
buchał zanim ExoPlayer przejął (raport usera). Sam override play() łapał za
wcześnie. Teraz LOCKujemy muted/volume na HTMLMediaElement.prototype: próba
odciszenia przed gestem usera jest ignorowana. Po tapie (watch-in-WebView dla
CF-blocked hosterów) odblokowanie + odciszenie istniejących <video>/<audio>.
Patch leci injectedJavaScriptBeforeContentLoaded, więc przed page JS.
This commit is contained in:
goon-foss 2026-07-21 15:17:30 +02:00
parent e2cb8df670
commit f55f87410a
2 changed files with 49 additions and 7 deletions

View file

@ -16,6 +16,13 @@ export type ChangelogEntry = {
};
export const CHANGELOG: ChangelogEntry[] = [
{
id: '2026-07-20b',
date: 'July 2026',
items: [
'No more burst of sound when opening a video: the page we briefly load to grab the stream now stays fully muted until it hands off to the player (or until you tap, for the few sites that play in the web view).',
],
},
{
id: '2026-07-20',
date: 'July 2026',

View file

@ -994,18 +994,53 @@ const INJECTED_JS = `
if (window.__goonPatched) return;
window.__goonPatched = true;
// -- 0a. Mute autoplay until the FIRST user gesture (kills the brief unmuted-audio
// flash gdy WebView otwiera stronę żeby przechwycić stream, report dccc05e4).
// Override play() PRZED page JS: bez gestu → muted; po tapie usera → dźwięk
// dozwolony (zgodne z "dźwięk dopiero po geście usera"). Element-level mute
// niżej łapał za późno (autoplay z dźwiękiem zdążył ruszyć).
// -- 0a. Mute autoplay until the FIRST user gesture (kills the unmuted-audio flash
// gdy WebView otwiera stronę żeby przechwycić stream, report dccc05e4).
// Sam override play() nie wystarczał: player hostera po starcie ustawiał
// \`video.muted = false\` / \`video.volume = 1\` i dźwięk wracał (raport usera
// 2026-07-20: "łapie z głosem przed ExoPlayerem"). Więc LOCKUJEMY muted/volume
// na poziomie prototypu — każda próba odciszenia PRZED gestem jest ignorowana.
// Po tapie usera (watch-in-WebView dla CF-blocked hosterów) odblokowujemy i
// odciszamy. W ścieżce scrape→native gestu nie ma → WebView zostaje niemy do
// końca (i tak go nie słychać, gra ExoPlayer). Patch leci
// injectedJavaScriptBeforeContentLoaded, więc jest PRZED elementami page JS.
try {
var _goonOrigPlay = HTMLMediaElement.prototype.play;
HTMLMediaElement.prototype.play = function() {
if (!window.__goonUserGestured) { try { this.muted = true; } catch (e) {} }
if (!window.__goonUserGestured) { try { this.muted = true; this.volume = 0; } catch (e) {} }
return _goonOrigPlay.apply(this, arguments);
};
var _goonAllowSound = function() { window.__goonUserGestured = true; };
var _mDesc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'muted');
if (_mDesc && _mDesc.get && _mDesc.set) {
Object.defineProperty(HTMLMediaElement.prototype, 'muted', {
configurable: true,
get: function() { return _mDesc.get.call(this); },
set: function(v) {
if (!window.__goonUserGestured && v === false) { _mDesc.set.call(this, true); return; }
_mDesc.set.call(this, v);
},
});
}
var _vDesc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'volume');
if (_vDesc && _vDesc.get && _vDesc.set) {
Object.defineProperty(HTMLMediaElement.prototype, 'volume', {
configurable: true,
get: function() { return _vDesc.get.call(this); },
set: function(v) {
if (!window.__goonUserGestured && v > 0) { _vDesc.set.call(this, 0); return; }
_vDesc.set.call(this, v);
},
});
}
var _goonAllowSound = function() {
if (window.__goonUserGestured) return;
window.__goonUserGestured = true;
// User tapnął → chce dźwięk: odcisz wszystko co już gra (watch-in-WebView).
try {
var els = document.querySelectorAll('video, audio');
for (var i = 0; i < els.length; i++) { try { els[i].muted = false; els[i].volume = 1; } catch (e) {} }
} catch (e) {}
};
window.addEventListener('touchstart', _goonAllowSound, { once: true, capture: true });
window.addEventListener('click', _goonAllowSound, { once: true, capture: true });
} catch (e) {}