NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @namespace https://openuserjs.org/users/yzwone
// @name t.me: fix popping sounds on vertical videos
// @description Fix for popping sounds when a vertical video plays on https://t.me/*
// @license GPL-3.0-only; http://www.gnu.org/licenses/gpl-3.0.txt
// @version 1.0.0
// @match https://t.me/*
// @grant none
// @run-at document-end
// ==/UserScript==
// ==OpenUserJS==
// @author yzwone
// ==/OpenUserJS==
(function () {
"strict"
/**
* TLDR;
* Popping sounds when playing vertical videos in t.me happen because of blurred `<video>` elements.
* A blurred video plays simultaneously with a normal video (they actually use the same video url) and
* is used as "borders" for the normal video.
* The popping sounds occur because the two videos desynchronize:
* 1) at first we hear echo
* 2) then we hear a popping sound - when the code periodically sets `currentTime` of the blurred video
* to `currentTime` of the normal video.
*/
// Parameters
/**
* The threshold value in seconds for writes to `<video>.currentTime`: the new value is only written
* if it exceeds the current value by this threshold.
*/
const CURRENT_TIME_SETTER_THRESHOLD_IN_SECONDS = 1;
function main() {
// process videos in the current frame: this is for urls like 'https://t.me/s/...' when multiple posts are shown
processNestedVideos(document);
// process videos in other frames: single posts are shown inside an `iframe`
for (let i = 0; i < window.frames.length; i++) {
processNestedVideos(window.frames[i].document);
}
// process videos, which are added dynamically when we scroll page
const observer = new MutationObserver(mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.addedNodes) {
mutation.addedNodes.forEach(el => processNestedVideos(el));
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/**
* @param {Node} rootEl
*/
function processNestedVideos(rootEl) {
if (!rootEl.querySelectorAll) {
return;
}
// popping sounds are caused by blurred videos
// (there are 2 `<video>` elements for every video post: one normal, and one blurred)
rootEl
.querySelectorAll('video.tgme_widget_message_video.js-message_video_blured')
.forEach(videoBlurredEl => stopPoppingSounds(videoBlurredEl));
}
/**
* Stop popping sounds caused by the given blurred video
*
* @param {HTMLVideoElement} videoBlurredEl
*/
function stopPoppingSounds(videoBlurredEl) {
// mute the blurred video (only the normal video will play sound)
muteVideo(videoBlurredEl);
// Unfortunately, the above is not enough: the normal video has a "timeupdate" event handler with this logic:
// if (videoBluredEl && videoBluredEl.currentTime != videoEl.currentTime) {
// videoBluredEl.currentTime = videoEl.currentTime;
// }
// When "currentTime" is assigned a new value, then you hear a popping sound, even if the video is muted.
// To fix that we override "currentTime" property so that it is only written if the
// difference between the new value and the current value is bigger then the threshold
addThresholdToCurrentTimeSetter(videoBlurredEl);
}
/**
* @param {HTMLVideoElement} videoEl
*/
function muteVideo(videoEl) {
videoEl.muted = true;
videoEl.defaultMuted = true;
videoEl.volume = 0;
}
/**
* Make `video.currentTime = newValue` to work only when the difference between
* the current value and the `newValue` is greater than the given threshold
*
* @param {HTMLVideoElement} videoEl
* @param {number} thresholdInSeconds
*/
function addThresholdToCurrentTimeSetter(videoEl) {
let currentDesc = null;
for (let obj = videoEl; obj && (!currentDesc); obj = Object.getPrototypeOf(obj)) {
currentDesc = Object.getOwnPropertyDescriptor(obj, 'currentTime');
}
if (!(currentDesc && currentDesc.get && currentDesc.set)) {
console.warn("t.me video popping: property descriptor for video.currentTime wasn't found");
return;
}
const oldGetter = currentDesc.get;
const oldSetter = currentDesc.set;
Object.defineProperty(videoEl, 'currentTime', {
...currentDesc,
set: function (newVal) {
const curVal = oldGetter.call(this);
if (Math.abs(newVal - curVal) >= CURRENT_TIME_SETTER_THRESHOLD_IN_SECONDS) {
oldSetter.call(this, newVal);
}
}
});
}
main();
})();