header-bg.jpg
Vue 中使用 video.js
发表于 2019-03-19 20:58
|
分类于 JavaScript
|
评论次数 3
|
阅读次数 10753

未标题2.png

video.js 是一款优秀的视频播放器组件, 官网的文档是直接以 JS 标签引入的方式使用的, 对 ES6 模块风格的方式完全空白, 本文记录一下我在 vue 项目中是如何使用 video.js 的(踩过的坑)

安装与使用

输入命令行, 下载video.js

cnpm i video.js

main.js中引入

import Video from 'video.js'
import 'video.js/dist/video-js.css'
Vue.prototype.$video = Video;

为了复用, 可以新建一个Player.vue组件, 然后在template中加入video标签:

<template>
    <video ref="video"
           controls
           class="video-js vjs-default-skin vjs-big-play-centered">
    </video>
</template>

覆盖全局样式:

<style>
    .vjs-big-play-button {
        height: 3em !important;
        width: 3em !important;
        line-height: 3em !important;
        border-radius: 50% !important;
    }

    .vjs-button > .vjs-icon-placeholder:before {
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 2em;
    }

    .vjs-slider-horizontal .vjs-volume-level:before {
        top: -0.333333333333333em;
        right: -0.5em;
    }

    .vjs-playback-rate .vjs-playback-rate-value {
        font-size: 1.8em;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    .vjs-control-bar {
        background: rgba(0,0,0,.8) !important;
    }

    .video-js .vjs-play-progress, .video-js .vjs-volume-level {
        background: linear-gradient(to right, #FF7B02, #FFA604) !important;
    }

    .video-js:hover .vjs-big-play-button, .vjs-custom-skin > .video-js .vjs-big-play-button:focus, .vjs-custom-skin > .video-js .vjs-big-play-button:active {
        background: linear-gradient(to right, #FF7B02, #FFA604) !important;
        opacity: .7;
    }

    .vjs-big-play-button .vjs-icon-placeholder {
        font-size: 70px !important;
    }

    .vjs-error .vjs-error-display:before {
        color: #fff !important;
        content: 'X' !important;
        font-family: Arial, Helvetica, sans-serif !important;
        font-size: 4.5em !important;
        left: 0 !important;
        line-height: 1.8 !important;
        margin-top: -0.5em !important;
        position: absolute !important;
        text-shadow: 0.05em 0.05em 0.1em #000 !important;
        text-align: center !important;
        top: 50% !important;
        vertical-align: middle !important;
        width: 100% !important;
    }
</style>

因为我的项目即 唯舞 , 使用的是黑色皮肤, 所以这套样式也是黑色的

Vue挂载完成后初始化video标签:

mounted() {
    const vm = this;
    this.player = this.$video(this.$refs.video, this.options, function () {

        this.on('play', () => {// 播放事件发生时, 立刻调整音量至用户已设置过的音量
            this.volume(vm.video.volume);
        });

        this.on('volumechange', () => {// 音量调整事件
            vm.video.volume = this.volume();
            window.localStorage.volume = this.volume();
        });

        this.on('error', () => {// 因为网络加载失败时 重新尝试
            let err = this.error();
            if (err.code === 2) {// 判断是否是网络加载问题
                let buffTime = this.bufferedEnd() - 6;
                this.load(vm.src);
                this.currentTime(buffTime);
                this.play();
            } else {
                vm.alert.err(err.message);
            }
        })
    });
},

因为 video 很多页面都会使用, 为了用户体验, 用户调整一次音量后是肯定是希望记住该音量大小, 下次在刷新页面后就不需要再次调整音量

这个需求也很简单,如上监听 volumechange 事件, 然后将用户调整的音量存进 Vuex,全局通用,再储存一份至 localStorage 里, 页面刷新的时候从 localStorage 里加载音量值, 同步音量大小

App.vue中如下设置即可:

beforeCreate() {
    let volume = window.localStorage.volume;
    if (volume) {
        this.$store.state.video.volume = volume;
    }
}

当视频路径发生变化时, 重新加载视频资源:

watch: {
    src() {
        this.player.src(this.src);
    }
},
computed: {
    src() {
        return this.options.sources[0].src;
    },
},

先在计算属性中返回视频的路径, 然后用watcher侦听该属性, 这样每次更改视频路径, 就可以进行重新加载视频资源

注意这里有一个Vue的BUG, 如果watch里监听options属性, 同时在渲染列表中同时渲染多个视频, 每次操作DOM都会触发watch,
明明options里的数据没有发生更改, 也会触发该watch事件, 例如这样写:

watch: {
    options: {
        handler() {
            console.log('观测到options已发生变化:');
	    this.player.init();// 错误做法, 假设该操作是初始化视频, 那么每次数据未发生变化却触发watch事件时, 视频也会被初始化
        },
        deep: true
    },
},

上面的代码将造成如下图这种BUG

load.gif

目前npm上有一个1000多赞的vue-video-player组件, 是基于video.js的, 该组件截止目前仍然存在这种BUG

相关issue

当操作DOM的时候, 页面中的所有视频都会被重新渲染, 就是因为该组件侦听了options属性, 那么解决这个BUG的方案也很简单, 就是不要侦听options, 只侦听src即可, 有其他需求, 思路也是相同的.

最后, 在业务组件中使用该Player组件:

<template>
    <div @contextmenu.prevent>
	<player :options="options"></player>
    </div>
</template>

<script>
    import player from '../base/Player';
    export default {
        components: {
            player
        },
        data: () => ({
            options : {
                playbackRates: [0.1, 0.2, 0.5, 1.0], //播放速度
                autoplay: true, //如果true,浏览器准备好时开始播放。
                muted: false, // 默认情况下将会消除任何音频。
                loop: false, // 导致视频一结束就重新开始。
                preload: 'auto', // auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
                //language: 'zh-CN',
                aspectRatio: '16:9', // 播放器的比例。用冒号分隔的两个数字(例如"16:9"或"4:3")
                fluid: true, // 当true时,播放器将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
                sources: [{
                    type: '',
                    src: '' //url地址
                }],
                //poster: '', //你的封面地址
                // width: document.documentElement.clientWidth,
                notSupportedMessage: '此视频暂无法播放,请稍后再试', //无法播放媒体源时显示的默认信息。
                controlBar: {
                    timeDivider: true,
                    durationDisplay: true,
                    remainingTimeDisplay: false,
                    fullscreenToggle: true  //全屏按钮
                },
                errorDisplay: false,
                posterImage: false,
                bigPlayButton : false,
                textTrackDisplay : false,
            },
        }),
        async beforeMount() {
            const src = await this.api.get('getVideoSrc');
            this.$set(this.options.sources, 0, {
                type: 'video/mp4',
                src
            });
        }
    }
</script>

Player.vue组件全部代码 :

<template>
    <video ref="video"
           controls
           class="video-js vjs-default-skin vjs-big-play-centered">
    </video>
</template>

<script>
    export default {
        data: () => ({
            player: null,
        }),
        props: {
            options: {
                type: Object,
                required: true
            },
        },
        watch: {
            src() {
                this.player.src(this.src);
            }
        },
        computed: {
            src() {
                return this.options.sources[0].src;
            },
            video() {
                return this.$store.state.video;
            }
        },
        mounted() {
            const vm = this;
            this.player = this.$video(this.$refs.video, this.options, function () {

                this.on('play', () => {
                    this.volume(vm.video.volume);
                });

                this.on('volumechange', () => {
                    vm.video.volume = this.volume();
                    window.localStorage.volume = this.volume();
                });

                this.on('error', () => {
                    let err = this.error();
                    if (err.code === 2) {// 判断是否是网络加载问题
                        let buffTime = this.bufferedEnd() - 6;
                        this.load(vm.src);
                        this.currentTime(buffTime);
                        this.play();
                    } else {
                        vm.alert.err(err.message);
                    }
                })
            });
        },
    }
</script>

发布评论
评论
共计 3条评论
最新评论
2021-03-29 17:26:15ww[北京市网友]
await this.api.get('getVideoSrc'); 报错Error in beforeMount hook (Promise/async): "TypeError: Cannot read property 'get' of undefined" 大佬了解一下?
0
0
回复
2020-11-10 18:47:15啦啦朵朵[北京市网友]
1[台湾省台湾省网友]
請問一下大大可不可以單頁放置多個VedioJs然後播放?
0
0
回复
你自己不会试试啊
0
0
回复
2020-03-06 10:25:41[台湾省台湾省网友]
請問一下大大可不可以單頁放置多個VedioJs然後播放?
0
0
回复