高性能流媒體服務器EasyDSS前端重構(四)- webpack + video.js 打造流媒體服務器前端

接上篇

接上篇《高性能流媒體服務器EasyDSS前端重構(三)- webpack + vue + AdminLTE 多頁面引入 element-uijavascript

本文圍繞着實現EasyDSS高性能流媒體服務器的前端框架來展開的,具體EasyDSS的相關信息可在:www.easydss.com 找到!css

video.js 介紹

Video.js - open source HTML5 & Flash video playerhtml

做爲一款高性能流媒體服務器的前端, 必不可少會用到流媒體播放器. 在播放器的選擇上, 咱們選中了功能強大而且開源的 video.js . 它能夠用來播放 RTMP/HLS 直播流.前端

本篇介紹在 webpack 中集成 video.js 播放器組件, 咱們將要完成一個 HLS 播放器 的小例子. 先來看一下效果圖吧:vue

HLS 播放器一

HLS 播放器二

安裝 video.js

咱們要開發的 HLS 播放器 須要用到 video.js 的一個官方插件: videojs-contrib-hls html5

儘管 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 的用法. 這裏介紹的也就是 「外部依賴法」.java

既是」外部依賴法」, 那咱們首先把外部依賴的 video.js 文件準備好. 在 src 目錄下新建 externals 目錄, 把事先下載好的 video-js-5.19.2 目錄文件拷貝到這裏.jquery

修改 template.html 以下:webpack

<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 以下:git

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

獲取更多信息

郵件:support@easydarwin.org

WEB:www.EasyDarwin.org

Copyright © EasyDarwin.org 2012-2017

EasyDarwin

相關文章
相關標籤/搜索