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}&currenttime=${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操作系统
    }
}
posted @ 2019-10-10 16:49  huihuihero  阅读(2708)  评论(0编辑  收藏  举报