Pause media in other tabs when playing media in current tab (mobile-like behavior)
// ==UserScript==
// @name Tab Media Focus (Smart Filter)
// @namespace https://greasyfork.org/en/scripts/589569/
// @version 1.2
// @description Pause media in other tabs when playing media in current tab (mobile-like behavior)
// @author TechComet
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addValueChangeListener
// @run-at document-idle
// @license MIT
// ==/UserScript==
// Written using Qwen Ai from the first attempt
(function() {
'use strict';
// 1. Generate a unique ID for this tab
const MY_TAB_ID = 'tab_' + Math.random().toString(36).substr(2, 9);
const STORAGE_KEY = 'active_media_focus_id';
// 2. Configuration for Smart Filtering
const CONFIG = {
ignoreMuted: true, // Ignore muted videos (common in previews)
ignoreShortDuration: true, // Ignore very short videos (previews)
minDurationSeconds: 5, // Minimum duration to be considered "real" media
previewKeywords: [ // Keywords in class names indicating previews
'preview', 'thumbnail', 'mini', 'hover', 'feed', 'shorts',
'ytd-thumbnail', 'video-preview', 'autoplay-preview'
]
};
console.log(`[MediaFocus] Initialized: ${MY_TAB_ID}`);
// 3. Check if video is a preview/thumbnail based on multiple criteria
function isPreviewVideo(media) {
// A. Check Mute Status (Fastest & Most Reliable)
if (CONFIG.ignoreMuted && (media.muted || media.volume === 0)) {
return true;
}
// B. Check Class Names (Fast)
if (media.classList) {
for (let i = 0; i < media.classList.length; i++) {
const cls = media.classList[i].toLowerCase();
for (const keyword of CONFIG.previewKeywords) {
if (cls.includes(keyword)) {
return true;
}
}
}
}
// C. Check Duration (Reliable if metadata loaded)
if (CONFIG.ignoreShortDuration && media.duration) {
if (isFinite(media.duration) && media.duration < CONFIG.minDurationSeconds) {
return true;
}
}
return false;
}
// 4. Pause all media elements in this tab
function pauseAllMedia() {
const mediaElements = document.querySelectorAll('video, audio');
let pausedCount = 0;
mediaElements.forEach(media => {
if (!media.paused) {
media.pause();
pausedCount++;
}
});
if (pausedCount > 0) console.log(`[MediaFocus] Paused ${pausedCount} media element(s)`);
}
// 5. Claim focus when valid media starts playing
function claimFocus() {
GM_setValue(STORAGE_KEY, MY_TAB_ID);
}
// 6. Listen for 'play' events on the window (Capture Phase)
// This avoids MutationObserver for better performance
window.addEventListener('play', (event) => {
const target = event.target;
// Ensure it's a valid media element
if (target instanceof HTMLMediaElement) {
// Apply smart filters
if (isPreviewVideo(target)) {
return; // Ignore preview/thumbnail videos
}
// Valid media detected, claim focus
console.log(`[MediaFocus] Valid media play detected`);
claimFocus();
}
}, true); // 'true' enables capture phase
// 7. Listen for changes from other tabs via shared storage
GM_addValueChangeListener(STORAGE_KEY, function(key, oldVal, newVal, remote) {
// If change came from a remote tab and it's not us, pause local media
if (remote && newVal && newVal !== MY_TAB_ID) {
console.log(`[MediaFocus] Tab ${newVal} is playing, pausing local media`);
pauseAllMedia();
}
});
})();