EasyDSS高性能流媒体服务器前端重构(四): webpack + video.js 打造流媒体服务器前端

来源:互联网 发布:java数组声明 编辑:程序博客网 时间:2024/06/07 03:09

video.js 介绍

Video.js - open source HTML5 & Flash video player

作为一款高性能流媒体服务器的前端, 必不可少会用到流媒体播放器. 在播放器的选择上, 我们选中了功能强大并且开源的 video.js . 它可以用来播放 RTMP/HLS 直播流.

本篇介绍在 webpack 中集成 video.js 播放器组件, 我们将要完成一个 HLS 播放器 的小例子. 先来看一下效果图吧:

HLS 播放器一

HLS 播放器二

安装 video.js

我们要开发的 HLS 播放器 需要用到 video.js 的一个官方插件: videojs-contrib-hls

尽管 video.js 官方文档中给出了 webpack 集成的说明(http://docs.videojs.com/tutorial-webpack.html), 但是在实际开发过程中, 我还是和其他人一样遇到了很多坑(https://github.com/videojs/videojs-contrib-hls/issues/600) 最后, 算是将 video.js 集成好, 却发现插放 HLS 流, 不能切换到 Flash 模式. 最终, 我决定采用外部依赖的方式集成 video.js, 正好借此熟悉一下 webpack externals 的用法. 这里介绍的也就是 “外部依赖法”.

既是”外部依赖法”, 那我们首先把外部依赖的 video.js 文件准备好. 在 src 目录下新建 externals 目录, 把事先下载好的 video-js-5.19.2 目录文件拷贝到这里.

修改 template.html 如下:

<html>    <head>        <title><%= htmlWebpackPlugin.options.title %></title>        <meta charset="utf-8">        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">        <!-- video.js -->        <link rel="stylesheet" href="/video-js-5.19.2/video-js.css"/>        <script src="/video-js-5.19.2/video.js"></script>        <script src="/video-js-5.19.2/videojs-contrib-hls4.js"></script>            </head>    <body class="skin-green sidebar-mini">        <div id="app"></div>    </body></html>

修改 webpack.dll.config.js 如下:

const HtmlWebpackPlugin = require('html-webpack-plugin');const CleanWebpackPlugin = require('clean-webpack-plugin');const CopyWebpackPlugin = require('copy-webpack-plugin');const webpack = require('webpack');const path = require('path');function resolve(dir) {    return path.resolve(__dirname, dir)}module.exports = {    entry: {        //提取共用组件, 打包成 vendor.js        vendor: ['jquery', 'vue', 'vuex', 'babel-polyfill',            'font-awesome/css/font-awesome.css', 'admin-lte/bootstrap/css/bootstrap.css',            'admin-lte/dist/css/AdminLTE.css', 'admin-lte/dist/css/skins/_all-skins.css',            'admin-lte/bootstrap/js/bootstrap.js', 'admin-lte/dist/js/app.js']    },    output: {        path: resolve('dll'),        filename: 'js/[name].[chunkhash:8].js',        library: '[name]_library'    },    resolve: {        extensions: ['.js', '.vue', '.json'],        alias: {            'vue$': 'vue/dist/vue.common.js',            'jquery$': 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js'        }    },    module: {        rules: [{            test: /\.css$/,            loader: 'style-loader!css-loader'        },        {            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=images/[name].[hash:8].[ext]'        },        {            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=fonts/[name].[hash:8].[ext]'        },        {            test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=media/[name].[hash:8].[ext]'        }]    },    plugins: [        new webpack.ProvidePlugin({            $: 'jquery',            jQuery: 'jquery',            "window.jQuery": 'jquery',            "window.$": 'jquery'        }),        new CleanWebpackPlugin(['dll']),        new CopyWebpackPlugin([            { from: 'src/externals' }        ]),        new webpack.DllPlugin({            path: resolve("dll/[name]-manifest.json"),            name: "[name]_library",            context: __dirname        }),        new HtmlWebpackPlugin({            filename: 'template.html',            title: '<%= htmlWebpackPlugin.options.title %>',            inject: 'head',            chunks: ['vendor'],            template: './src/template.html',            minify: {                removeComments: true,                collapseWhitespace: false            }        })            ]};

引入 CopyWebpackPlugin 将 externals 目录下的外部依赖文件拷贝到 dll 目录, 最终, 这些外部依赖文件将被拷贝到发布目录下

修改 webpack.config.js 如下:

const HtmlWebpackPlugin = require('html-webpack-plugin');const CleanWebpackPlugin = require('clean-webpack-plugin');const CopyWebpackPlugin = require('copy-webpack-plugin');const webpack = require('webpack');const path = require('path');require("babel-polyfill");function resolve(dir) {    return path.resolve(__dirname, dir)}module.exports = {    //定义页面的入口, 因为js中将要使用es6语法, 所以这里需要依赖 babel 垫片    entry: {        index: ['babel-polyfill', './src/index.js'],        player: ['babel-polyfill', './src/player.js'],        about: ['babel-polyfill', './src/about.js']    },    output: {        path: resolve('dist'), // 指示发布目录        filename: 'js/[name].[chunkhash:8].js' //指示生成的页面入口js文件的目录和文件名, 中间包含8位的hash值    },    externals: {        //video.js 作为外部资源引入        'video.js': 'videojs'    },    //下面给一些常用组件和目录取别名, 方便在js中 import    resolve: {        extensions: ['.js', '.vue', '.json'],        alias: {            'vue$': 'vue/dist/vue.common.js',            'jquery$': 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js',            'src': resolve('src'),            'assets': resolve('src/assets'),            'components': resolve('src/components')        }    },    module: {        //配置 webpack 加载资源的规则        rules: [{            test: /\.js$/,            loader: 'babel-loader',            include: [resolve('src')]        }, {            test: /\.vue$/,            loader: 'vue-loader'        }, {            test: /\.css$/,            loader: 'style-loader!css-loader'        },        {            test: /\.less$/,            loader: "less-loader"        },        {            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=images/[name].[hash:8].[ext]'        },        {            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=fonts/[name].[hash:8].[ext]'        },        {            test: /\.(swf|mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,            loader: 'url-loader?limit=10000&name=media/[name].[hash:8].[ext]'        }]    },    plugins: [        //引入全局变量        new webpack.ProvidePlugin({            $: 'jquery',            jQuery: 'jquery',            "window.jQuery": 'jquery',            "window.$": 'jquery'        }),        new webpack.DllReferencePlugin({            context: __dirname,            manifest: require('./dll/vendor-manifest.json')        }),        new CopyWebpackPlugin([            { from: 'dll', ignore: ['template.html', 'vendor-manifest.json'] }        ]),        //编译前先清除 dist 发布目录        new CleanWebpackPlugin(['dist']),        //生成视频广场首页, 在这个页面中自动引用入口 index --> dist/js/index.[chunkhash:8].js        //以 src/index.html 这个文件作为模板        new HtmlWebpackPlugin({            filename: 'index.html',            title: '视频广场',            inject: true, // head -> Cannot find element: #app            chunks: ['index'],            template: './dll/template.html',            minify: {                removeComments: true,                collapseWhitespace: false            }        }),        new HtmlWebpackPlugin({            filename: 'player.html',            title: 'HLS 播放器',            inject: true,            chunks: ['player'],            template: './dll/template.html',            minify: {                removeComments: true,                collapseWhitespace: false            }        }),        //生成版本信息页面, 在这个页面中自动引用入口 about --> dist/js/about.[chunkhash:8].js        //以 src/index.html 这个文件作为模板        new HtmlWebpackPlugin({            filename: 'about.html',            title: '版本信息',            inject: true,            chunks: ['about'],            template: './dll/template.html',            minify: {                removeComments: true,                collapseWhitespace: false            }        })    ]};

重点是在 externals 块下面声明 videojs 作为外部资源使用
然后, 我们添加一个新的静态页面配置, 用做 HLS 播放器的入口

添加左侧菜单项

打开 src/store/index.js, 修改如下:

import Vue from "vue";import Vuex from "vuex";Vue.use(Vuex);const store = new Vuex.Store({    state: {        logoText: "EasyDSS",        logoMiniText: "DSS",        menus: [            {                path: "/index.html",                icon: "mouse-pointer",                text: "视频广场"            }, {                path: "/player.html",                icon: "play",                text: "HLS 播放器"            }, {                path: "/about.html",                icon: "support",                text: "版本信息"            }        ]    },    getters : {    },    mutations: {    },    actions : {    }})export default store;

编写HLS 播放器 页面

将 video.js 简单封装成组件, 新建 src/compontents/VideoJS.vue

<template>    <div class="player-wrapper">        <div class="video-wrapper" style="padding-bottom:55%;position:relative;margin:0 auto;overflow:hidden;">            <div class="video-inner" style="position:absolute;top:0;bottom:0;left:0;right:0;">            </div>        </div>    </div></template><script>videojs.options.flash.swf = '/video-js-5.19.2/video-js-fixed.swf';videojs.options.techOrder = ['html5', 'flash'];if (videojs.browser.IE_VERSION) { // if IE use flash first    videojs.options.techOrder = ['flash', 'html5'];}export default {    data() {        return {            player: null        }    },    props: {        videoUrl: {            default: ""        },        autoplay: {            default: true        }    },    beforeDestroy() {        this.destroyVideoJS();    },    deactivated() {        this.destroyVideoJS();    },    watch: {        videoUrl: function(val) {            this.destroyVideoJS();            this.initVideoJS();        }    },    mounted() {        this.initVideoJS();    },    computed: {        type() {            let _type = "application/x-mpegURL";            if (this.rtmp) {                _type = "rtmp/mp4";            }            return _type;        },        rtmp() {            return (this.src || "").indexOf("rtmp") == 0;        },        src() {            if (!this.videoUrl) {                return "";            }            if (this.videoUrl.indexOf("/") === 0) {                return location.protocol + "//" + location.host + this.videoUrl;            }            return this.videoUrl;        },        videoHtml() {            return `                <video class="video-js vjs-default-skin vjs-big-play-centered" style="width: 100%; height: 100%;" controls preload="none">                    <source src="${this.src}" type="${this.type}"></source>                    <p class="vjs-no-js">                        To view this video please enable JavaScript, and consider upgrading to a web browser that                        <a href="http://videojs.com/html5-video-support/" target="_blank">                            supports HTML5 video                        </a>                    </p>                </video>                        `;        }    },    methods: {        destroyVideoJS() {            if (this.player) {                this.player.dispose();                this.player = null;            }        },        initVideoJS() {            $(this.$el).find(".video-inner").empty().append(this.videoHtml);            if (!this.src) {                return;            }            if (this.rtmp) {                this.player = videojs($(this.$el).find("video")[0], {                    notSupportedMessage: '您的浏览器没有安装或开启Flash',                    tech: ['flash'],                    autoplay: this.autoplay                });                this.player.on("error", e => {                    var $e = $(this.$el).find(".vjs-error .vjs-error-display .vjs-modal-dialog-content");                    var $a = $("<a href='http://www.adobe.com/go/getflashplayer' target='_blank'></a>").text($e.text());                    $e.empty().append($a);                })            } else {                this.player = videojs($(this.$el).find("video")[0], {                    autoplay: this.autoplay                });            }        }    }}</script>

封装 video.js api

编写播放器弹出框组件, 新建 src/components/VideoDlg.vue

<template>    <div class="modal fade" data-keyboard="false" data-backdrop="static">        <div class="modal-dialog modal-lg">            <div class="modal-content">                <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">                        <span aria-hidden="true">&times;</span>                    </button>                    <h4 class="modal-title text-success text-center">{{videoTitle}}</h4>                </div>                <div class="modal-body">                    <VideoJS v-if="bShow" :videoUrl="videoUrl"></VideoJS>                </div>                <div class="modal-footer">                    <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>                </div>            </div>        </div>    </div>    </template><script>import VideoJS from './VideoJS.vue'export default {    data() {        return {            videoUrl: "",            videoTitle: "",            bShow: false        }    },    mounted() {        $(document).on("hide.bs.modal", this.$el, () => {            this.bShow = false;        }).on("show.bs.modal", this.$el, () => {            this.bShow = true;        })    },    components: { VideoJS },    methods: {        play(src,title) {            this.videoUrl = src||"";            this.videoTitle = title||"";            $(this.$el).modal("show");        }    }}</script>

封装 bootstrap 模态框

编写HLS播放器页面内容, 新建 src/components/Player.vue

<template>    <div class="container-fluid no-padding">        <br>        <div class="col-sm-8 col-sm-offset-2">            <form role="form" class="form-horizontal" id="url-form">                <div class="form-group">                    <div class="input-group" id="input-url-group">                        <input type="text" class="form-control" id="input-url" name="url" placeholder="输入播放地址" v-model.trim="url" @keydown.enter.prevent="play">                        <span class="input-group-btn">                            <a class="btn btn-primary" role="button" @click.prevent="play">                                <i class="fa fa-play"></i> 播放</a>                        </span>                    </div>                </div>            </form>        </div>    </div></template><script>import Vue from 'vue'import { Message } from 'element-ui'Vue.prototype.$message = Message;export default {    data() {        return {            url: ""        }    },    methods: {        play() {            if (!this.url) {                this.$message({                    type: 'error',                    message: "播放地址不能为空"                });                return;            }            this.$emit("play", { videoUrl: this.url, videoTitle: this.url});        }    }}</script>

这里顺带演示了 element-ui 的 Message 用法
点击播放按钮, 消息向父组件传递, 播放地址作为参数一起传递

编写入口 js , 新建 src/player.js

import Vue from 'vue'import store from "./store";import AdminLTE from './components/AdminLTE.vue'import Player from './components/Player.vue'import VideoDlg from './components/VideoDlg.vue'new Vue({  el: '#app',  store,  template: `  <AdminLTE>    <VideoDlg ref="videoDlg"></VideoDlg>    <Player @play="play"></Player>  </AdminLTE>`,  components: {    AdminLTE, Player, VideoDlg  },  methods: {      play(video){          this.$refs.videoDlg.play(video.videoUrl, video.videoTitle);      }  }})

接收 Player 组件传来的播放消息, 打开播放器弹出框, 完成视频播放

运行

我们修改了 template.html 和 webpack.dll.config.js , 所以先要重新 build 共用组件库

npm run dll

然后

npm run start

源码位置: https://github.com/penggy/easydss-web-src/tree/blog_4

阅读全文
0 0
原创粉丝点击