Video Sites Shortcuts

Add keyboard shortcuts to various video sites

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Video Sites Shortcuts
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license      Unlicense
// @description  Add keyboard shortcuts to various video sites
// @author       Jim Chen
// @homepage     https://jimchen.me
// @match        *://*.bilibili.com/*
// @match        *://vkvideo.ru/*
// @match        *://rutube.ru/*
// @match        *://*.1tv.ru/*
// @match        *://*.matchtv.ru/*
// @match        file://*/*
// @run-at       document-idle
// ==/UserScript==
(function () {
  document.addEventListener("keydown", function (e) {
    const activeElement = document.activeElement;
    const isInputElement = activeElement && /input|textarea/i.test(activeElement.tagName);

    // If typing in an input/textarea, only allow Escape to blur
    if (isInputElement) {
      if (e.key === "Escape") {
        activeElement.blur();
      }
      return;
    }

    const hostname = window.location.hostname;
    const isBilibili = hostname.includes("bilibili.com");
    const isVkVideo = hostname.includes("vkvideo.ru");
    const isRuTube = hostname.includes("rutube.ru");

    // --- Search Focus ---
    if (e.key === "/" || e.key === ".") {
      let searchInput = null;

      if (isBilibili) {
        const selector = window.location.href.startsWith("https://search.bilibili.com/")
          ? 'input.search-input-el[placeholder="输入关键字搜索"]' // More specific selector for search page
          : ".nav-search-input"; // General header search
        searchInput = document.querySelector(selector);
      } else if (isVkVideo) {
        // VK Video specific selector
        searchInput = document.querySelector("#video_service_search_input");
      } else if (isRuTube) {
        // RuTube specific selector for search input
        searchInput = document.querySelector(".wdp-search-line-module__input");
      }

      if (searchInput) {
        e.preventDefault(); // Prevent browser's default find action
        searchInput.focus();
        searchInput.select(); // Select existing text
      }
      return;
    }

    const video = document.querySelector("video");
    if (!video) return;

    // Number keys 0-9 for seeking
    if (/^[0-9]$/.test(e.key)) {
      e.preventDefault();
      const percentage = parseInt(e.key) * 10;
      if (video.duration && isFinite(video.duration)) {
        video.currentTime = (percentage / 100) * video.duration;
      }
      return;
    }

    let keyHandled = false;

    if (e.shiftKey && (e.key === ">" || e.key === "Ю")) {
      // Increase speed
      video.playbackRate = Math.min(video.playbackRate + 0.25, 4); // Capped at 4x
      keyHandled = true;
    } else if (e.shiftKey && (e.key === "<" || e.key === "Б")) {
      // Decrease speed
      video.playbackRate = Math.max(video.playbackRate - 0.25, 0.25); // Minimum 0.25x
      keyHandled = true;
    } else if (e.key === "j" || e.key === "J" || e.key === "о" || e.key === "О") {
      video.currentTime = Math.max(video.currentTime - 10, 0);
      keyHandled = true;
    } else if (e.key === "k" || e.key === "K" || e.key === "л" || e.key === "Л") {
      if (video.paused) {
        video.play().catch((err) => console.error("Play failed:", err)); // Add error handling for play()
      } else {
        video.pause();
      }
      keyHandled = true;
    } else if (e.key === "l" || e.key === "L" || e.key === "д" || e.key === "Д") {
      if (video.duration && isFinite(video.duration)) {
        video.currentTime = Math.min(video.currentTime + 10, video.duration);
      }
      keyHandled = true;
    }
    if (keyHandled) {
      e.preventDefault();
    }
  });
})();