Loading...

EasyDSS高性能RTMP、HLS(m3u8)、HTTPFLV、RTSP流媒体服务器前端源码重构(四) webpack + video.js 打造流媒体服务器前端

EasyPlayer播放器是基于EasyDSS流媒体服务器视频强大的后台管理能力,提供视频点播和直播播放能力的强大播放载体。流畅稳定的播放性能,集广告植入、数据监测等功能于一身,为开发者提供端到端的一站式品视频直播解决方案。覆盖多类应用场景,包括Web、H5、iOS、Android等多平台终端,提供快速接入的js代码和SDK,满足客户多样需求,让客户轻松聚焦于业务发展本身,畅享极速高清播放新体验。
EasyDSS高性能RTMP、HLS(m3u8)、HTTPFLV、RTSP流媒体服务器前端源码重构(四) webpack + video.js 打造流媒体服务器前端

video.js 介绍

Video.js – open source HTML5 & Flash video player

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

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

EasyDSS高性能RTMP、HLS(m3u8)、HTTPFLV、RTSP流媒体服务器前端源码重构(四) webpack + video.js 打造流媒体服务器前端 EasyDSS高性能RTMP、HLS(m3u8)、HTTPFLV、RTSP流媒体服务器前端源码重构(四) webpack + video.js 打造流媒体服务器前端

安装 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/easydss/easydss-web-src/tree/blog_4

EasyPlayer播放器特点

EasyDSS高性能RTMP、HLS(m3u8)、HTTPFLV、RTSP流媒体服务器前端源码重构(四) webpack + video.js 打造流媒体服务器前端

关于EasyDSS

EasyDSS(http://www.easydss.com)流媒体解决方案采用业界优秀的流媒体框架模式设计,服务运行轻量、高效、稳定、可靠、易维护,支持RTMP直播、RTMP推送、HTTP点播、HTTP-FLV直播、HLS直播,并支持关键帧缓冲,画面秒开等多种特性,能够接入Web、Android、iOS、H5、微信等全平台客户端,是移动互联网时代贴近企业点播/直播需求的一款接地气的流媒体服务器,配套OBS、EasyRTMP等直播推流工具以及EasyPlayer等网络播放器,可以形成一套完整的视频直播、录播解决方案,满足用户在各种行业场景的流媒体业务需求。

原文链接:https://www.cnblogs.com/babosa/p/11123997.html
本文来源 爱码网,其版权均为 原网址 所有 与本站无关,文章内容系作者个人观点,不代表 本站 对观点赞同或支持。如需转载,请注明文章来源。

© 版权声明

相关文章