前段时间项目中做视频直播用到了百度云的音视频直播LSS,由于需求需要在pc端要加一些自定义的按钮,因此需要把百度云播放器放在页面弹框里面,而layer算是比较好用的一款弹框插件,做出的效果图如图:
弹框下面还有截图的功能,截图是用H5的canvas能直接截取video标签里内容drawImage()方法,点击截图按钮效果如图:
保存按钮直接上传到服务器,可以看到图一背景还引入了百度地图,当时做这个功能的时候确实花费了一点精力,因为页面的功能比较复杂,后面会把详细的js代码po出,当是给自己的一点积累吧,不过这些都不是这篇博客的主角,这篇博客主要是解决一个百度云音视频直播全屏时与layer弹框冲突的bug,当初这个功能好不容易告一段落的时候,发现点击百度云播放器右下角的全屏按钮会出现这样的奇怪问题:
可以看到的是本来应该全屏的视频却跑到左下角去了,而且把页面的样式全挤乱了,这也太奇葩了,度娘了好久,基本上找不到解决方法,可能把百度云播放器跟layer结合使用人很少吧,没办法只能自己找问题了,之前准备看百度云播放器web sdk里面的cyberplayer.js的,结果发现头大了,那就还是从layer找原因吧,看页面的结构:感觉就是这个layui-layer类的问题,我在页面直接删除这个类,果然可以全屏了,于是就在js里面点击全屏按钮的时候移除掉这个类,果然可以全屏了:
那么在点击Esc退出全屏或者点右下角按钮恢复原屏的时候再加上layui-layer这个类不就ok了。然而,并没有我想的这么简单,在我点击Esc的时候,发现需要点两下才能添加layui-layer,点一下的时候都不进keydown事件,解决了一个问题又来了新的问题。。。正在犯难的时候想着layui-layer应该是这个弹框的主要类名,直接移除可能是会有问题,于是就看到在这个类的后面还有layui-layer-page,layer-anim,layui-layer-camera,三个类名,最后一个是我自定义弹框样式的可以不用管,那么在不移除layui-layer情况下,移除layui-layer-page和layer-anim会不会有什么变化呢,经过测试移除layer-anim也是可以全屏显示的,而且。。。在移除layer-anim后全屏,点击Esc或者右下角恢复原屏时添加layer-anim类也是可以复原的,至此bug才得到了解决,这 bug着实困扰了我一段时间。
最后作为积累,把这个功能的全部js都贴出来,里面的截屏函数,地图引用和自定义地图控件,保存上传截图等方法还是值得积累的~~~
/**
* Created by wyj on 2017/12/28. 门店巡视
*/
(function (define) {
'use strict';
define(function (require, exports, module) {
var controller = {
//本页面对象相关元素
thisObject:{
//标题
title:"门店巡视",
//本页面的主html
mainHtml:null,
//门店信息
storeData:null,
//视频流参数
streamname:'',
domain:'',
appname:'',
//截图数量
imgCount:0,
//批量保存截图
pathListArray:[],
//分页参数
pagerData:{
pageSize:6,//得到每页显示的条数,默认每页显示6条
pageIndex:1,//得到当前页,以便向服务端请求对应页的数据,默认第一页
counts:"",//总数量
otherValue:""
},
//地图对象
myMap:null,
},
//后台的方法的url
RepositoryUrl:"kxls/basedata/storeinspect/repository/mainRepository",
//初始化百度地图
initBaiduMap:function(){
require(['bmap'],function(bmap){
var myMap = new BMap.Map("map", {enableMapClick:false});//构造底图时,关闭底图可点功能
//地图对象公开到外面,方便其他函数调用
controller.thisObject.myMap = myMap;
//判断当前登录账号是集团账号还是网点账号 3--网点账号
if(window.$context.$api.auth.getCurrentUser().usertype!=3){
var size = new BMap.Size(10, 15);
myMap.addControl(new BMap.CityListControl({
anchor: BMAP_ANCHOR_TOP_LEFT,
offset: size,
// 切换城市之前事件
onChangeBefore: function(){
$(".curStore",controller.thisObject.mainHtml).text("选择门店").removeAttr("data-cecode").removeAttr("data-dealerdbcode");
$(".curCamera",controller.thisObject.mainHtml).text("选择摄像机").removeAttr("data-cameraip");
},
// 切换城市之后事件
// onChangeAfter:function(){
// alert('after');
// }
}));
}
//自定义控件
// 定义一个控件类,即function
function storeCameraControl(){
// 默认停靠位置和偏移量
this.defaultOffset = new BMap.Size(0, 0);
}
// 通过JavaScript的prototype属性继承于BMap.Control
storeCameraControl.prototype = new BMap.Control();
// 自定义控件必须实现自己的initialize方法,并且将控件的DOM元素返回
// 在本方法中创建个div元素作为控件的容器,并将其添加到地图容器中
storeCameraControl.prototype.initialize = function(myMap){
//判断当前登录账号是集团账号还是网点账号 3--网点账号
if(window.$context.$api.auth.getCurrentUser().usertype==3){
// 创建一个DOM元素,先创建jQuery对象
var myMapHeaderBox = $(''
+ '- 视频巡检系统
'
+ '- '
+ ''+controller.thisObject.storeData[0].DealerName+''
+ '
'
+ ' '
+ '- '
+ '
选择摄像机
'
+ ' '
+ '- '
+ ''
+ ''
+ '
'
+ '
'
+ ''
+ ' '
+ ' '
+'
');
}else{
// 创建一个DOM元素,先创建jQuery对象
var myMapHeaderBox = $(''
+ '- 视频巡检系统
'
+ '- '
+ ''
+ ''
+ '
'
+ '- '
+ '
选择门店
'
+ ' '
+ '- '
+ ''
+ ''
+ '
'
+ '
'
+ ''
+ ' '
+ '- '
+ '
选择摄像机
'
+ ' '
+ '- '
+ ''
+ ''
+ '
'
+ '
'
+ ''
+ ' '
+ ' '
+'
');
}
// 添加DOM元素到地图中
myMap.getContainer().appendChild(myMapHeaderBox[0]);
// 将DOM元素返回
return myMapHeaderBox[0];
}
// 创建控件
var myStoreCameraCtrl = new storeCameraControl();
// 添加到地图当中
myMap.addControl(myStoreCameraCtrl);
//百度地图自定义样式
var styleJson = [
{
"featureType": "water",
"elementType": "all",
"stylers": {
"color": "#044161"
}
},
{
"featureType": "land",
"elementType": "all",
"stylers": {
"color": "#004981"
}
},
{
"featureType": "boundary",
"elementType": "geometry",
"stylers": {
"color": "#064f85"
}
},
{
"featureType": "railway",
"elementType": "all",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "highway",
"elementType": "geometry",
"stylers": {
"color": "#004981"
}
},
{
"featureType": "highway",
"elementType": "geometry.fill",
"stylers": {
"color": "#005b96",
"lightness": 1
}
},
{
"featureType": "highway",
"elementType": "labels",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "arterial",
"elementType": "geometry",
"stylers": {
"color": "#004981"
}
},
{
"featureType": "arterial",
"elementType": "geometry.fill",
"stylers": {
"color": "#00508b"
}
},
{
"featureType": "poi",
"elementType": "all",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "green",
"elementType": "all",
"stylers": {
"color": "#056197",
"visibility": "off"
}
},
{
"featureType": "subway",
"elementType": "all",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "manmade",
"elementType": "all",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "local",
"elementType": "all",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "arterial",
"elementType": "labels",
"stylers": {
"visibility": "off"
}
},
{
"featureType": "boundary",
"elementType": "geometry.fill",
"stylers": {
"color": "#029fd4"
}
},
{
"featureType": "building",
"elementType": "all",
"stylers": {
"color": "#1a5787"
}
},
{
"featureType": "label",
"elementType": "all",
"stylers": {
"visibility": "off"
}
}
]
myMap.setMapStyle({styleJson:styleJson});
//点击位置获取详细地址信息
var data_info = controller.thisObject.storeData;
var opts = {
width : 250, // 信息窗口宽度
height: 80, // 信息窗口高度
enableMessage:true//设置允许信息窗发送短息
};
//点聚合显示门店数量
var markers = [];
//根据点标注显示地图大小
var points = [];
for(var i=0;i 门店地址:"+data_info[i].Address;
// 将标注添加到地图中
addClickHandler(content,marker);
//点聚合显示门店数量
markers.push(marker);
}
//根据点标注显示地图大小
var view = myMap.getViewport(eval(points));
var mapZoom = view.zoom;
var centerPoint = view.center;
myMap.centerAndZoom(centerPoint,mapZoom);
myMap.enableScrollWheelZoom();
myMap.enableInertialDragging();
myMap.enableContinuousZoom();
//最简单的用法,生成一个marker数组,然后调用markerClusterer类即可。
var markerClusterer = new BMapLib.MarkerClusterer(myMap, {markers:markers});
function addClickHandler(content,marker){
marker.addEventListener("click",function(e){
openInfo(content,e)}
);
}
function openInfo(content,e){
var p = e.target;
var point = new BMap.Point(p.getPosition().lng, p.getPosition().lat);
var infoWindow = new BMap.InfoWindow(content,opts); // 创建信息窗口对象
myMap.openInfoWindow(infoWindow,point); //开启信息窗口
//将点击信息传给当前选中门店容器里
$(".curStore",controller.thisObject.mainHtml).attr("data-cecode",p.storeData.cecode)
.attr("data-dealerdbcode",p.storeData.dealerdbcode)
.attr("data-loginStatus",p.storeData.loginstatus);
$(".curStore",controller.thisObject.mainHtml).text(p.storeData.dealerName);
}
});
},
//查询门店列表
queryStoreList:function(isInit){
//点击选择城市
var queryName = $("#cur_city_name",controller.thisObject.mainHtml).text();
if(queryName.slice(-1)=="市"||queryName.slice(-1)=="省"){
queryName = queryName.substring(0,queryName.length-1);
}
//手动输入查询
var dealerName = '';
if($(".myMapSearchInput",controller.thisObject.mainHtml).val()!=undefined){
dealerName = $(".myMapSearchInput",controller.thisObject.mainHtml).val();
}
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
Repository.queryStoreList(
{
queryName:queryName,
dealerName:dealerName,
cecode:$("body").attr("cecode")
},
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1){
//清空门店列表
$(".storeMenu>ul").empty();
//保存到页面storeData
controller.thisObject.storeData = result.listResult.rows;
if(result.listResult.rows.length==0){
$(".storeMenu>ul").append("此地区暂无门店! ");
}else{
//门店列表赋值
for(var i=0;iul").append(""+result.listResult.rows[i].DealerName+"("+result.listResult.rows[i].loginStatus+") ");
}else{
$(".storeMenu>ul").append(""+result.listResult.rows[i].DealerName+"("+result.listResult.rows[i].loginStatus+") ");
}
}
}
}
if(isInit){
controller.initBaiduMap();
}else{
if($(".chooseStore",controller.thisObject.mainHtml).css("display")!=="none"){
$(".cameraMenu",controller.thisObject.mainHtml).hide();
$(".storeMenu",controller.thisObject.mainHtml).show();
//下拉框定位
$(".storeMenu",controller.thisObject.mainHtml).css("left",$(".chooseStore",controller.thisObject.mainHtml).width()/4-$(".storeMenu",controller.thisObject.mainHtml).width());
}
}
}
});
})
},
//查询摄像头列表
queryCameraList:function(cecode,DealerDBCode){
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
Repository.queryCameraList(
{
cecode:cecode,
DealerDBCode:DealerDBCode,
},
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1){
//清空摄像头列表
$(".cameraMenu>ul").empty();
if(result.listResult==null){
$(".cameraMenu>ul").append("暂无摄像头! ");
}else if(result.listResult!=null&&$(".curStore",controller.thisObject.mainHtml).attr("data-loginStatus")=="离线"){
$(".cameraMenu>ul").append("门店已离线! ");
}else if(result.listResult!=null&&$(".curStore",controller.thisObject.mainHtml).attr("data-loginStatus")=="在线"){
//摄像头列表赋值
for(var i=0;iul").append(""+result.listResult.rows[i].camera_name+" ");
}
}
}
//下拉框定位
$(".storeMenu",controller.thisObject.mainHtml).hide();
$(".cameraMenu",controller.thisObject.mainHtml).show();
$(".cameraMenu",controller.thisObject.mainHtml).css("left",-$(".chooseStore",controller.thisObject.mainHtml).width()/5-$(".cameraMenu",controller.thisObject.mainHtml).width());
}
});
});
},
//开始摄像头直播
startCameraLive:function(cameraIp,cecode,DealerDBCode){
layer.msg("正在打开摄像头...");
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
Repository.startCameraLive(
{
cameraIp:cameraIp,
cecode:cecode,
DealerDBCode:DealerDBCode,
},
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1&&result.mapRow!=null){
//赋值删除直播流需要的参数
controller.domain = result.mapRow.domain;
controller.appname = result.mapRow.appname;
controller.streamname = result.mapRow.streamname;
//弹出直播窗口
controller.getCameraLive(result.mapRow.flvurl);
}else{
layer.msg(result.msg);
}
});
});
},
//获取摄像机直播
getCameraLive:function(flvurl){
//页面路径 //js路径
require(["text!kxls/basedata/storeinspect/view/showCamera.html"],function(pageHtml){
layer.open({
type: 1,
title:'门店直播',
//宽高
area:["700px","580px"],
content:pageHtml,
btn: ['截图'],
btn1: function(index, layero){
//截图按钮的回调
controller.cutScreen();
},
//最小化最大化
maxmin:false,
//弹出框的弹出层次
zIndex: 5000,
skin: 'layui-layer-camera',
//允许拖拽到窗口外
moveOut: true,
//弹窗成功后
success: function(layero,index){
var player = cyberplayer("playercontainer").setup({
width: 680,
height: 448,
file: flvurl, // <—flv直播地址
autostart: true,
stretching: "uniform",
volume: 100,
controls: true,
isLive: true, // 标明是否是直播
controlbar: {
barLogo: false, // 进度条上的logo可隐藏
barLogoUrl: "https://www.baidu.com/" // 进度条上的logo的跳转地址可配置
},
skin: "roundster", // 如果不喜欢默认皮肤,则有如下一些可选择,也可以自己写css来覆盖原生样式
// beelden, bekle, five, glow, roundster, seven, six, stormtrooper, vapor
ak: "b80d945fa0c34a4fb17c3ccb9b001387" // 公有云平台注册即可获得accessKey
});
//最大化去掉layui-layer类名-----这里就是解决全屏的bug
$(layero).on('click','.jw-icon-fullscreen',function(){
$(layero).removeClass("layer-anim");
event.stopPropagation();
});
//点击退出按钮添加layui-layer类名------这里就是解决全屏的bug
$(document).one("keydown",function(e){
if(e.keyCode==27){
$(layero).addClass("layer-anim");
event.stopPropagation();
}
});
},
cancel: function(index,layero){
layer.close(index);
//截图清零
controller.thisObject.imgCount = 0;
//关闭截图弹框
$("#imgBox").closest(".layui-layer").remove();
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
Repository.closeLiveVideo(
{
domain:controller.domain,
appname:controller.appname,
streamname:controller.streamname,
},
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1){
}
});
});
},
});
});
},
//截图按钮
cutScreen:function(){
//canvas截图
var video, $imgBox;
//图片缩放比例,1--原图大小
var scale = 1;
var video = $("#playercontainer video").get(0);
$imgBox = $("#imgBox");
var canvas = document.createElement("canvas");
canvas.width = video.videoWidth * scale;
canvas.height = video.videoHeight * scale;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
var img = document.createElement("img");
img.src = canvas.toDataURL("image/png");
//自定义截图样式
//截图序号递增
controller.thisObject.imgCount++;
var imgIndex = $("截图"+controller.thisObject.imgCount+"");
//装截图的div
var imgDiv = $("");
//截图备注
var imgText = $("截图"+controller.thisObject.imgCount+"备注:");
imgDiv.append(imgIndex,img,imgText);
layer.open({
type: 1,
title: " ",
//宽高
area:["260px","580px"],
offset: [$(".layui-layer-camera").offset().top, $(".layui-layer-camera").offset().left+$(".layui-layer-camera").width()],
content:'',
btn: ['保存'],
btn1: function(index, layero){
controller.saveCutScreen(layero);
},
//弹出框的弹出层次
zIndex: 5000,
skin: 'layui-layer-camera',
//显示动画--从左滑入
anim: 3,
//遮盖
shade:false,
//弹窗成功后
success: function(layero,index){
//图片加入当前弹层
$("#imgBox").prepend(imgDiv);
//移除其他弹层
$("#imgBox").closest(".layui-layer").next().remove();
//添加滚动条
controller.imgBoxScrollbar();
//上传截图
controller.sumitImageFile(img.src,controller.thisObject.imgCount,imgDiv);
},
cancel: function(index, layero){
//截图清零
controller.thisObject.imgCount = 0;
}
});
},
//添加滚动条
imgBoxScrollbar:function(){
//滚动条
require(['jquery.mCustomScrollbar'],function(){
//如果内容更改,比如展示30条数据,先卸载滚动条,再重新加入滚动条
var carTypePop=$("#imgBox").parent();
carTypePop.mCustomScrollbar('destroy');
carTypePop.mCustomScrollbar(
{
theme:"minimal-dark",
}
);
});
},
//上传截图
sumitImageFile:function(base64Codes,cutScreenImgName,imgDiv){
/**
* @param base64Codes
* 图片的base64编码
*/
var form=$("#cutScreen-file-form",controller.thisObject.mainHtml)[0];
//这里连带form里的其他参数也一起提交了,如果不需要提交其他参数可以直接FormData无参数的构造函数
var formData = new FormData(form);
//convertBase64UrlToBlob函数是将base64编码转换为Blob
formData.append("uploadfile",new File([controller.convertBase64UrlToBlob(base64Codes)],"截图"+cutScreenImgName+".png")); //append函数的第一个参数是后台获取数据的参数名,和html标签的input的name属性功能相同
//ajax 提交form
$.ajax({
url : window.$context.$config.domain + 'file/liveVideoCutScreen/upload.do?token=' + window.$context.$api.auth.getToken() + '&r=' + window.$context.$sys.util.getRandom(),
type : "POST",
data : formData,
dataType:"text",
processData : false, // 告诉jQuery不要去处理发送的数据
contentType : false, // 告诉jQuery不要去设置Content-Type请求头
success:function(data){
imgDiv.attr("photofilepath",eval('(' + data + ')').path[0][0].split("|")[1]).attr("photofilesize",eval('(' + data + ')').path[0][1]);
},
xhr:function(){ //在jquery函数中直接使用ajax的XMLHttpRequest对象
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", function(evt){
if (evt.lengthComputable) {
var percentComplete = Math.round(evt.loaded * 100 / evt.total);
console.log("正在提交."+percentComplete.toString() + '%'); //在控制台打印上传进度
}
}, false);
return xhr;
}
});
},
/**
* 将以base64的图片url数据转换为Blob
* @param urlData
* 用url方式表示的base64图片数据
*/
convertBase64UrlToBlob:function(urlData){
var bytes=window.atob(urlData.split(',')[1]); //去掉url的头,并转换为byte
//处理异常,将ascii码小于0的转换为大于0
var ab = new ArrayBuffer(bytes.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob( [ab] , {type : 'image/png'});
},
//保存截图
saveCutScreen:function(layero){
layer.msg("保存中...");
//需要保存的截图
controller.needSaveCutScreen(layero);
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
Repository.upLoadImgSave(
{
dealercode:$(".curStore",controller.thisObject.mainHtml).attr("data-cecode"), //'门店编码',
cameraip: $(".curCamera",controller.thisObject.mainHtml).attr("data-cameraip"), //'摄像头IP',
Remark:"",
pathList:JSON.stringify(controller.thisObject.pathListArray),
},
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1){
layer.msg("保存成功!");
}
});
});
},
//循环得到需要保存的截图
needSaveCutScreen:function(layero){
for(var i=0;i<$(".imgDiv",layero).length;i++){
controller.thisObject.pathListArray.push({
photoname: $($(".imgDiv",layero)[i]).find(".imgIndex").text(), //'图片名称',
photodes: $($(".imgDiv",layero)[i]).find("textarea").val(), //'图片描述',
//'图片文件路径',
photofilepath:$($(".imgDiv",layero)[i]).attr("photofilepath"),
//'图片文件大小',
photofilesize:$($(".imgDiv",layero)[i]).attr("photofilesize"),
});
}
},
//查看截图弹框
queryCutScreenPop:function(){
layer.open({
type: 1,
title:'图片列表',
//宽高
area:["900px","520px"],
content:''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+ ''
+'', //这里content是一个DOM,注意:最好该元素要存放在body最外层,否则可能被其它的相对元素所影响
//最小化最大化
maxmin:false,
//弹出框的弹出层次
zIndex: 5000,
//弹窗成功后
success: function(layero,index){
//请求查看截图接口
controller.queryCutScreen();
},
cancel: function(index,layero){
layer.close(index);
},
});
},
//请求查看截图接口
queryCutScreen:function(){
//清空数据
$(".cutScreenBox .layui-row").empty();
$(".cutScreenBox .noCutScreen").addClass("elem-hide");
//添加加载中动画
$(".cutScreenBox .layui-row").append("")
//请求相关Repository
require([controller.RepositoryUrl], function(Repository) {
controller.thisObject.pagerData.otherValue = $(".cutScreenBox .searchInputBox>input").val();
Repository.querySaveImg(
controller.thisObject.pagerData,
//拿到数据后
function (result) { //下面格式基本固定,可以根据自己的业务做修改
if(result.result==1){
//赋值给总数量
controller.thisObject.pagerData.counts = result.listResult.records;
//清空数据
$(".cutScreenBox .layui-row").empty();
if(result.listResult.rows.length==0){
$(".cutScreenBox .noCutScreen").removeClass("elem-hide");
$(".cutScreenBox .noCutScreen").html("暂无图片!")
}else{
for(var i=0;i"
+ ""
+ ""
+""+result.listResult.rows[i].photodes+"