Keep Screen On - A page to prevent your device from sleeping
一个有意思的网站,这个网站只有一个按钮,打开按钮(如下图)并保持网站所在浏览器tab,就能阻止屏幕熄屏,对PC、Mac、Android、iOS 均有效。实现上,如果浏览器支持 Wake Lock API ,网站会调用 navigator.wakeLock.request(“screen”) 来请求屏幕唤醒,这样能一直保持不熄屏
如果不支持该API,网站会在后台播放一个无声的短视频来模拟用户活动,从而防止屏幕休眠。来源@卡颂

这个网站使用的是 NoSleep.js: Prevent display sleep and enable wake lock in any Android or iOS web browser. 这个包

const { webm, mp4 } = require("./media.js");
 
// Detect iOS browsers < version 10
const oldIOS = () =>
  typeof navigator !== "undefined" &&
  parseFloat(
    (
      "" +
      (/CPU.*OS ([0-9_]{3,4})[0-9_]{0,1}|(CPU like).*AppleWebKit.*Mobile/i.exec(
        navigator.userAgent
      ) || [0, ""])[1]
    )
      .replace("undefined", "3_2")
      .replace("_", ".")
      .replace("_", "")
  ) < 10 &&
  !window.MSStream;
 
// Detect native Wake Lock API support
const nativeWakeLock = () => "wakeLock" in navigator;
 
class NoSleep {
  constructor() {
    this.enabled = false;
    if (nativeWakeLock()) {
      this._wakeLock = null;
      const handleVisibilityChange = () => {
        if (this._wakeLock !== null && document.visibilityState === "visible") {
          this.enable();
        }
      };
      document.addEventListener("visibilitychange", handleVisibilityChange);
      document.addEventListener("fullscreenchange", handleVisibilityChange);
    } else if (oldIOS()) {
      this.noSleepTimer = null;
    } else {
      // Set up no sleep video element
      this.noSleepVideo = document.createElement("video");
 
      this.noSleepVideo.setAttribute("title", "No Sleep");
      this.noSleepVideo.setAttribute("playsinline", "");
 
      this._addSourceToVideo(this.noSleepVideo, "webm", webm);
      this._addSourceToVideo(this.noSleepVideo, "mp4", mp4);
 
      this.noSleepVideo.addEventListener("loadedmetadata", () => {
        if (this.noSleepVideo.duration <= 1) {
          // webm source
          this.noSleepVideo.setAttribute("loop", "");
        } else {
          // mp4 source
          this.noSleepVideo.addEventListener("timeupdate", () => {
            if (this.noSleepVideo.currentTime > 0.5) {
              this.noSleepVideo.currentTime = Math.random();
            }
          });
        }
      });
    }
  }
 
  _addSourceToVideo(element, type, dataURI) {
    var source = document.createElement("source");
    source.src = dataURI;
    source.type = `video/${type}`;
    element.appendChild(source);
  }
 
  get isEnabled() {
    return this.enabled;
  }
 
  enable() {
    if (nativeWakeLock()) {
      return navigator.wakeLock
        .request("screen")
        .then((wakeLock) => {
          this._wakeLock = wakeLock;
          this.enabled = true;
          console.log("Wake Lock active.");
          this._wakeLock.addEventListener("release", () => {
            // ToDo: Potentially emit an event for the page to observe since
            // Wake Lock releases happen when page visibility changes.
            // (https://web.dev/wakelock/#wake-lock-lifecycle)
            console.log("Wake Lock released.");
          });
        })
        .catch((err) => {
          this.enabled = false;
          console.error(`${err.name}, ${err.message}`);
          throw err;
        });
    } else if (oldIOS()) {
      this.disable();
      console.warn(`
        NoSleep enabled for older iOS devices. This can interrupt
        active or long-running network requests from completing successfully.
        See https://github.com/richtr/NoSleep.js/issues/15 for more details.
      `);
      this.noSleepTimer = window.setInterval(() => {
        if (!document.hidden) {
          window.location.href = window.location.href.split("#")[0];
          window.setTimeout(window.stop, 0);
        }
      }, 15000);
      this.enabled = true;
      return Promise.resolve();
    } else {
      let playPromise = this.noSleepVideo.play();
      return playPromise
        .then((res) => {
          this.enabled = true;
          return res;
        })
        .catch((err) => {
          this.enabled = false;
          throw err;
        });
    }
  }
 
  disable() {
    if (nativeWakeLock()) {
      if (this._wakeLock) {
        this._wakeLock.release();
      }
      this._wakeLock = null;
    } else if (oldIOS()) {
      if (this.noSleepTimer) {
        console.warn(`
          NoSleep now disabled for older iOS devices.
        `);
        window.clearInterval(this.noSleepTimer);
        this.noSleepTimer = null;
      }
    } else {
      this.noSleepVideo.pause();
    }
    this.enabled = false;
  }
}
 
module.exports = NoSleep;

这段核心代码的原理主要是首先在 contructor 构造函数中检查是否支持远程的屏幕唤醒锁 WakeLock API,如果支持,则设置相应的监听器,如果是旧的 IOS 设备,则初始化一个定时器,如果都不是,则设置一个空白没有音量的视频元素来模拟用户的行为,阻止屏幕休眠。

检测原生WakeLock API 支持:通过检查 navigator 对象中是否存在 wakeLock 属性,来确定浏览器是否支持屏幕唤醒锁。

  • 如果只是原生的WakeLock API , 就请求屏幕唤醒锁
  • 对于老旧的 IOS 设备。方法通过重定向页面和停止加载来阻止屏幕休眠
  • 对于不支持 WakeLock API的其他设备,通过循环播放一个小视频来阻止屏幕进行休眠
  • 视频资源使用 Base64 编码的 URL 形式嵌入。