Facebook - Video Resume

When playing a Facebook video, this script stores the current time (minus ten seconds) so that when the page is refreshed (be-it after reopening the browser or manual refresh) or after it gets reset due to bad internet connection, the video resumes from where it stopped.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @description     When playing a Facebook video, this script stores the current time (minus ten seconds) so that when the page is refreshed (be-it after reopening the browser or manual refresh) or after it gets reset due to bad internet connection, the video resumes from where it stopped.
// @name            Facebook - Video Resume
// @namespace       https://www.facebook.com
// @include         https://www.facebook.com/*
// @version         3
// @grant    				GM.getValue
// @grant    				GM.setValue
// @grant    				GM.notification
// @run-at document-idle
// ==/UserScript==

const {max, floor} = Math;

const interval = 2e3;
var intervalId;
const rewind = 10;

window.eval(`const _wr = (type) => {
    const orig = history[type];
    return function() {
        const rv = orig.apply(this, arguments);
        const e = new Event(type);
        e.arguments = arguments;
        window.dispatchEvent(e);
        return rv;
    };
};
history.pushState = _wr('pushState'), history.replaceState = _wr('replaceState');
`);

String.prototype.firstMatch = function(regexp) {
  const match = this.match(regexp);
  return match && match[1];
}

const isVideoPath = (location) => /\/.*\/videos(\/.*)?\/\d+/.test(location.pathname) || /v=\d+/.test(location.search);
var location = window.location;

const getVideoId = () => location.pathname.firstMatch(/\/.*\/videos(?:\/.*)?\/(\d+)/) || location.search.firstMatch(/v=(\d+)/);
const getTimeKey = () => `${getVideoId()}fb-video`;
const setTime = (time) => GM.setValue(getTimeKey(), time);
const getTime = () => GM.getValue(getTimeKey());


const initialize = (video) => {
  console.debug(getTimeKey(), video);
  if (video) {
    getTime().then(time => {
      video.currentTime = time;
    });


    intervalId = setInterval(() => {
      const time = max(0, floor(video.currentTime) - rewind);
      //console.debug(time);
      setTime(time);
    }, interval);



    video.addEventListener('error', (e) => {
      clearInterval(intervalId);
      setTimeout(() => initialize(video), 1e3);
    });

    video.addEventListener('ended', (e) => {
      setTime(0);
    });
  }
}

if (isVideoPath(location)) {
	initialize(document.querySelector('video'));
}

const pathChanged = (e) => {
  location = window.location;
  clearInterval(intervalId);
}

new MutationObserver(ms => {
  const nodes = ms.flatMap(m => Array.from(m.addedNodes)).filter(n => n.querySelector)
  const video = nodes.map(n => n.querySelector('video')).find(v => v);
  if (isVideoPath(location) && video) {
    console.debug(video);
    clearInterval(intervalId);
		initialize(video);
  }
}).observe(document.body, {childList: true, subtree: true});

window.addEventListener('pushState', pathChanged);
window.addEventListener('replaceState', pathChanged);
window.addEventListener('popstate', pathChanged);

window.addEventListener('beforeunload', (e) => clearInterval(intervalId));