Greasy Fork

YouTube enhancer for Via

NewPipe button, force H.264 ≤30FPS, persistent quality lock, HW acceleration.

当前为 2025-05-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTube enhancer for Via
// @namespace    https://viayoo.com/
// @version      1.1
// @license MIT
// @description  NewPipe button, force H.264 ≤30FPS, persistent quality lock, HW acceleration.
// @author       jc
// @match        *://*.youtube.com/*
// @match        *://*.youtube-nocookie.com/*
// @match        *://*.youtubekids.com/*
// @match        https://m.youtube.com/*
// @exclude      *://www.youtube.com/*/music*
// @exclude      *://music.youtube.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // 1. Force H.264 and ≤30fps
  (function () {
    const BAD = /webm|vp8|vp9|av01/i;
    const FR = /framerate=(\d+)/;
    const ms = window.MediaSource;
    if (!ms) return;
    const orig = ms.isTypeSupported.bind(ms);
    ms.isTypeSupported = type => {
      if (typeof type !== 'string' || BAD.test(type)) return false;
      const m = FR.exec(type);
      if (m && +m[1] > 30) return false;
      return orig(type);
    };
  })();

  // 2. Hide overlays
  (function () {
    const rules = [
      '.ytp-ad-overlay.ytp-overlay-loading',
      '.ytp-featured-product',
      'ytd-ad-preview-slot-renderer',
      'ytm-compact-ad'
    ];
    const s = document.createElement('style');
    s.textContent = rules.join(',') + '{display:none!important;}';
    document.head.appendChild(s);
  })();

  // 3. Floating NewPipe-style download button
  (function () {
    function makeBtn() {
      const btn = document.createElement('div');
      btn.textContent = '↓';
      Object.assign(btn.style, {
        position: 'fixed',
        bottom: '80px',
        right: '16px',
        width: '44px',
        height: '44px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '24px',
        background: '#222',
        color: '#fff',
        borderRadius: '50%',
        boxShadow: '0 2px 10px rgba(0,0,0,0.3)',
        cursor: 'pointer',
        zIndex: 999999,
        transition: 'opacity .2s, transform .2s',
        opacity: '0.6'
      });
      btn.addEventListener('mouseenter', () => btn.style.opacity = '1');
      btn.addEventListener('mouseleave', () => btn.style.opacity = '0.6');
      btn.addEventListener('click', () => {
        let id = new URL(location.href).searchParams.get('v');
        if (!id) {
          const m = location.pathname.match(/\/shorts\/([^\/?&]+)/);
          id = m && m[1];
        }
        if (id) {
          location.href = `intent://www.youtube.com/watch?v=${id}#Intent;scheme=https;package=org.schabi.newpipe;end`;
        }
      });
      document.body.appendChild(btn);
    }
    if (document.body) makeBtn();
    else new MutationObserver((_, obs) => {
      if (document.body) {
        obs.disconnect();
        makeBtn();
      }
    }).observe(document.documentElement, { childList: true });
  })();

  // 4. Persistent quality setter with fallback
  (function () {
    const QUALITY_KEY = 'yt_quality_preference';

    // Save quality on selection
    const observeMenu = new MutationObserver(() => {
      document.querySelectorAll('.ytp-menuitem-label').forEach(el => {
        if (/^\d+p$/.test(el.textContent)) {
          el.addEventListener('click', () => {
            localStorage.setItem(QUALITY_KEY, el.textContent);
          }, { once: true });
        }
      });
    });

    observeMenu.observe(document.documentElement, { childList: true, subtree: true });

    // Apply saved quality
    function applyQuality() {
      const q = localStorage.getItem(QUALITY_KEY);
      if (!q) return;
      const trySet = () => {
        const player = document.querySelector('video')?.player_ || window.ytplayer?.config?.args;
        if (!player) return;
        try {
          if (typeof player.setPlaybackQuality === 'function') {
            player.setPlaybackQuality(q);
            player.setPlaybackQualityRange && player.setPlaybackQualityRange(q);
          } else if (window.yt && yt.player && yt.player.getPlayerByElement) {
            const p = yt.player.getPlayerByElement(document.querySelector('#movie_player'));
            if (p && p.setPlaybackQuality) p.setPlaybackQuality(q);
          }
        } catch (e) {}
      };
      const t = setInterval(() => {
        trySet();
      }, 1000);
      setTimeout(() => clearInterval(t), 8000);
    }

    document.addEventListener('yt-navigate-finish', applyQuality);
    window.addEventListener('load', applyQuality);
  })();

  // 5. Mobile-only hardware acceleration and bottom bar pin
  if (location.hostname === 'm.youtube.com') {
    const css = `
      ytm-app>.page-manager,
      ytm-browse-response-renderer,
      ytm-search-response-renderer,
      ytm-section-list-renderer,
      ytm-player-fragment,
      ytm-watch-player-renderer,
      ytm-player-layout,
      .html5-video-player,
      ytm-reel-player-overlay-renderer,
      ytm-shorts-player-renderer,
      ytm-reel-video-renderer,
      ytm-reel-watch-sequence-renderer {
        transform: translateZ(0);
      }
      ytm-pivot-bar-renderer {
        position: fixed!important;
        bottom: 0!important;
        left: 0!important;
        right: 0!important;
        width: 100%!important;
        z-index: 2000!important;
        background-color: var(--yt-spec-brand-background-solid,#fff)!important;
        border-top: 1px solid var(--yt-spec-10-percent-layer,rgba(0,0,0,0.1))!important;
      }
      body,
      ytm-app,
      ytm-app>.page-manager {
        padding-bottom: 56px!important;
      }
    `;
    const style = document.createElement('style');
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);

    document.body.addEventListener('click', e => {
      const a = e.target.closest('a[href]');
      if (a) {
        const u = a.href;
        if ((u.includes('/watch?v=') || u.includes('/shorts/')) &&
          !a.closest('ytm-pivot-bar-renderer') &&
          !a.closest('ytm-player-controls-overlay-renderer') &&
          !a.closest('.player-controls-middle')) {
          e.preventDefault();
          e.stopPropagation();
          window.open(u, '_blank');
        }
      }
    }, true);
  }

})();