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(); })();