您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
全屏横屏、快进快退、长按倍速,为不可描述的网站特别设计,兼容性很强。使用前请先关闭同类横屏或手势脚本,以避免冲突。
当前为
// ==UserScript== // @name 俺的手机视频脚本 // @description 全屏横屏、快进快退、长按倍速,为不可描述的网站特别设计,兼容性很强。使用前请先关闭同类横屏或手势脚本,以避免冲突。 // @version 1.0 // @author shopkeeperV // @namespace http://greasyfork.icu/zh-CN/users/150069 // @match http*://*/* // ==/UserScript== /*jshint esversion: 8*/ (function () { 'use strict'; //部分网站阻止视频操作层触摸事件传播,需要定制监听目标,默认是document let listenTarget = document; //是否是定制网站 let diy = true; if (window.location.host === "m.youtube.com") { if (window.location.href.search("watch") >= 0) { //视频页下面的推荐视频点击后不是跳转链接,而是用AJAX来重新渲染页面,这样,需要重新给视频操控层绑定事件 let timer; let observer; let refresh = function (mutations) { //youtube视频在脚本执行时还没加载,需要个定时器循环获取状态 if (mutations && mutations[0].removedNodes.length > 0) { //删除和添加子节点个执行一次,需要中断其中任意一个 return; } timer = setInterval(() => { let listenTargetArray = document.getElementsByClassName("player-controls-background"); //该元素通过开发者工具元素页面【中断于->删除节点】确认 let containerArray = document.getElementsByClassName("page-container"); if (listenTargetArray.length > 0 && containerArray.length > 0) { //视频已加载 listenTarget = listenTargetArray[0]; listen(); if (!observer) { observer = new MutationObserver(refresh); observer.observe(containerArray[0], {childList: true}); } //清除定时器 clearInterval(timer); } }, 500); } refresh(); } else { //非视频页面 return; } } //通用 else { diy = false; //没有定制的网站将监听document的touchstart事件 listen(); } function listen() { //所有逻辑都是在手触摸屏幕后开始执行,在手指离开后对页面没有残留 //虽然这样更消耗性能,但是对不同的网站兼容性更强 listenTarget.addEventListener("touchstart", (e) => { let startX; let startY; let endX; let endY; let videoElement; let target = e.target; //用于有操控层的网站,保存的是视频与操控层适当尺寸下的最大共同祖先节点,确认后需要在后代内搜索视频元素 //同时在非diy的情况下,作为脚本监听手指移动和抬起的目标 let biggestContainer; //实际控制层 let controller; //当触摸的不是视频元素,可能是非视频相关组件,或视频的操控层 if (target.tagName !== "VIDEO") { let _width = target.clientWidth; let _height = target.clientHeight; let parents = []; let temp = target; while (true) { temp = temp.parentElement; if (temp.clientWidth >= _width && temp.clientHeight >= _height) { //是一个可用的祖先节点 parents.push(temp) //youtube全屏时,有时没有body标签 if (temp.tagName === "BODY" || temp.tagName === "HTML" || temp.parentElement.clientWidth > _width * 1.2 || temp.parentElement.clientHeight > _height * 1.2) { //已找到所有符合条件的祖先节点,取最后一个 biggestContainer = parents.pop(); parents = null; break; } } } //尝试获取视频元素 let videoArray = biggestContainer.getElementsByTagName("video"); if (videoArray.length > 0) { videoElement = videoArray[0]; if (diy) { controller = listenTarget; } else { controller = biggestContainer; } if (videoArray.length > 1) { console.log("触摸位置找到不止一个视频。"); } } else { //非视频相关组件 return; } } //触摸的是视频元素,则一切清晰明了 else { videoElement = target; controller = videoElement; } //一个合适尺寸的最近祖先元素用于显示手势信息 let noticeContainer = findNoticeContainer(); //指示器元素 let notice; //视频快进快退量 let timeChange = 0; //1表示右滑快进,2表示左滑快退,方向一旦确认就无法更改 let direction; //禁止长按视频呼出浏览器菜单,为长按倍速做准备 videoElement.addEventListener("contextmenu", (e) => { e.preventDefault(); }) //长按倍速定时器 let rateTimer = setTimeout(() => { videoElement.playbackRate = 3; //禁止再快进快退 controller.removeEventListener("touchmove", touchmoveHandler); //显示notice notice.innerText = "x3"; notice.style.display = "block"; }, 800); //添加提示元素 if (noticeContainer) { notice = document.createElement("div"); let noticeWidth = 100;//未带单位,后面需要加单位 let noticeHeight = 30; let noticeLeft = noticeContainer.clientWidth / 2 - noticeWidth / 2; notice.style.cssText = "position:absolute;display:none;z-index:99999;top:10px;" + "text-align:center;opacity:0.5;background-color:black;color:white;" + "font:16px/1.8 sans-serif;letter-spacing:normal;border-radius:4px;"; notice.style.width = noticeWidth + "px"; notice.style.height = noticeHeight + "px"; notice.style.left = noticeLeft + "px"; noticeContainer.appendChild(notice); } else { //怎么可能有视频没有div包着啊 console.log("该视频没有可以用于给notice定位的祖先元素。"); } if (e.touches.length === 1) { //单指触摸,记录位置 startX = Math.floor(e.touches[0].clientX); startY = Math.floor(e.touches[0].clientY); endX = 0; endY = 0; } controller.addEventListener("touchmove", touchmoveHandler); controller.addEventListener("touchend", touchendHandler); function findNoticeContainer() { let temp = videoElement; let _width = videoElement.clientWidth; let _height = videoElement.clientHeight; while (true) { //寻找最近的长宽大于>=视频的祖先节点 if (temp.parentElement.clientWidth >= _width && temp.parentElement.clientHeight >= _height) { return temp.parentElement; } else { temp = temp.parentElement; } } } function getClearTimeChange(timeChange) { timeChange = Math.abs(timeChange); let minute = Math.floor(timeChange / 60); let second = timeChange % 60; return (minute === 0 ? "" : (minute + "min")) + second + "s"; } function touchmoveHandler(moveEvent) { //触摸屏幕后,0.8s内如果有移动,清除长按定时事件 clearTimeout(rateTimer); if (moveEvent.touches.length === 1) { //仅支持单指触摸,记录位置 endX = Math.floor(moveEvent.touches[0].clientX); endY = Math.floor(moveEvent.touches[0].clientY); } //由第一次移动确认手势方向,就不再变更 //10个像素起 if (endX > startX + 10) { //快进 if (!direction) { //首次移动,记录方向 direction = 1; } if (direction === 1) { //方向未变化 timeChange = endX - startX - 10; } else { timeChange = 0; } } else if (endX < startX - 10) { //快退 if (!direction) { //首次移动,记录方向 direction = 2; } if (direction === 2) { //方向未变化 timeChange = endX - startX + 10; } else { timeChange = 0; } } //未到阈值或垂直方向上的移动不显示 if (direction) { notice.style.display = "block"; notice.innerText = (direction === 1 ? ">>>" : "<<<") + getClearTimeChange(timeChange); } } function touchendHandler() { if (endX === 0) { console.log("长按了"); if (rateTimer) { //定时器也许已经执行,此时清除也没关系 clearTimeout(rateTimer); videoElement.playbackRate = 1; } } else { console.log("横向位移" + (endX - startX)); console.log("纵向位移" + (endY - startY)); //快进 if (timeChange && timeChange !== 0) { videoElement.currentTime += timeChange; } } controller.removeEventListener("touchmove", touchmoveHandler); controller.removeEventListener("touchend", touchendHandler); if (notice) notice.remove(); } }); } //全屏横屏模块 //利用resize事件监听全屏动作,fullscreenchange部分网站监听不到 window.addEventListener("resize", () => { //不设置延迟很容易黑屏 setTimeout(fullscreenHandler, 500); }, true/*设为在捕获阶段触发,可以在视频就绪后触发一次*/); async function fullscreenHandler() { //获取全屏元素,查找视频,判断视频长宽比来锁定方向 let _fullscreenElement = document.fullscreenElement; //退出全屏、top内iframe大小调整、已横屏三种情况直接返回 //一个文档内(iframe也是一个文档)的全屏动作,会触发两次resize,全屏时一次,转向时一次(lock()方法) if (!_fullscreenElement || _fullscreenElement.tagName === "IFRAME" || screen.orientation.type.search("landscape") >= 0) { return; } let videoElement; if (_fullscreenElement.tagName !== "VIDEO") { //最大的全屏元素不是视频本身,需要寻找视频元素 let videoArray = _fullscreenElement.getElementsByTagName("video"); if (videoArray.length > 0) { videoElement = videoArray[0]; } } else videoElement = _fullscreenElement; if (videoElement && videoElement.videoHeight < videoElement.videoWidth) { //高度小于宽度,需要转向,landscape会自动调用陀螺仪 await screen.orientation.lock("landscape"); } } })();