您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
모바일 브라우저에서 좌우 스와이프 제스처로 동영상 탐색 및 길게 눌러 2배속 재생
- // ==UserScript==
- // @name Mobile Video Seek Gesture
- // @namespace http://tampermonkey.net/
- // @version 5.1
- // @description 모바일 브라우저에서 좌우 스와이프 제스처로 동영상 탐색 및 길게 눌러 2배속 재생
- // @author 사용자
- // @license MIT
- // @match *://*/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- const userPlaybackRates = new Map(); // 사용자 설정 배속 저장
- let longPressTimeout = null; // 길게 누름 감지
- // 제스처 공통 인터페이스 추상화
- function attachGesture(videoOrPlayer) {
- if (videoOrPlayer._gestureAdded) return;
- videoOrPlayer._gestureAdded = true;
- const isNativeVideo = videoOrPlayer instanceof HTMLVideoElement;
- const isVideoJS = !isNativeVideo
- && typeof videoOrPlayer.currentTime === 'function'
- && typeof videoOrPlayer.playbackRate === 'function';
- function getCurrentTime() {
- return isNativeVideo ? videoOrPlayer.currentTime : videoOrPlayer.currentTime();
- }
- function setCurrentTime(t) {
- if (isNativeVideo) videoOrPlayer.currentTime = t;
- else videoOrPlayer.currentTime(t);
- }
- function getDuration() {
- return isNativeVideo ? videoOrPlayer.duration : videoOrPlayer.duration();
- }
- function getPlaybackRate() {
- return isNativeVideo ? videoOrPlayer.playbackRate : videoOrPlayer.playbackRate();
- }
- function setPlaybackRate(rate) {
- if (isNativeVideo) videoOrPlayer.playbackRate = rate;
- else videoOrPlayer.playbackRate(rate);
- }
- function getContainer() {
- if (isNativeVideo) return videoOrPlayer.parentElement;
- else return videoOrPlayer.el();
- }
- const container = getContainer();
- // 오버레이 생성
- if (container.overlay) container.overlay.remove();
- const overlay = document.createElement('div');
- overlay.style.position = 'absolute';
- overlay.style.top = '50%';
- overlay.style.left = '50%';
- overlay.style.transform = 'translate(-50%, -50%)';
- overlay.style.padding = '10px 20px';
- overlay.style.backgroundColor = 'rgba(0,0,0,0.7)';
- overlay.style.color = '#fff';
- overlay.style.fontSize = '18px';
- overlay.style.textAlign = 'center';
- overlay.style.borderRadius = '8px';
- overlay.style.zIndex = '9999';
- overlay.style.display = 'none';
- overlay.style.lineHeight = '1.5'; // 줄 간격 설정
- container.appendChild(overlay);
- container.overlay = overlay; // 비디오에 오버레이 속성 추가
- // 제스처 상태
- let startX = 0, initialTime = 0, seeking = false, timeChange = 0;
- let isSpeedingUp = false; // 현재 배속 상태 확인
- let movedEnoughForSeek = false; // 스와이프 감지 여부
- // 시간 포맷
- // 시간을 시:분:초 형식으로 변환
- function formatCurrentTime(seconds) {
- let absSeconds = Math.floor(seconds); // 소수점 제거
- let hours = Math.floor(absSeconds / 3600);
- let minutes = Math.floor((absSeconds % 3600) / 60);
- let secs = absSeconds % 60;
- if (hours > 0) {
- return `${hours < 10 ? '0' : ''}${hours}:` +
- `${minutes < 10 ? '0' : ''}${minutes}:` +
- `${secs < 10 ? '0' : ''}${secs}`;
- } else {
- return `${minutes < 10 ? '0' : ''}${minutes}:` +
- `${secs < 10 ? '0' : ''}${secs}`;
- }
- }
- // 시간 변화량을 형식화
- function formatTimeChange(seconds) {
- const sign = seconds < 0 ? '-' : '+';
- let absSeconds = Math.floor(Math.abs(seconds));
- let hours = Math.floor(absSeconds / 3600);
- let minutes = Math.floor((absSeconds % 3600) / 60);
- let secs = absSeconds % 60;
- if (hours > 0) {
- return `${sign}${hours < 10 ? '0' : ''}${hours}:` +
- `${minutes < 10 ? '0' : ''}${minutes}:` +
- `${secs < 10 ? '0' : ''}${secs}`;
- } else {
- return `${sign}${minutes < 10 ? '0' : ''}${minutes}:` +
- `${secs < 10 ? '0' : ''}${secs}`;
- }
- }
- // 터치 시작 이벤트
- function onTouchStart(e) {
- startX = e.touches[0].clientX;
- initialTime = getCurrentTime();
- seeking = true;
- movedEnoughForSeek = false; // 초기화
- overlay.style.display = 'block';
- // 길게 누르면 배속 시작
- // Video.js에서는 길게 누름 기능 적용 안함
- if (!isVideoJS) {
- longPressTimeout = setTimeout(() => {
- if (!movedEnoughForSeek) { // 탐색 중이 아닐 때만 배속 적용
- userPlaybackRates.set(videoOrPlayer, getPlaybackRate()); // 기존 배속 저장
- setPlaybackRate(2.0); // 2배속
- overlay.innerHTML = `<div>2x Speed</div>`;
- isSpeedingUp = true;
- }
- }, 500); // 0.5초 이상 누르면 배속
- }
- }
- // 터치 이동 이벤트
- function onTouchMove(e) {
- if (!seeking || isSpeedingUp) return;
- const deltaX = e.touches[0].clientX - startX;
- if (Math.abs(deltaX) > 10) { // 일정 거리 이상 움직이면 탐색 모드로 간주
- movedEnoughForSeek = true;
- clearTimeout(longPressTimeout); // 길게 누름 취소
- }
- timeChange = deltaX * 0.05; // 민감도 조정
- let newTime = initialTime + timeChange;
- // 비디오 길이를 넘지 않도록 시간 범위 제한
- newTime = Math.max(0, Math.min(newTime, getDuration()));
- overlay.innerHTML = `
- <div>${formatCurrentTime(newTime)}</div>
- <div>(${formatTimeChange(timeChange)})</div>
- `;
- }
- // 터치 종료 이벤트
- function onTouchEnd() {
- seeking = false;
- clearTimeout(longPressTimeout); // 길게 누름 감지 중단
- longPressTimeout = null; // longPressTimeout 초기화
- if (isSpeedingUp) {
- setPlaybackRate(userPlaybackRates.get(videoOrPlayer) || 1.0); // 원래 속도로 복귀
- isSpeedingUp = false;
- } else if (movedEnoughForSeek) {
- let newTime = initialTime + timeChange;
- // 비디오 길이를 넘지 않도록 시간 범위 제한
- newTime = Math.max(0, Math.min(newTime, getDuration()));
- setCurrentTime(newTime);
- }
- // 오버레이 숨기기 - 바로 숨겨짐
- overlay.style.display = 'none';
- overlay.innerHTML = ''; // 이전에 표시된 내용도 비움
- }
- container.addEventListener('touchstart', onTouchStart);
- container.addEventListener('touchmove', onTouchMove);
- container.addEventListener('touchend', onTouchEnd);
- if (isNativeVideo) {
- videoOrPlayer.addEventListener('ratechange', () => {
- if (!isSpeedingUp) userPlaybackRates.set(videoOrPlayer, videoOrPlayer.playbackRate);
- });
- }
- if (isVideoJS) {
- videoOrPlayer.on('ratechange', () => {
- userPlaybackRates.set(videoOrPlayer, videoOrPlayer.playbackRate());
- });
- }
- }
- // 재귀적 Shadow DOM 포함 video 탐색
- function findAllVideos(root = document) {
- const videos = Array.from(root.querySelectorAll('video'));
- root.querySelectorAll('*').forEach(el => {
- if (el.shadowRoot) videos.push(...findAllVideos(el.shadowRoot));
- });
- return videos;
- }
- // 모든 비디오에 제스처 추가
- function scanVideos() {
- findAllVideos().forEach(v => attachGesture(v));
- if (typeof videojs !== 'undefined') {
- Object.values(videojs.getAllPlayers()).forEach(p => attachGesture(p));
- }
- }
- // DOM 변경 감지 및 비디오 발견 시 제스처 추가
- const observer = new MutationObserver(scanVideos);
- observer.observe(document.body, { childList: true, subtree: true });
- // 페이지 로딩 시 비디오 탐색
- window.addEventListener('load', scanVideos);
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址