feat: hide with controls timer for non-youtube websites, site-specific rules for it too

This commit is contained in:
2026-03-29 15:13:59 -04:00
parent 03f5dbaa96
commit 14e37c62d5
4 changed files with 134 additions and 18 deletions
+4 -2
View File
@@ -24,14 +24,16 @@
/* YouTube auto-hide feature: fade controller with YouTube's controls */
/* When the wrapper has ytp-autohide class, hide it (unless vsc-hidden overrides) */
.vsc-controller.ytp-autohide:not(.vsc-hidden) {
.vsc-controller.ytp-autohide:not(.vsc-hidden),
.vsc-controller.vsc-idle-hidden:not(.vsc-hidden) {
visibility: hidden;
transition: opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
}
/* Show it temporarily when it has vsc-show class */
.vsc-controller.ytp-autohide.vsc-show:not(.vsc-hidden) {
.vsc-controller.ytp-autohide.vsc-show:not(.vsc-hidden),
.vsc-controller.vsc-idle-hidden.vsc-show:not(.vsc-hidden) {
visibility: visible;
opacity: 1;
}
+74 -5
View File
@@ -14,6 +14,8 @@ var tc = {
audioBoolean: false,
startHidden: false,
hideWithYouTubeControls: false,
hideWithControls: false,
hideWithControlsTimer: 2.0,
controllerLocation: "top-left",
controllerOpacity: 0.3,
keyBindings: [],
@@ -952,7 +954,13 @@ chrome.storage.sync.get(tc.settings, function (storage) {
tc.settings.audioBoolean = Boolean(storage.audioBoolean);
tc.settings.enabled = Boolean(storage.enabled);
tc.settings.startHidden = Boolean(storage.startHidden);
tc.settings.hideWithYouTubeControls = Boolean(storage.hideWithYouTubeControls);
tc.settings.hideWithControls =
typeof storage.hideWithControls !== "undefined"
? Boolean(storage.hideWithControls)
: Boolean(storage.hideWithYouTubeControls);
tc.settings.hideWithControlsTimer =
Math.min(15, Math.max(0.1, Number(storage.hideWithControlsTimer) || 2.0));
tc.settings.hideWithYouTubeControls = tc.settings.hideWithControls;
tc.settings.controllerLocation = normalizeControllerLocation(
storage.controllerLocation
);
@@ -1195,6 +1203,10 @@ function defineVideoController() {
this.youTubeAutoHideObserver.disconnect();
this.youTubeAutoHideObserver = null;
}
if (this.genericAutoHideCleanup) {
this.genericAutoHideCleanup();
this.genericAutoHideCleanup = null;
}
if (this.div) this.div.remove();
if (this.restoreSpeedTimer) clearTimeout(this.restoreSpeedTimer);
if (this.video) {
@@ -1374,6 +1386,57 @@ function defineVideoController() {
log("YouTube auto-hide observer setup complete", 4);
};
tc.videoController.prototype.setupGenericAutoHide = function (wrapper) {
if (!wrapper) return;
const video = this.video;
let timer = null;
const resetTimer = () => {
wrapper.classList.remove("vsc-idle-hidden");
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
// Only hide if the video is not paused
// (Many players keep controls visible while paused)
// However, the user said "Reveal on every mouse and keyboard input"
// and "auto-hidden after timespan".
// We'll follow the timer strictly.
wrapper.classList.add("vsc-idle-hidden");
log("Generic hide: controller hidden due to inactivity", 5);
}, tc.settings.hideWithControlsTimer * 1000);
};
// Initial show/timer
resetTimer();
// The wrapper covers the player area on most sites due to inject.css styles,
// but we listen on both the video and the wrapper for maximum coverage.
const activityEvents = ["mousemove", "mousedown", "keydown", "touchstart"];
activityEvents.forEach((type) => {
video.addEventListener(type, resetTimer, { passive: true });
wrapper.addEventListener(type, resetTimer, { passive: true });
});
// Also reset timer on play/pause events to ensure sync when player state changes
video.addEventListener("play", resetTimer, { passive: true });
video.addEventListener("pause", resetTimer, { passive: true });
// Store a cleanup function
this.genericAutoHideCleanup = () => {
if (timer) clearTimeout(timer);
activityEvents.forEach((type) => {
video.removeEventListener(type, resetTimer);
wrapper.removeEventListener(type, resetTimer);
});
video.removeEventListener("play", resetTimer);
video.removeEventListener("pause", resetTimer);
};
log(`Generic auto-hide setup complete with ${tc.settings.hideWithControlsTimer}s timer`, 4);
};
tc.videoController.prototype.initializeControls = function () {
const doc = this.video.ownerDocument;
const speed = this.video.playbackRate.toFixed(2);
@@ -1476,9 +1539,13 @@ function defineVideoController() {
controller.addEventListener("click", (e) => e.stopPropagation(), false);
controller.addEventListener("mousedown", (e) => e.stopPropagation(), false);
// Setup YouTube auto-hide observer if enabled
if (tc.settings.hideWithYouTubeControls && isOnYouTube()) {
this.setupYouTubeAutoHide(wrapper);
// Setup auto-hide observers if enabled
if (tc.settings.hideWithControls) {
if (isOnYouTube()) {
this.setupYouTubeAutoHide(wrapper);
} else {
this.setupGenericAutoHide(wrapper);
}
}
var fragment = doc.createDocumentFragment();
@@ -1607,7 +1674,9 @@ function applySiteRuleOverrides() {
"rememberSpeed",
"forceLastSavedSpeed",
"audioBoolean",
"controllerOpacity"
"controllerOpacity",
"hideWithControls",
"hideWithControlsTimer"
];
siteSettings.forEach((key) => {
+19 -3
View File
@@ -141,10 +141,16 @@
<input id="startHidden" type="checkbox" />
</div>
<div class="row">
<label for="hideWithYouTubeControls">Hide with controls (YouTube)<br />
<em>Fade controller in/out with YouTube's video interface</em>
<label for="hideWithControls">Hide with controls (All sites)<br />
<em>Fade controller in/out with video interface (perfect sync on YouTube; idle-based elsewhere)</em>
</label>
<input id="hideWithYouTubeControls" type="checkbox" />
<input id="hideWithControls" type="checkbox" />
</div>
<div class="row">
<label for="hideWithControlsTimer">Auto-hide timer (seconds)<br />
<em>Seconds of inactivity before hiding (0.1 - 15). Used for non-YouTube sites.</em>
</label>
<input id="hideWithControlsTimer" type="text" placeholder="2" />
</div>
<div class="row">
<label for="controllerLocation">Default controller location</label>
@@ -248,6 +254,16 @@
<label>Controller opacity:</label>
<input type="text" class="site-controllerOpacity" />
</div>
<div class="site-rule-option">
<label>
<input type="checkbox" class="site-hideWithControls" />
Hide with controls (idle-based)
</label>
</div>
<div class="site-rule-option">
<label>Auto-hide timer (0.1 - 15s):</label>
<input type="text" class="site-hideWithControlsTimer" />
</div>
<div class="site-rule-shortcuts">
<label>
<input type="checkbox" class="override-shortcuts" />
+37 -8
View File
@@ -151,6 +151,8 @@ var tcDefaults = {
audioBoolean: false,
startHidden: false,
hideWithYouTubeControls: false,
hideWithControls: false,
hideWithControlsTimer: 2.0,
controllerLocation: "top-left",
forceLastSavedSpeed: false,
enabled: true,
@@ -575,7 +577,16 @@ function save_options() {
settings.audioBoolean = document.getElementById("audioBoolean").checked;
settings.enabled = document.getElementById("enabled").checked;
settings.startHidden = document.getElementById("startHidden").checked;
settings.hideWithYouTubeControls = document.getElementById("hideWithYouTubeControls").checked;
settings.hideWithControls = document.getElementById("hideWithControls").checked;
settings.hideWithControlsTimer =
Math.min(15, Math.max(0.1, parseFloat(document.getElementById("hideWithControlsTimer").value) || tcDefaults.hideWithControlsTimer));
// Sync back to the legacy key if it exists, for backward compatibility
settings.hideWithYouTubeControls = settings.hideWithControls;
if (settings.hideWithControlsTimer < 0.1) settings.hideWithControlsTimer = 0.1;
if (settings.hideWithControlsTimer > 15) settings.hideWithControlsTimer = 15;
settings.controllerLocation = normalizeControllerLocation(
document.getElementById("controllerLocation").value
);
@@ -615,7 +626,9 @@ function save_options() {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
{ key: "controllerOpacity", type: "text" }
{ key: "controllerOpacity", type: "text" },
{ key: "hideWithControls", type: "checkbox" },
{ key: "hideWithControlsTimer", type: "text" }
];
siteSettings.forEach((s) => {
@@ -829,20 +842,27 @@ function createSiteRule(rule) {
{ key: "rememberSpeed", type: "checkbox" },
{ key: "forceLastSavedSpeed", type: "checkbox" },
{ key: "audioBoolean", type: "checkbox" },
{ key: "controllerOpacity", type: "text" }
{ key: "controllerOpacity", type: "text" },
{ key: "hideWithControls", type: "checkbox" },
{ key: "hideWithControlsTimer", type: "text" }
];
settings.forEach((s) => {
var input = ruleEl.querySelector(`.site-${s.key}`);
if (!input) return;
var value;
if (rule && rule[s.key] !== undefined) {
value = rule[s.key];
} else {
// Initialize with current global value
if (s.type === "checkbox") {
value = document.getElementById(s.key).checked;
} else {
value = document.getElementById(s.key).value;
var globalInput = document.getElementById(s.key);
if (globalInput) {
if (s.type === "checkbox") {
value = globalInput.checked;
} else {
value = globalInput.value;
}
}
}
@@ -886,7 +906,16 @@ function restore_options() {
document.getElementById("audioBoolean").checked = storage.audioBoolean;
document.getElementById("enabled").checked = storage.enabled;
document.getElementById("startHidden").checked = storage.startHidden;
document.getElementById("hideWithYouTubeControls").checked = storage.hideWithYouTubeControls;
// Migration/Normalization for hideWithControls
const hideWithControls = typeof storage.hideWithControls !== "undefined"
? storage.hideWithControls
: storage.hideWithYouTubeControls;
document.getElementById("hideWithControls").checked = hideWithControls;
document.getElementById("hideWithControlsTimer").value =
storage.hideWithControlsTimer || tcDefaults.hideWithControlsTimer;
document.getElementById("controllerLocation").value =
normalizeControllerLocation(storage.controllerLocation);
document.getElementById("controllerOpacity").value =