// ==UserScript==
// @name 手机端浏览器功能扩展(原yandex视频横屏插件)
// @name:en Add additional functions to mobile browser
// @description 手机端类似yandex浏览器添加额外的功能。例如:双击视频全屏,左右滑动视频进度,单手手势操作等。现有手势,↓↑回到顶部,↑↓回到底部,→←后退,←→前进,←↓刷新页面,←↑新建空白页,→↓关闭标签页,→↑重新打开页面。
// @description:en Add additional functions to mobile browser(like yandex browser).For example, double-click full-screen video and gesture control.
// @version 3.3.0
// @author L.Xavier
// @namespace https://greasyfork.org/zh-CN/users/128493
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-body
// @note 功能说明:1.全屏下强制为横屏观看,支持重力感应切换方向(需要设备支持)。(新版浏览器已更新该功能,本脚本可解决某些网站以及桌面版网页仍然无法横屏播放的问题) 2.添加双击全屏功能。一些网站全屏按钮无法使视频进入全屏模式(例如m.bilibili.com),在视频播放状态下,双击全屏(支持双击退出全屏)。该功能需要关闭浏览器的"忽略网站的禁止字体缩放请求"功能,不关闭双击会缩放网页。 3.视频拖动进度功能,左右滑调整视频播放进度。 4.添加手势操作,方便单手操作浏览器。现有手势,↓↑回到顶部,↑↓回到底部,→←后退,←→前进,←↓刷新页面,←↑新建空白页,→↓关闭标签页,→↑重新打开页面。在手势设置界面可自行添加修改手势功能。(↑→↓←打开手势设置界面)
// @v1.1.0 2019-01-01 - 1.添加竖屏模式。有些视频更适宜在竖屏状态下观看,全屏状态下,手机竖直摆放时会重新回到竖屏模式。2.添加双击全屏功能。一些网站全屏按钮无法使视频进入全屏模式(例如m.bilibili.com),新增在视频播放状态下,双击全屏(支持双击退出全屏)。该功能需要关闭yandex浏览器的"忽略网站的禁止字体缩放请求"功能,不关闭双击会缩放网页。
// @v1.1.1 2019-01-02 - 优化了代码逻辑
// @v1.1.2 2019-01-12 - Yandex浏览器版本18.11.1(安卓自2018年12月26日,版本号979),切换竖屏会退出全屏状态,暂时取消竖屏模式,其他保持不变。不是该版本浏览器,有竖屏观看需要的可以安装本脚本历史版本1.1.1。
// @v1.2.1 2019-02-01 - 重写了一部分代码,使之使用更加人性化。重新添加竖屏观看模式。使用方法,摇一摇切换竖屏观看,再摇一摇可换回横屏观看(可调节灵敏度,SHAKE_THRESHOLD参数,越小越灵敏)。火狐浏览器(安卓)使用该版本,除了双击全屏,其他功能正常。
// @v1.2.2 2019-02-03 - 防止误操作,摇一摇调整为全屏下执行。
// @v1.2.3 2019-02-04 - 修复了摇一摇短时间内可能触发两次的问题。
// @v1.2.4 2019-02-09 - 修复了后加载视频,某些功能可能需要多次点击才能生效的问题。
// @v1.2.5 2019-02-13 - 调整视频标签查找逻辑和事件方法,添加了更新说明。
// @v2.0.0 2019-08-22 - 新增视频滑动进行前进后退功能;修复在已有视频标签下,重新请求视频源双击全屏无效问题。
// @v2.1.0 2019-08-24 - 完善视频滑动前进后退进度,添加进度时间小tip(可能不同手机会有位置不居中的问题,大家可以用bilibili的桌面版页面测试)。优化代码逻辑。
// @v3.0.0 2019-10-02 - 横屏播放调整为桌面版启动,旧版yandex无横屏的小伙伴可以更新版本或用本脚本旧版本。因显示优先级问题,去除视频时间tip。新添加手势功能,有爱的小伙伴可自行玩耍,添加新手势。现有手势,→↑回到顶部,→↓回到底部,→←后退,←→前进,←视频进度后退,→视频进度前进。
// @v3.1.0 2019-10-03 - 调整视频横屏播放逻辑,修复脚本与浏览器竖屏播放功能冲突问题。去除摇一摇竖屏功能。
// @v3.1.1 2019-10-03 - 调整手势灵敏度
// @v3.1.2 2019-10-03 - 修复语法错误导致的横屏逻辑失效问题
// @v3.2.0 2019-10-06 - 精简优化代码。调整视频滑动进度函数,使进度随滑动距离成指数增加,减小在短距离时的时间进度。调整手势功能代码逻辑,下个版本将添加手势功能设置UI,可自己添加修改手势操作。
// @v3.2.1 2019-10-15 - 修复因代码冲突导致的视频标签查找问题,调整查找视频标签逻辑。调整视频滑动进度判断逻辑。
// @v3.3.0 2019-11-05 - 添加手势设置UI界面(略粗糙,见谅),↑→↓←打开手势设置界面。添加并调整一部分功能代码。
// @note 注:(yandex视频横屏插件更名为手机端浏览器功能扩展) 新版yandex浏览器已支持横屏播放,但某些网站全屏播放以及电脑端模式下播放仍无法横屏,本脚本可以完美解决。
// ==/UserScript==
(function() {
'use strict';
//video标签变量
var videoEle=document.getElementsByTagName('video');
var videoPlayer=null,vi=0,videoNum=0,isReady_find=true;
var oriHway='landscape-primary',oriHgamma=0,oriHbeta=0,isLock=true;
function setVideo(e){
videoPlayer=this;
if(videoPlayer.videoWidth>videoPlayer.videoHeight){isLock=false;}
else{isLock=true;screen.orientation.unlock();}
}
//video标签事件绑定
function videoEvent(){
if(isReady_find){
isReady_find=false;
setTimeout(function(){
if(videoEle.length>videoNum || (videoPlayer && !(videoPlayer in videoEle))){
for(vi=0;vi<videoEle.length;vi++){
videoEle[vi].addEventListener('playing',setVideo,false);
}
if(!videoNum){
//双击全屏
window.addEventListener('dblclick',function(){
if(document.webkitFullscreenElement){document.webkitExitFullscreen();}
else{videoPlayer.webkitRequestFullScreen();}
},false);
//重力感应
window.addEventListener('deviceorientation',function(e){
if(!isLock){
oriHgamma=e.gamma;
oriHbeta=(e.beta>0) ? e.beta : -e.beta;
if((oriHbeta<75 || oriHbeta>105) && (oriHgamma<-25 || oriHgamma>25)){
oriHway=((oriHbeta<75 && oriHgamma<-25) || (oriHbeta>105 && oriHgamma>25)) ? 'landscape-primary' : 'landscape-secondary';
}
screen.orientation.lock(oriHway);
}
},false);
videoPlayer=videoEle[0];
}
videoNum=videoEle.length;
}
isReady_find=true;
},1000);
}
}
//手指滑动变量
var angX=0,angY=0,startX=0,startY=0,endX=0,endY=0;
var direction='',path='',_startX=0,_startY=0,isReady_touch=true;
var videoRect=null,videoX=0,videoY=0,videoW=0,videoH=0;
//手指滑动事件绑定
function sliderbackListener(){
//手指接触屏幕
window.addEventListener('touchstart',function(e){
startX=e.changedTouches[0].clientX;
startY=e.changedTouches[0].clientY;
_startX=e.changedTouches[0].screenX;
_startY=e.changedTouches[0].screenY;
videoEvent();
},false);
//手指滑动屏幕
window.addEventListener('touchmove',function(e){
e.preventDefault();
if(e.targetTouches.length==1 || e.scale==1){
endX=e.changedTouches[0].screenX;
endY=e.changedTouches[0].screenY;
angX=(endX>_startX) ? endX-_startX : _startX-endX;
angY=(endY>_startY) ? endY-_startY : _startY-endY;
if(angX>100 || angY>100){
if(angX>angY){direction=(endX>_startX) ? '→' : '←';}
else{direction=(endY>_startY) ? '↓' : '↑';}
if(path.charAt(path.length-1)!=direction){path+=direction;if(isRevise){pathEle.innerHTML=path;}}
_startX=endX;_startY=endY;
}
}
},false);
//手指离开屏幕。
window.addEventListener('touchend', function(e){
if(isReady_touch){
if(!isRevise){
endX=e.changedTouches[0].clientX;
endY=e.changedTouches[0].clientY;
angX=(endX>startX) ? endX-startX : startX-endX;
angY=(endY>startY) ? endY-startY : startY-endY;
if(path.length<2 && videoPlayer && angX>angY){
videoRect=videoPlayer.getBoundingClientRect();
videoX=videoRect.x;
videoY=videoRect.y;
videoW=videoRect.width;
videoH=videoRect.height;
if(startX>videoX && startX<(videoX+videoW) && startY>videoY && startY<(videoY+videoH)){
angX=endX-startX;
videoPlayer.currentTime+=angX*angX*angX/20000;
}
}else if(gesture[path]){
try{eval(pathFn[gesture[path]]);}
catch(error){alert('“'+path+'” 手势执行脚本错误:\n'+error+' !');}
}
}
path='';isReady_touch=false;
setTimeout(function(){isReady_touch=true;},100);
}
},false);
}
sliderbackListener();
//手势功能原始数据
var gesture={
'↑→↓←':'打开设置',
'→←':'后退',
'←→':'前进',
'↓↑':'回到顶部',
'↑↓':'回到底部',
'←↓':'刷新页面',
'←↑':'新建页面',
'→↓':'关闭页面',
'→↑':'恢复页面',
'→←→':'查看源代码'
},
pathFn={
'打开设置':'openSet();',
'后退':'history.go(-1);',
'前进':'history.go(1);',
'回到顶部':'document.documentElement.scrollTop=0;',
'回到底部':'document.documentElement.scrollTop=document.documentElement.scrollHeight;',
'刷新页面':'history.go(0);',
'新建页面':'GM_openInTab("about:blank",{active:true});',
'关闭页面':'GM_setValue("lastTab",location.href);window.close();',
'恢复页面':'GM_openInTab(GM_getValue("lastTab","about:blank"),{active:true});',
'查看源代码':'GM_openInTab("view-source:"+location.href,{active:true});'
};
//手势存储数据读取
gesture=GM_getValue('gesture',gesture);
pathFn=GM_getValue('pathFn',pathFn);
//手势操作设置UI
var gestureBox=null,gestureUl=null,gestureEle=null,pathEle=null,gestureName='',gesturePath='',isRevise=false;
function openSet(){
//页面生成
GM_addStyle('html{font-size:62.5% !important;}'+
'#gestureBox{background-color:#fff;width:100%;height:100%;position:fixed;padding:0;margin:0;top:0;left:0;overflow-y:auto;z-index:999998;}'+
'#gestureBox *{font-family:"Microsoft YaHei";margin:0;padding:0;text-align:center;}'+
'#gestureBox h1{width:60%;height:4rem;line-height:4rem;font-size:2rem;color:#0074d9;background-color:#dee6ef;margin:1rem auto;border-radius:4rem;box-shadow:.3rem .3rem 1rem #dfdfdf}'+
'#gestureBox #addGesture{width:4rem;height:4rem;margin:0 auto 1rem auto;line-height:4rem;background-color:#dee6ef;color:#032e58;font-size:3rem;border-radius:4rem;box-shadow:.1rem .1rem .5rem #dfdfdf}'+
'#gestureBox .gestureLi{height:4rem;line-height:4rem;width:100%;padding:1rem 0;border-bottom:.3rem dashed #dfdfdf}'+
'#gestureBox .gestureLi p{width:38%;height:4rem;line-height:4rem;font-size:2rem;border-left:0.6rem solid;margin-left:1%;color:#ffb400;background-color:#fff1cf;float:left}'+
'#gestureBox .gestureLi .gesturePath{float:left;width:40%;height:4rem;background-color:#f3f3f3;color:#000;font-size:2rem;line-height:4rem;box-shadow:.1rem .1rem .5rem #ccc9c9;border-radius:1rem;margin-left:3%}'+
'#gestureBox .gestureLi .delGesture{width:5rem;height:4rem;line-height:4rem;border-radius:4rem;float:right;margin-right:1%;font-size:2rem;color:#f00;text-decoration:line-through}'+
'#gestureBox #revisePath{background-color:rgba(0,0,0,.5);width:100%;height:100%;position:fixed;top:0;left:0;overflow:hidden;z-index:999999;display:none;color:#000}'+
'#gestureBox #revisePath span{width:5rem;height:5rem;font-size:5rem;line-height:5rem;position:absolute}'+
'#gestureBox #revisePath div{position:absolute;width:30%;height:3rem;line-height:3rem;font-size:3rem;bottom:15%}'+
'#gestureBox #revisePath p{position:absolute;top:15%;font-size:4rem;line-height:4rem;height:4rem;width:100%}'+
'#gestureBox #revisePath #path{top:40%;color:#ffee03;font-size:6rem}'+
'#gestureBox #editGesture{background-color:#fff;width:100%;height:100%;position:fixed;top:0;left:0;overflow:hidden;z-index:999999;display:none;color:#000}'+
'#gestureBox #editGesture p{font-size:3rem;text-align:left;margin-top:3rem;margin-left:3rem;width:100%;height:3rem;line-height:3rem}'+
'#gestureBox #editGesture #gestureName{margin-top:1rem;width:80%;height:2rem;line-height:2rem;font-size:2rem;color:#000;border:0.1rem solid #dadada;border-radius:1rem;text-align:left;padding:1rem}'+
'#gestureBox #editGesture #pathFn{width:80%;margin-top:1rem;height:40%;font-size:2rem;text-align:left;line-height:2.2rem;padding:1rem;border:0.1rem solid #dadada;border-radius:1rem;}'+
'#gestureBox #editGesture div{width:10rem;height:5rem;font-size:3rem;line-height:5rem;display:inline-block;color:#fff;background-color:#2866bd;margin:3rem 1rem 0rem 1rem}');
gestureBox=document.createElement('div');
gestureBox.setAttribute('id','gestureBox');
document.body.appendChild(gestureBox);
gestureBox.innerHTML='<h1>手势轨迹设置</h1><div id="addGesture">+</div>'+
'<div id="revisePath"><span style="top:0;left:0;text-align:left;">┌</span><span style="top:0;right:0;text-align:right;">┐</span><span style="bottom:0;left:0;text-align:left;">└</span><span style="bottom:0;right:0;text-align:right;">┘</span>'+
'<p>请滑动手指</p><p id="path"></p>'+
'<div id="clearPath" style="left:10%;">Clear</div><div id="cancleRevise" style="right:10%;">Cancle</div></div>'+
'<div id="editGesture"><p>手势名称:</p><input type="text" id="gestureName" maxlength="12" placeholder="最大输入12个字符">'+
'<p>手势路径脚本:</p><textarea id="pathFn" placeholder="全局参数方法说明→ (startX,startY):滑动初始坐标, (endX,endY):滑动结束坐标, videoPlayer:当前播放的video标签。"></textarea>'+
'<div id="saveGesture">保存</div><div id="closeEdit">关闭</div></div>';
gestureUl=document.createElement('div');
gestureUl.setAttribute('id','gestureUI');
gestureBox.appendChild(gestureUl);
pathEle=document.getElementById('path');
init();
//清除路径
document.getElementById('clearPath').addEventListener('click',function(){pathEle.innerHTML='';},false);
//修改路径
document.getElementById('cancleRevise').addEventListener('click',function(){
if(pathEle.innerHTML){
delete gesture[gesturePath];
gesture[pathEle.innerHTML]=gestureName;
GM_setValue('gesture',gesture);
init();
}
isRevise=false;
document.getElementById('revisePath').style.display='none';
},false);
//.新建手势
document.getElementById('addGesture').addEventListener('click',function(){
document.getElementById('gestureName').value='';
document.getElementById('pathFn').value='';
gestureName='';
document.getElementById('editGesture').style.display='block';
},false);
//保存手势
document.getElementById('saveGesture').addEventListener('click',function(){
if(document.getElementById('gestureName').value){
delete pathFn[gestureName];
for(vi in gesture){
if(gesture[vi]==gestureName){
gesture[vi]=document.getElementById('gestureName').value;
break;
}
}
pathFn[document.getElementById('gestureName').value]=document.getElementById('pathFn').value;
GM_setValue('pathFn',pathFn);
init();
document.getElementById('editGesture').style.display='none';
}else{
alert('请输入手势名称!');
}
},false);
//关闭编辑
document.getElementById('closeEdit').addEventListener('click',function(){
document.getElementById('editGesture').style.display='none';
},false);
}
function editGesture(e){
gestureName=this.getAttribute('name');
document.getElementById('gestureName').value=gestureName;
document.getElementById('pathFn').value=pathFn[gestureName];
document.getElementById('editGesture').style.display='block';
}
function revisePath(e){
gestureName=this.getAttribute('name');
gesturePath=this.innerHTML;
isRevise=true;
pathEle.innerHTML='';
document.getElementById('revisePath').style.display='block';
}
function delGesture(e){
gestureName=this.getAttribute('name');
delete pathFn[gestureName];
for(vi in gesture){
if(gesture[vi]==gestureName){
delete gesture[vi];
break;
}
}
GM_setValue('pathFn',pathFn);
GM_setValue('gesture',gesture);
init();
}
function init(){
gestureUl.innerHTML='';
for(vi in pathFn){
gesturePath='';
for(gestureName in gesture){
if(gesture[gestureName]==vi){
gesturePath=gestureName;
break;
}
}
gestureUl.innerHTML+='<div class="gestureLi"><p name="'+vi+'">'+vi+'</p><div class="gesturePath" name="'+vi+'">'+gesturePath+'</div><div class="delGesture" name="'+vi+'">删除</div></div>';
}
//编辑手势
gestureEle=document.querySelectorAll('#gestureBox .gestureLi p');
for(vi=0;vi<gestureEle.length;vi++){
gestureEle[vi].addEventListener('click',editGesture,false);
}
//修改路径
gestureEle=document.querySelectorAll('#gestureBox .gestureLi .gesturePath');
for(vi=0;vi<gestureEle.length;vi++){
gestureEle[vi].addEventListener('click',revisePath,false);
}
//删除手势
gestureEle=document.querySelectorAll('#gestureBox .gestureLi .delGesture');
for(vi=0;vi<gestureEle.length;vi++){
gestureEle[vi].addEventListener('click',delGesture,false);
}
}
})();