// ==UserScript== // @name Stay Awake! (Modern NoSleep) // @name:zh-CN 保持唤醒!(新防休眠) // @namespace https://github.com/ymhomer/ym_Userscript // @version 0.1.2 // @description Prevents the screen from sleeping. Uses Tampermonkey menu if available, otherwise a floating button. // @description:zh-CN 防止屏幕自动休眠。优先使用Tampermonkey菜单切换,若无则使用悬浮按钮。采用现代Wake Lock API,并备有视频播放方案。 // @author ymhomer // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-idle // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ---------------------------------------------------- // Core Class: ModernNoSleep // ---------------------------------------------------- class ModernNoSleep { constructor() { this.enabled = false; this._wakeLock = null; this._noSleepVideo = null; this.wakeLockType = 'wakeLock' in navigator ? 'modern' : 'legacy'; if (this.wakeLockType === 'modern') { document.addEventListener('visibilitychange', this._onVisibilityChange.bind(this)); document.addEventListener('fullscreenchange', this._onVisibilityChange.bind(this)); } } async enable() { if (this.enabled) return; try { if (this.wakeLockType === 'modern') { this._wakeLock = await navigator.wakeLock.request('screen'); this._wakeLock.addEventListener('release', () => { if (this.enabled) { this.enabled = false; console.log('Wake Lock was released by the system.'); if(this.onRelease) this.onRelease(); } }); } else { this._createLegacyVideo(); await this._noSleepVideo.play(); } this.enabled = true; console.log(`NoSleep enabled (${this.wakeLockType}).`); } catch (err) { this.enabled = false; console.error(`Failed to enable NoSleep: ${err.name}, ${err.message}`); throw err; } } disable() { if (!this.enabled) return; if (this.wakeLockType === 'modern' && this._wakeLock) { this._wakeLock.release(); this._wakeLock = null; } else if (this.wakeLockType === 'legacy' && this._noSleepVideo) { this._noSleepVideo.pause(); } this.enabled = false; console.log('NoSleep disabled.'); } get isEnabled() { return this.enabled; } _onVisibilityChange() { if (this.enabled && document.visibilityState === 'visible') { this.enable().catch(() => {}); } } _createLegacyVideo() { if (this._noSleepVideo) return; const minimalMp4 = 'data:video/mp4;base64,AAAAHGZ0eXBNNFYgAAACAGlzb21pc28yYXZjMQAAAAhmcmVlAAAAG21kYXQAAAGzABAHAAABthABGWAAAAABAAA2Z2VzZHMAAAAAAQABAAEAAAA2YXZjMQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAEAAQABAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAc3R0cwAAAAAAAAABAAAAAQABAAABAAAAHHN0c2QAAAAAAAAAAQAAAFhzdHNjAAAAAAAAAAEAAAABAAAAAQAAAAEAAABoc3RzegAAAAAAAAAAAAAAAQAAAAEAAAAUc3RjbwAAAAAAAAABAAAAMAAAAGB1ZHRhAAAAWG1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAAK2lsc3QAAAAjqXRvbwAAABtkYXRhAAAAAQAAAABMYXZmYzgxLjEyLjEwMA=='; this._noSleepVideo = document.createElement('video'); this._noSleepVideo.setAttribute('title', 'No Sleep'); this._noSleepVideo.setAttribute('playsinline', ''); this._noSleepVideo.setAttribute('loop', ''); this._noSleepVideo.muted = true; const source = document.createElement('source'); source.src = minimalMp4; source.type = 'video/mp4'; this._noSleepVideo.appendChild(source); } } // ---------------------------------------------------- // User Interface Implementation // ---------------------------------------------------- const noSleep = new ModernNoSleep(); let menuCommandId = null; // A flag to check if the floating UI has been created let isFloatingUiCreated = false; // Check if running in Tampermonkey const isTampermonkey = typeof GM_registerMenuCommand !== 'undefined'; if (isTampermonkey) { // Function to create the floating UI const createFloatingUi = (message = 'Screen lock is inactive. Click to enable.') => { if (isFloatingUiCreated) return; const container = document.createElement('div'); container.id = 'modern-nosleep-widget'; const messageBox = document.createElement('div'); messageBox.id = 'nosleep-message-box'; messageBox.innerHTML = `${message}`; messageBox.style.display = 'none'; // Initially hidden const toggleButton = document.createElement('button'); toggleButton.id = 'nosleep-toggle-btn'; toggleButton.title = 'Toggle Screen Wake Lock'; toggleButton.innerHTML = '🌙'; const hideButton = document.createElement('button'); hideButton.id = 'nosleep-hide-btn'; hideButton.title = 'Hide for this session'; hideButton.innerHTML = '×'; container.appendChild(messageBox); container.appendChild(toggleButton); container.appendChild(hideButton); document.body.appendChild(container); GM_addStyle(` #modern-nosleep-widget { position: fixed; bottom: 20px; right: 20px; z-index: 999999; display: flex; flex-direction: column; gap: 5px; align-items: flex-end; } #modern-nosleep-widget button { background-color: rgba(0, 0, 0, 0.7); color: white; border: 1px solid rgba(255, 255, 255, 0.5); border-radius: 50%; cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: background-color 0.2s, transform 0.2s; font-family: sans-serif; } #modern-nosleep-widget button:hover { background-color: rgba(0, 0, 0, 0.9); transform: scale(1.1); } #nosleep-toggle-btn { width: 48px; height: 48px; font-size: 24px; line-height: 48px; } #nosleep-toggle-btn.active { background-color: #f39c12; } #nosleep-hide-btn { width: 24px; height: 24px; font-size: 16px; line-height: 22px; } #nosleep-message-box { background-color: rgba(255, 0, 0, 0.7); color: white; padding: 8px 12px; border-radius: 8px; font-size: 14px; margin-bottom: 5px; max-width: 200px; text-align: right; transition: opacity 0.5s ease-in-out; } `); const updateButtonState = (enabled) => { if (enabled) { toggleButton.innerHTML = '☀️'; toggleButton.classList.add('active'); toggleButton.title = 'Screen lock is active. Click to disable.'; messageBox.style.display = 'none'; } else { toggleButton.innerHTML = '🌙'; toggleButton.classList.remove('active'); toggleButton.title = 'Screen lock is inactive. Click to enable.'; } }; toggleButton.addEventListener('click', async () => { if (noSleep.isEnabled) { noSleep.disable(); updateButtonState(false); } else { try { await noSleep.enable(); updateButtonState(true); } catch (err) { messageBox.innerHTML = `Could not activate wake lock. Your browser might not support it or requires a specific user interaction.`; messageBox.style.display = 'block'; setTimeout(() => { messageBox.style.display = 'none'; }, 5000); updateButtonState(false); } } }); hideButton.addEventListener('click', () => { container.style.display = 'none'; }); noSleep.onRelease = () => { updateButtonState(false); }; isFloatingUiCreated = true; }; // Register Tampermonkey menu command const updateMenu = () => { if (menuCommandId) GM_unregisterMenuCommand(menuCommandId); menuCommandId = GM_registerMenuCommand( noSleep.isEnabled ? '🌙 Disable Screen Wake Lock' : '☀️ Enable Screen Wake Lock', async () => { if (noSleep.isEnabled) { noSleep.disable(); } else { try { await noSleep.enable(); } catch (err) { // On failure, create floating UI and show error message createFloatingUi(); const messageBox = document.getElementById('nosleep-message-box'); if (messageBox) { messageBox.innerHTML = `Could not activate wake lock. Your browser might not support it or requires a specific user interaction.`; messageBox.style.display = 'block'; setTimeout(() => { messageBox.style.display = 'none'; }, 5000); } } } updateMenu(); } ); }; noSleep.onRelease = updateMenu; updateMenu(); } else { // Fallback to floating button UI directly if not in Tampermonkey createFloatingUi(); } })();