vue移动端项目经验(二)
视频插件vue-video-player的使用及注意事项
官方文档
video.js:https://docs.videojs.com/docs/api/player.html
vue-video-player:https://github.com/surmon-china/vue-video-player
1、包的安装
npm install vue-video-player --save
2、包的引入
import VideoPlayer from 'vue-video-player'
require('video.js/dist/video-js.css') //若此行报错无法找到,则改为require('vue-video-player/node_modules/video.js/dist/video-js.css')
require('vue-video-player/src/custom-theme.css')
Vue.use(VideoPlayer)
3、插件使用
html部分
<div style="overflow:hidden"> //移动端当屏幕宽度较小时,此视频控件调节音量时会使得屏幕出现横向滚动条,故在父级盒子使用overflow:hidden来阻止滚动条出现,以免影响用户体验。
<video-player class="video-player vjs-custom-skin"
ref="videoPlayer"
:playsinline="true"
:options="playerOptions" //相关配置
@play="onPlayerPlay($event)"//监听开始状态(非必需),这是事件,按需使用
@pause="onPlayerPause($event)"//监听暂停状态(非必需),这是事件,按需使用
>
</video-player>
</div>
js部分
data(){
return{
playerOptions : {
playbackRates: [0.5, 1.0, 1.5, 2.0], //播放速度
autoplay: false, //如果true,浏览器准备好时开始回放。
muted: false, // 默认情况下将会消除任何音频。
loop: false, // 导致视频一结束就重新开始。
preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
language: 'zh-CN',
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [{
type: "",//这里的种类支持很多种:基本视频格式、直播、流媒体等,具体可以参看git网址项目
src: "" //url地址
}],
poster: "../../static/images/test.jpg", //你的封面地址
// width: document.documentElement.clientWidth, //播放器宽度
notSupportedMessage: '此视频暂无法播放,请稍后再试', //允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true, //当前时间和持续时间的分隔符
durationDisplay: true, //显示持续时间
remainingTimeDisplay: false, //是否显示剩余时间功能
fullscreenToggle: true //全屏按钮
}
}
}
},
//挂载视频组件(非必须)不写这一步也可以实现播放,添加这个是为了自定义按钮使用
computed: {
player() {
return this.$refs.videoPlayer.player//自定义播放
}
},
视频插件xgplayer(西瓜播放器)的使用及注意事项
更多详情见官方文档:http://h5player.bytedance.com/
贴代码:
1、安装包:npm install xgplayer
2、代码部分:
<template>
<div id="mse"></div> //1、标签,必须要有
</template>
<script>
import * as axios from 'axios'
import common from '../../kits/common.js'
import Player from 'xgplayer'; //2、引入视频组件
export default{
name:'Demo',
data(){
return{
playerConfig:{ //3、动态设置相关值
id: 'mse',
normalUrl:'', //普清
midUrl:'', //高清
hightUrl: '', //超清
poster: '', //封面
}
}
},
created(){
this.getVideoInfo()
},
methods:{
playerInit(){ //4、设置视频配置(注意:playerInit应放在异步函数里或mounted之后,不可在created里直接加载,否则不生效)
let player = new Player({ //初始化player实例对象
id:'mse',
url: this.playerConfig.highUrl, //视频链接
poster:this.playerConfig.poster, //封面图片
fluid: true, //流式布局
volume: 0.6, //初始音量
whitelist: ['iPhone','Android'], //白名单
playsinline: true, //内联模式
videoInit: true,
'x5-video-player-type': 'h5',
});
player.emit('resourceReady', [{name: '超清', url: this.playerConfig.highUrl}, {name: '高清', url: this.playerConfig.highUrl},{name: '标清', url: this.playerConfig.highUrl},]);
}
},
getVideoInfo(){
axios.get(`${common.videoApi}/getVideo`).then(res=>{
this.playerConfig.highUrl=res.data.data.url1 //获取超清视频链接
this.playerConfig.midUrl=res.data.data.url2 //获取高清视频链接
this.playerConfig.poster=res.data.data.vimage //获取视频封面
this.playerInit() //为什么在这里调用playerInit函数而不在mounted生命周期中调用?因为接口请求是异步的,在mounted中接口调用还未返回数据,故playerConfig中的数据仍然为空。而视频组件依赖于playerConfig中的数据
})
}
}
</script>
【【【直播】】】:直播与视频播放差不多,就以下几点不同
1、引入不同:
import 'xgplayer'; //先引入xgplayer
import HlsJsPlayer from 'xgplayer-hls.js'; //再引入组件
2、初始化不同:
let player=new HlsJsPlayer({ //初始化实例对象HlsJsPlayer
id:'hlsmse',
url: this.hlsPlayerConfig.url,
poster:this.hlsPlayerConfig.poster,
})
【【【注意事项:西瓜播放器xgplayer我用的时候有个Bug,就是在当前页面,xgplayer的视频链接url每重新赋值一次,xgplayer都会在原有的<video>标签上再生成一个新的<video>标签,并去覆盖原有的标签,通过这种方式实现当前视频的更新,但这也导致一个问题,因为原有的<video>标签不会被清空。就是说会出现多个video标签在同一个页面上的同一个位置。不仅影响性能,还会导致视频组件占位冲突。目前我想到的解决办法:在url重新赋值后,利用provide / inject 组合 刷新当前页面,以达到重新加载xgplayer组件的目的。(如何刷新,见下方)】】】
【【【09.10.24,想到了以上bug第二种解决办法:在重新初始化播放器之前,利用js先删除旧的已经渲染过的<div id="mse"></div>,紧接在再创建一个新的<div id="mse"></div>即可,代码如下
<div class="videoBody" id="videoBody">
<div v-show="islive==false" id="mse" class="mse"></div>
<div v-show="islive!==true" id="hlsmse" class="hlsmse"></div>
<div id="msedemo"></div>
</div>
重新创建mse:
document.getElementById('videoBody').removeChild(document.getElementById('mse'))
document.getElementById('videoBody').insertBefore(document.createElement('div'),document.getElementById('hlsmse'))
document.getElementById('hlsmse').previousElementSibling.setAttribute('id','mse')
重新创建hlsmse:
document.getElementById('videoBody').removeChild(document.getElementById('hlsmse'))
document.getElementById('videoBody').insertBefore(document.createElement('div'),document.getElementById('msedemo'))
document.getElementById('msedemo').previousElementSibling.setAttribute('id','hlsmse')
】】】
【【【注册事件,如监听播放暂停事件】】】:
playerInit(){ //xgplayer配置
let player = new Player({
id:'mse',
url: this.playerConfig.url,
});
player.on('play',function(){ //注册并监听播放事件
consol.log('播放了')
})
player.on('pause',function(){ //注册并监听暂停事件
console.log('暂停了')
})
},
具体可查看官网:http://h5player.bytedance.com/api/#%E4%BA%8B%E4%BB%B6
【【【上次播放到这里】】】:
1、在playInit的new Player里添加:
progressDot: [{'time': this.playerConfig.lastTime,'text':'您上次看到了这里'}],
2、在playInit函数最后添加以下代码
if(this.playerConfig.currentTime>0){
player.currentTime=this.playerConfig.lastTime //将上次播放的时间配置到当前播放器的播放时间
}
this.postTime=setInterval(()=>{
if(player.hasStart){
axios.post(`${common.videoapi}/amstc/addVideoTime?vid=${this.vid}&uid=${this.uid}¤ttime=${parseInt(player.currentTime)}`) //每过20秒将当前播放时间上传至服务器,下次进入此页面获取上次播放时间。
}
},20000)
3、在页面销毁后,清除定时器
destroyed(){
clearInterval(this.postTime)
},
注意,此功能适用于pc端,移动端安卓ios会有兼容问题
【【【安卓手机微信端里,组件出现黑屏情况,视频加载不出来,并报错The play() request was interrupted by a new load request】】】:
提示:在安卓端的微信内嵌的浏览器里,视频组件是可以实现自动播放的,在其他地方浏览器则不可播放,除非初始时是静音状态。因为浏览器视频播放协议的原因,video在用户交互之前,一般不允许自动播放。
原因看这:http://link.sov5.cn/l/LKioRMRBQX
解决:
let player=new HlsJsPlayer({
id:'hlsmse',
autoplay:false, //这里取消自动播放
url: this.hlsPlayerConfig.url,
poster:this.posterImg,
fluid: true,
volume: 0.6,
useHls: true,
whitelist: ['iPhone','Android'],
playsinline: true,
ignores: ['time','volume'],
'x5-video-player-type': 'h5',
})
+ player.start(this.hlsPlayerConfig.url) //调用此函数即可,调用此函数在安卓微信端会自动播放
vue如何刷新当前页面
转自:https://www.cnblogs.com/mmzuo-798/p/9356253.html
重新刷新当前页面,首先想到的就是路由重新导入当前页面,比如这样:
this.$router.push({name:'goodsList',params:{vid:vid}})
但是:我们会发现用vue-router在当前页面再重定向路由到页面本身,就算改变id,页面也是不进行刷新的,根本没有任何作用~所以这个方法out
下面提供两种方法:
1、强制刷新法,页面会类似于ctrl+f5的那种刷新,会有一段时间的空白出现,用户体验很不好,不建议使用
location. reload()
this.$router.go(0)
2、provide / inject 组合 方式是我试过最实用的
① 首先,要修改下你的app.vue,如图
② 通过声明reload方法,控制router-view的显示或隐藏,从而控制页面的再次加载,这边定义了
isRouterAlive //true or false
来控制,然后在需要当前页面刷新的页面中注入App.vue组件提供(provide)的 reload 依赖,然后直接用this.reload来调用就行
vue利用watch监听路由变化,可配合上面刷新方法使用
watch: {
$route() {
console.log("路由发生变化了")
this.goodId=this.$route.params.vid //2、获取新id重新请求数据
axios.get(`${common.goodsapi}/getgoods?id=${this.goodId}`).then(res=>{.....})
this.reload() //3、监听到路由变化后刷新页面,同时完成页面数据更新
}
},
methods:{
goToGoodsList(){
this.$router.push({name:'goodsList',params:{vid:vid}}) //1、点击后路由跳转仍在本页面,路由仅所携带的id发生改变。
}
}
回到顶部
document.documentElement.scrollTo(0,0)
转换时间格式
transCommentDate(dateNum){
const date=new Date(dateNum)
let Y=date.getFullYear()
let M=date.getMonth()+1
M=M<10?('0'+M):M
let D=date.getDate()
D=D<10?('0'+D):D
let h=date.getHours()
h=h<10?('0'+h):h
let m=date.getMinutes()
m=m<10?('0'+m):m
let s=date.getSeconds()
s=s<10?('0'+s):s
return `${Y}-${M}-${D} ${h}:${m}:${s}`
},
切换输入框时的获取焦点问题
需求:有两个input框(评论和回复),在点击“回个话”按钮后,评论input框切换成回复input框,同时回复input框获取焦点
评论框:<input class="comment" id="comment" v-if="!isReply" /> 回复框:<input class="reply" id="reply" v-slse-if="isReply"/>
思路:当点击“回个话”按钮后,执行函数使this.isReply=true,回复框显示。再使用document.getElementById("reply").focus()使其获取焦点。
问题:以上思路执行后会报错,错误为document.getElementById("reply")找不到。为什么呢,因为回复框原本是不存在的,只有在页面mounted之后,回复框dom才会加载出来,而document.getElementById("reply")是在页面加载之前就执行了,故找不到。
【【【解决办法:将两个输入框的id设置成一样的(如id="inputLabel"),再使用document.getElementById("inputLabel")即可。】】】
利用websocket实现直播实时评论
直接贴代码
<template>
<div>
<div>
<ul>
<li v-for="(item,index) in messages" :key="index">{{item.ccontent}}</li> //渲染从socket获取到的评论数据
</ul>
</div>
<input v-model="commentValue" />
<div @click="sendComment">发表评论</div> //发送评论数据到socket
<div>
</template>
<script>
import * as axios from 'axios'
import common from '../../sets/common.js'
import {getCookie} from '../../sets/cookie.js' //引入cookie
data(){
return{
usertoken:null, //用户信息
wspath:'', //websocket的path
socket:null, //socket实例对象
messages:[], //从socket获取的评论数据
commentValue:'', //用户输入的评论内容
}
}
created(){
this.usertoken = getCookie('Token')
this.chatInit() //初始化socket
},
methods:{
//1、实例化socket
chatInit(){
if(typeof(WebSocket) === 'undefined'){
this.$toast({
message:'您的浏览器不支持socket',
duration:2000,
})
}else{
// 实例化socket
if(this.usertoken){
this.wspath = `${common.socketlink}${this.roomid}?token=${this.usertoken}` //socketlink由后端提供给我,roomid从直播接口获取,token自己去设置
} else {
this.wspath = `${common.socketlink}${this.roomid}`
}
//初始化socket实例对象
this.socket = new WebSocket(this.wspath)
// 监听socket连接
this.socket.onopen = ()=>{
console.log('socket连接成功')
}
// 监听socket错误信息
this.socket.onerror = ()=>{
console.log('socket连接错误')
}
//监听socket关闭
this.socket.onclose=()=>{
console.log('socket连接关闭')
}
// 监听socket消息
this.socket.onmessage = this.getComment //这里面调用函数时不能带(),若this.getComment()会报错
}
},
//2、监听socket消息
getComment(msg){
this.messages=this.messages.concat(JSON.parse(msg.data))
},
//3、发送评论
sendComment(){
this.socket.send(`{"ccontent":"${this.commentValue}"}`) //这里注意上传时前面参数如ccontent要与后端的一致,不然socket会检测上传错误,从而自动关闭连接
this.commentValue=''
document.getElementById('commentScrollHidden').scrollTop=document.getElementById('scrollBottom').offsetHeight //发送评论后评论区回到底部
}
},
}
</script>
实现简单的微信分享功能(若想使用js-sdk实现分享功能请移步 https://www.cnblogs.com/huihuihero/p/12132952.html)
①先动态设置每个页面的title(方法见下方),
②有些页面需要自定义标题的也可以通过 document.title= 设置(方法见下方)。
③最后就是ios微信浏览器兼容问题,解决了即可(方法见下方)
ios微信浏览器分享问题
【问题】:ios端微信内分享的链接url不改变,总是同一个页面
【原因】:
IOS:微信IOS设备,所记录的url是进入页的url,所以无论分享哪个页面,总是会分享成初始进入页的url
Android:微信安卓设备,所记录的是当前页的url,所以分享出去的也会是当前页的url
【详细解释】:Vue的路由默认的mode是hash,这个模式带来的bug不是一般的多,在hash模式下url地址会出现#,这个#号会使window.location.href()方法失效,因此location.href方式这里最好不要用。并且ios微信支付的调起也会因为#的存在而导致jsap调起失败,但是Android并没有问题。因此mode的模式最好是history,这种模式下微信支付调起完全没问题。接下来就是微信分享的问题,在history模式下android还是能够正常分享,但是ios系统在这种模式下在微信浏览器下的url地址就是一开始进入页面的url,不会改变,无论路由如何的切换(android则不存在这种情况)。
【解决思路】:思路1、用js-sdk,详见微信文档。思路2、判断若是微信端,则在进入页面后将页面的路由拼接给url,已达到重新定义url的效果
【解决方法】:在main.js中添加以下代码(利用Vue Router的afterEach方法)
router.afterEach((to, from) => {
const u = navigator.userAgent.toLowerCase()
//下面这句是做判断,是否是ios。当然你也可以做一些其他判断,如这里我通过to.path=='/demo/demoReply'将一些页面排除在外
if(u.indexOf("like mac os x") < 0 || u.match(/MicroMessenger/i) != 'micromessenger' || u.match(/WebP/i) == "webp"||to.path=='/pArticle/pArcReply'||to.path=='/demo/demoReply') return
if (to.path !== global.location.pathname) {
location.assign(to.fullPath) //因为vue路由的hash模式,这里最好不要用 location.href= 或 location.replace() ,以避免url中#带来的问题
}
})
【提个醒】:1、使用以上方法时,遇到了个问题,一是路由传参使用params时,当参数超过2个就会出现bug,然后以query方式传参解决了,但参数再多时,query方式也不好用了,最终只好把这些页面在上述代码中通过判断方式排除了,暂未知晓原因。
2、这个只能在线上用可以,线下时,需要先注释掉,不能使用。
微信分享自定义标题
在当前页面设置title即可,分享出去会自带页面title
通过document.title="中国进入5G时代"方式设置
getVideoInfo(){
axios.get(`${common.videoapi}/getvideoinfo`).then(res=>{
document.title=res.data.data.name
})
}
vue根据路由动态改变页面标题
在main.js中添加以下代码
router.beforeEach((to, from, next) => {
/* 路由发生变化修改页面title */
if (to.meta.title) {
document.title = to.meta.title;
}
next();
})
在router.js路由页面
{
path:'/demo/demoReply',
component:demoReply,
name:'DemoReply',
meta:{
title:'评论回复' //设置标题
}
},
改变v-html内的指定标签的样式,这里以img为例,设置img最大宽度100%,高度自适应。(注:一定要设置高度自适应)
方式1:js设置
updated(){
let elem=document.getElementById('demo').getElementsByTagName('img') //获取指定标签里的img
let elem2=Array.from(elem) //因为getElementsByTagName()返回的是一个伪数组,所以这里使用Array.from转换为数组
for(let item of elem2){
item.style.maxWidth="100%"
item.style.height="auto"
}
},
方式2:css穿透模式下设置
/deep/ htmlcontent{
img{
max-width:100%!important;
height:auto!important;
}
}
vue2.0项目上线屏蔽console.log()
在build/webpack.prod.conf.js文件里的UglifyJsPlugin里加上这样两行代码即可
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_debugger: true, //屏蔽debugger
drop_console: true, //屏蔽console
}
},
sourceMap: config.build.productionSourceMap,
parallel: true
}),
减少,压缩项目打包后体积
在 config/index.js 中修改参数,
找到 productionSourceMap: true,改为 false 即可。
时间转换
transDate(dateNum){
const date=new Date(dateNum)
let Y=date.getFullYear()
let M=date.getMonth()+1
M=M<10?('0'+M):M
let D=date.getDate()
D=D<10?('0'+D):D
let h=date.getHours()
h=h<10?('0'+h):h
let m=date.getMinutes()
m=m<10?('0'+m):m
let s=date.getSeconds()
s=s<10?('0'+s):s
return `${Y}-${M}-${D} ${h}:${m}:${s}`
},
解决格林尼治时间在ios端和android端的兼容问题
格林尼治时间格式:2019-03-05T09:02:24.000+0000
android端:
new Date("2019-03-05T09:02:24.000+0000") 可正常转换。
new Date("2019-03-05T09:02:24.000+0000".substr(0,19)) 转换出来的时间会早8个小时
ios端:
new Date("2019-03-05T09:02:24.000+0000") 输出为NaN,不可用。
new Date("2019-03-05T09:02:24.000+0000".substr(0,19)) 可正常转换
故通过判断是否是ios端,再选择转换方式。具体代码如下:
transDate(dateNum){
let u = navigator.userAgent;
if (!!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) { //ios端
const date=new Date(dateNum.substr(0,19)) //截取前19位后再转换
let Y=date.getFullYear()
let M=date.getMonth()+1
M=M<10?('0'+M):M
let D=date.getDate()
D=D<10?('0'+D):D
let h=date.getHours()
h=h<10?('0'+h):h
let m=date.getMinutes()
m=m<10?('0'+m):m
let s=date.getSeconds()
s=s<10?('0'+s):s
return `${Y}-${M}-${D} ${h}:${m}:${s}`
}else{ //非ios端
const date=new Date(dateNum) //不截取,正常转换
let Y=date.getFullYear()
let M=date.getMonth()+1
M=M<10?('0'+M):M
let D=date.getDate()
D=D<10?('0'+D):D
let h=date.getHours()
h=h<10?('0'+h):h
let m=date.getMinutes()
m=m<10?('0'+m):m
let s=date.getSeconds()
s=s<10?('0'+s):s
return `${Y}-${M}-${D} ${h}:${m}:${s}`
}
},
判断手机操作系统是ios端还是android端
checkPort(){
let u = navigator.userAgent, app = navigator.appVersion;
let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //android终端
let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isAndroid) {
//这个是安卓操作系统
}
if (isIOS) {
//这个是ios操作系统
}
}