阶段4:格式自动识别 + flv.js 替换为 mpegts.js + 移动端 5 项修复 + 自动化冒烟测试
- 按 URL 扩展名自动识别插件(.m3u8→hls.js,.flv/.ts/.m2ts→mpegts.js),iOS 上 m3u8 仍走原生 HLS - flv 播放改由 mpegts.js 承担,移除 flv.js 依赖,plug:'flv.js' 保留兼容别名 - 修复 plug:'dash.js' 调用不存在函数的死代码 bug - mpegts 能力判断由 mseLivePlayback 放宽为 isSupported()(修复部分浏览器点播被误杀) - 移动端:iOS 原生全屏状态同步、iPad/iPod UA 识别、X5 同层播放属性、进度/音量条触摸拖动、触摸坐标滚动偏移修正 - 新增 Playwright 冒烟测试(npm test)与支持 Range 的本地服务器(npm run serve) - 文档说明:测试禁用 python http.server(不支持 Range,mp4 无法 seek)
This commit is contained in:
+108
-18
@@ -59,6 +59,8 @@ var language = languageDefault;
|
||||
var CT=null,CK=null,CM=null,CV=null;//CT=播放器容器,页面中已存的,CT>CK>CM>CV>video
|
||||
var pugPlayer=null;//插件播放器
|
||||
var hls=null;//播放hls
|
||||
var plugAuto='';//自动识别设置的插件名,用于与用户显式指定的plug区分
|
||||
var videoNativeFull=false;//video元素是否处于原生全屏(iOS Safari的webkitEnterFullscreen)
|
||||
var loadMeta=true;//第一次加载到元数据
|
||||
var app='';//平台类型
|
||||
var nowRotate=0;//当前视频旋转角度
|
||||
@@ -339,6 +341,8 @@ var language = languageDefault;
|
||||
video.attr('playsinline','true');
|
||||
video.attr('webkit-playsinline','true');
|
||||
video.attr('x5-playsinline','true');
|
||||
video.attr('x5-video-player-type','h5');//X5内核启用同层H5播放
|
||||
video.attr('x5-video-player-fullscreen','true');//X5内核全屏时使用同层播放器
|
||||
}
|
||||
if(vars['crossOrigin']){
|
||||
video.useCORS=true;//解决跨域
|
||||
@@ -466,6 +470,14 @@ var language = languageDefault;
|
||||
return;
|
||||
}
|
||||
else{
|
||||
/*
|
||||
* 未指定插件(或插件为上次自动识别的结果)时,按视频地址扩展名自动识别插件
|
||||
* 仅填写vars['plug'],后续canPlay分流逻辑不变:iOS等原生支持m3u8的环境仍走原生播放
|
||||
*/
|
||||
if(!vars['plug'] || vars['plug']==plugAuto){
|
||||
vars['plug']=getPlugByUrl(vstr);
|
||||
plugAuto=vars['plug'];
|
||||
}
|
||||
if(vars['plug'] && !canPlay(vstr)){
|
||||
plugPlayer(vstr);
|
||||
}
|
||||
@@ -568,9 +580,6 @@ var language = languageDefault;
|
||||
case 'mpegts.js':
|
||||
mpegtsPlayer(url);
|
||||
break;
|
||||
case 'dash.js':
|
||||
dashPlayer(url);
|
||||
break;
|
||||
default:
|
||||
if(valType(vars['plug'])=='function'){
|
||||
vars['plug'](video,url);
|
||||
@@ -626,12 +635,12 @@ var language = languageDefault;
|
||||
},
|
||||
/*
|
||||
* flvPlayer
|
||||
* 功能:使用flv.js插件进行播放
|
||||
* 功能:播放flv格式视频(基于mpegts.js库,plug:'flv.js'为兼容旧配置的别名)
|
||||
*/
|
||||
flvPlayer=function (url){
|
||||
var path=getPath('flv.js')+'flv.min.js';
|
||||
var path=getPath('mpegts.js')+'mpegts.js';
|
||||
loadJs(path,function(){
|
||||
if (typeof(flvjs)!='undefined' && !isUndefined(flvjs) && flvjs.isSupported()) {
|
||||
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||
var config={
|
||||
type: 'flv',
|
||||
url: url
|
||||
@@ -649,13 +658,13 @@ var language = languageDefault;
|
||||
pugPlayer.destroy();
|
||||
pugPlayer=null;
|
||||
}
|
||||
pugPlayer = flvjs.createPlayer(config);
|
||||
pugPlayer = mpegts.createPlayer(config);
|
||||
pugPlayer.attachMediaElement(video);
|
||||
pugPlayer.load();
|
||||
pugPlayer.on(flvjs.Events.METADATA_ARRIVED, function(res){
|
||||
pugPlayer.on(mpegts.Events.METADATA_ARRIVED, function(res){
|
||||
videoHandler.loadedMetaData();
|
||||
});
|
||||
pugPlayer.on(flvjs.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
||||
pugPlayer.on(mpegts.Events.ERROR, function(errorType, errorDetail, errorInfo){
|
||||
CT.error={code:errorInfo['code'],message:errorInfo['msg']};
|
||||
eventTarget('error',CT.error);//注册监听error
|
||||
showError();
|
||||
@@ -670,7 +679,7 @@ var language = languageDefault;
|
||||
mpegtsPlayer=function (url){
|
||||
var path=getPath('mpegts.js')+'mpegts.js';
|
||||
loadJs(path,function(){
|
||||
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.getFeatureList().mseLivePlayback) {
|
||||
if (typeof(mpegts)!='undefined' && !isUndefined(mpegts) && mpegts.isSupported()) {
|
||||
var config={
|
||||
type: 'mse',
|
||||
url: url
|
||||
@@ -703,6 +712,29 @@ var language = languageDefault;
|
||||
}
|
||||
});
|
||||
},
|
||||
/*
|
||||
* getPlugByUrl
|
||||
* 功能:根据视频地址的扩展名自动识别需要使用的插件
|
||||
* 识别不到时返回''(维持原生播放路径),URL支持携带query/hash参数
|
||||
*/
|
||||
getPlugByUrl=function(url){
|
||||
if(valType(url)!='string'){
|
||||
return '';
|
||||
}
|
||||
var purePath=url.split('?')[0].split('#')[0];
|
||||
var ext=purePath.substring(purePath.lastIndexOf('.')+1).toLowerCase();
|
||||
switch(ext){
|
||||
case 'm3u8':
|
||||
return 'hls.js';
|
||||
case 'flv':
|
||||
return 'mpegts.js';
|
||||
case 'ts':
|
||||
case 'm2ts':
|
||||
return 'mpegts.js';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
},
|
||||
/*
|
||||
* canPlay
|
||||
* 功能:判断是否能支持相关视频格式
|
||||
@@ -742,7 +774,9 @@ var language = languageDefault;
|
||||
return sup;
|
||||
};
|
||||
var getExtension=function(filepath) {
|
||||
return filepath.replace(/.+\./, '');
|
||||
//先剥离query与hash,避免 xxx.m3u8?token=... 之类地址无法识别扩展名
|
||||
var purePath=filepath.split('?')[0].split('#')[0];
|
||||
return purePath.replace(/.+\./, '');
|
||||
};
|
||||
if(isUndefined(num)){
|
||||
if(valType(vStr)=='string'){
|
||||
@@ -1225,6 +1259,24 @@ var language = languageDefault;
|
||||
CM.doubleClick(player.fullOrExit);//监听视频双击
|
||||
$(document).addListener('keydown',videoHandler.keydown);//监听键盘按键
|
||||
addListener(window, 'resize', videoHandler.resize);//监听窗口尺寸变化
|
||||
/*
|
||||
* 监听video元素原生全屏的进出(iOS Safari),同步内部全屏状态
|
||||
*/
|
||||
video.addListener('webkitbeginfullscreen',function(){
|
||||
videoNativeFull=true;
|
||||
videoHandler.resize();
|
||||
});
|
||||
video.addListener('webkitendfullscreen',function(){
|
||||
videoNativeFull=false;
|
||||
videoHandler.resize();
|
||||
});
|
||||
/*
|
||||
* 监听标准全屏状态变化(桌面端),保持全屏状态准确
|
||||
*/
|
||||
addListener(document,'fullscreenchange',videoHandler.resize);
|
||||
addListener(document,'webkitfullscreenchange',videoHandler.resize);
|
||||
addListener(document,'mozfullscreenchange',videoHandler.resize);
|
||||
addListener(document,'MSFullscreenChange',videoHandler.resize);
|
||||
if(!isUndefined(vars['smallWindows'])){
|
||||
if(valType(vars['smallWindows'])=='boolean' && vars['smallWindows']){
|
||||
addListener(window, 'scroll', windowScroll);//监听窗口滚动
|
||||
@@ -1769,7 +1821,7 @@ var language = languageDefault;
|
||||
replaceInformation('volume',parseInt(this.volume*100));
|
||||
},
|
||||
resize:function(){
|
||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||
if(fullState) {
|
||||
C['bar']['fullAndExit']['exitFull'].show();
|
||||
C['bar']['fullAndExit']['full'].hide();
|
||||
@@ -2892,6 +2944,11 @@ var language = languageDefault;
|
||||
* 功能: 退出全屏
|
||||
*/
|
||||
exitFull:function(){
|
||||
//iOS Safari上video处于原生全屏时,通过video.webkitExitFullscreen退出
|
||||
if(videoNativeFull && !isUndefined(video.webkitExitFullscreen)){
|
||||
video.webkitExitFullscreen();
|
||||
return player;
|
||||
}
|
||||
var exitFullFun = document.exitFullscreen || //W3C
|
||||
document.mozCancelFullScreen || //FireFox
|
||||
document.webkitExitFullscreen || //Chrome等
|
||||
@@ -2913,7 +2970,7 @@ var language = languageDefault;
|
||||
* 功能:在全屏和退出全屏之间进行切换
|
||||
*/
|
||||
fullOrExit:function(){
|
||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement;
|
||||
var fullState = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement || videoNativeFull;
|
||||
if(fullState){
|
||||
player.exitFull();
|
||||
}
|
||||
@@ -4928,6 +4985,16 @@ var language = languageDefault;
|
||||
bg.mousemove(bgMouseMove);
|
||||
bg.mouseout(bgMouseOut);
|
||||
mouseLine.mouseover(clearTime);
|
||||
/*
|
||||
* 移动端:支持在进度条上按住拖动seek,复用bgMouseDown,preventDefault防止页面滚动
|
||||
*/
|
||||
var bgTouchSeek=function(e){
|
||||
e.preventDefault && e.preventDefault();
|
||||
bgMouseDown(e);
|
||||
};
|
||||
bg.touchstart(bgTouchSeek);
|
||||
bg.touchmove(bgTouchSeek);
|
||||
bg.touchend(bgTouchSeek);
|
||||
},
|
||||
/*
|
||||
* volumeDragY
|
||||
@@ -5003,6 +5070,16 @@ var language = languageDefault;
|
||||
player.volume(vol);
|
||||
};
|
||||
bg.mousedown(bgMouseDown);
|
||||
/*
|
||||
* 移动端:支持在音量条上按住拖动调节音量,复用bgMouseDown,preventDefault防止页面滚动
|
||||
*/
|
||||
var bgTouchVolume=function(e){
|
||||
e.preventDefault && e.preventDefault();
|
||||
bgMouseDown(e);
|
||||
};
|
||||
bg.touchstart(bgTouchVolume);
|
||||
bg.touchmove(bgTouchVolume);
|
||||
bg.touchend(bgTouchVolume);
|
||||
},
|
||||
/*
|
||||
* changeProgress
|
||||
@@ -6315,11 +6392,24 @@ var language = languageDefault;
|
||||
}
|
||||
var x=eve.clientX + (document.documentElement.scrollLeft || $('body').scrollLeft),
|
||||
y=eve.clientY + (document.documentElement.scrollTop || $('body').scrollTop);
|
||||
if(isUndefined(x.toString()) || x.toString()=='NaN'){
|
||||
x=eve.touches[0].clientX
|
||||
}
|
||||
if(isUndefined(y.toString()) || y.toString()=='NaN'){
|
||||
y=eve.touches[0].clientY
|
||||
if(isUndefined(x.toString()) || x.toString()=='NaN' || isUndefined(y.toString()) || y.toString()=='NaN'){
|
||||
//触摸事件:touchstart/touchmove取touches;touchend时touches为空,取changedTouches兜底
|
||||
var touch=null;
|
||||
if(!isUndefined(eve.touches) && eve.touches.length>0){
|
||||
touch=eve.touches[0];
|
||||
}
|
||||
else if(!isUndefined(eve.changedTouches) && eve.changedTouches.length>0){
|
||||
touch=eve.changedTouches[0];
|
||||
}
|
||||
if(touch){
|
||||
//与mouse路径对齐,补上页面滚动偏移
|
||||
x=touch.clientX + (window.pageXOffset || document.documentElement.scrollLeft || 0);
|
||||
y=touch.clientY + (window.pageYOffset || document.documentElement.scrollTop || 0);
|
||||
}
|
||||
else{
|
||||
x=0;
|
||||
y=0;
|
||||
}
|
||||
}
|
||||
if(x.toString()=='NaN'){
|
||||
x=0;
|
||||
|
||||
+6
-2
@@ -173,11 +173,15 @@ import { addListener } from './dom.js';
|
||||
|
||||
/*
|
||||
* getApp
|
||||
* 功能,获取平台类型是否是iphone
|
||||
* 功能,获取平台类型是否是iphone(含ipod/ipad,统一返回'iphone'以保持现有分支兼容)
|
||||
*/
|
||||
function getApp(){
|
||||
var u = navigator.userAgent.toLowerCase();
|
||||
if(u.indexOf('iphone')>-1){
|
||||
if(u.indexOf('iphone')>-1 || u.indexOf('ipod')>-1 || u.indexOf('ipad')>-1){
|
||||
return 'iphone';
|
||||
}
|
||||
//iPadOS 13+默认使用桌面UA(Macintosh),通过MacIntel平台+触屏点数判断
|
||||
if(u.indexOf('macintosh')>-1 && navigator.platform=='MacIntel' && navigator.maxTouchPoints>1){
|
||||
return 'iphone';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user