接上篇

接上篇《高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui

本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到!

video.js 介绍

Video.js - open source HTML5 & Flash video player

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

本篇介绍在 webpack 中集成 video.js 播放器组件, 我们将要完成一个 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 如下:

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

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

  1. const HtmlWebpackPlugin = require('html-webpack-plugin');
  2. const CleanWebpackPlugin = require('clean-webpack-plugin');
  3. const CopyWebpackPlugin = require('copy-webpack-plugin');
  4. const webpack = require('webpack');
  5. const path = require('path');
  6. function resolve(dir) {
  7. return path.resolve(__dirname, dir)
  8. }
  9. module.exports = {
  10. entry: {
  11. //提取共用组件, 打包成 vendor.js
  12. vendor: ['jquery', 'vue', 'vuex', 'babel-polyfill',
  13. 'font-awesome/css/font-awesome.css', 'admin-lte/bootstrap/css/bootstrap.css',
  14. 'admin-lte/dist/css/AdminLTE.css', 'admin-lte/dist/css/skins/_all-skins.css',
  15. 'admin-lte/bootstrap/js/bootstrap.js', 'admin-lte/dist/js/app.js']
  16. },
  17. output: {
  18. path: resolve('dll'),
  19. filename: 'js/[name].[chunkhash:8].js',
  20. library: '[name]_library'
  21. },
  22. resolve: {
  23. extensions: ['.js', '.vue', '.json'],
  24. alias: {
  25. 'vue$': 'vue/dist/vue.common.js',
  26. 'jquery$': 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js'
  27. }
  28. },
  29. module: {
  30. rules: [{
  31. test: /\.css$/,
  32. loader: 'style-loader!css-loader'
  33. },
  34. {
  35. test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  36. loader: 'url-loader?limit=10000&name=images/[name].[hash:8].[ext]'
  37. },
  38. {
  39. test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  40. loader: 'url-loader?limit=10000&name=fonts/[name].[hash:8].[ext]'
  41. },
  42. {
  43. test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
  44. loader: 'url-loader?limit=10000&name=media/[name].[hash:8].[ext]'
  45. }]
  46. },
  47. plugins: [
  48. new webpack.ProvidePlugin({
  49. $: 'jquery',
  50. jQuery: 'jquery',
  51. "window.jQuery": 'jquery',
  52. "window.$": 'jquery'
  53. }),
  54. new CleanWebpackPlugin(['dll']),
  55. new CopyWebpackPlugin([
  56. { from: 'src/externals' }
  57. ]),
  58. new webpack.DllPlugin({
  59. path: resolve("dll/[name]-manifest.json"),
  60. name: "[name]_library",
  61. context: __dirname
  62. }),
  63. new HtmlWebpackPlugin({
  64. filename: 'template.html',
  65. title: '<%= htmlWebpackPlugin.options.title %>',
  66. inject: 'head',
  67. chunks: ['vendor'],
  68. template: './src/template.html',
  69. minify: {
  70. removeComments: true,
  71. collapseWhitespace: false
  72. }
  73. })
  74. ]
  75. };

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

修改 webpack.config.js 如下:

  1. const HtmlWebpackPlugin = require('html-webpack-plugin');
  2. const CleanWebpackPlugin = require('clean-webpack-plugin');
  3. const CopyWebpackPlugin = require('copy-webpack-plugin');
  4. const webpack = require('webpack');
  5. const path = require('path');
  6. require("babel-polyfill");
  7. function resolve(dir) {
  8. return path.resolve(__dirname, dir)
  9. }
  10. module.exports = {
  11. //定义页面的入口, 因为js中将要使用es6语法, 所以这里需要依赖 babel 垫片
  12. entry: {
  13. index: ['babel-polyfill', './src/index.js'],
  14. player: ['babel-polyfill', './src/player.js'],
  15. about: ['babel-polyfill', './src/about.js']
  16. },
  17. output: {
  18. path: resolve('dist'), // 指示发布目录
  19. filename: 'js/[name].[chunkhash:8].js' //指示生成的页面入口js文件的目录和文件名, 中间包含8位的hash值
  20. },
  21. externals: {
  22. //video.js 作为外部资源引入
  23. 'video.js': 'videojs'
  24. },
  25. //下面给一些常用组件和目录取别名, 方便在js中 import
  26. resolve: {
  27. extensions: ['.js', '.vue', '.json'],
  28. alias: {
  29. 'vue$': 'vue/dist/vue.common.js',
  30. 'jquery$': 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js',
  31. 'src': resolve('src'),
  32. 'assets': resolve('src/assets'),
  33. 'components': resolve('src/components')
  34. }
  35. },
  36. module: {
  37. //配置 webpack 加载资源的规则
  38. rules: [{
  39. test: /\.js$/,
  40. loader: 'babel-loader',
  41. include: [resolve('src')]
  42. }, {
  43. test: /\.vue$/,
  44. loader: 'vue-loader'
  45. }, {
  46. test: /\.css$/,
  47. loader: 'style-loader!css-loader'
  48. },
  49. {
  50. test: /\.less$/,
  51. loader: "less-loader"
  52. },
  53. {
  54. test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  55. loader: 'url-loader?limit=10000&name=images/[name].[hash:8].[ext]'
  56. },
  57. {
  58. test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  59. loader: 'url-loader?limit=10000&name=fonts/[name].[hash:8].[ext]'
  60. },
  61. {
  62. test: /\.(swf|mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
  63. loader: 'url-loader?limit=10000&name=media/[name].[hash:8].[ext]'
  64. }]
  65. },
  66. plugins: [
  67. //引入全局变量
  68. new webpack.ProvidePlugin({
  69. $: 'jquery',
  70. jQuery: 'jquery',
  71. "window.jQuery": 'jquery',
  72. "window.$": 'jquery'
  73. }),
  74. new webpack.DllReferencePlugin({
  75. context: __dirname,
  76. manifest: require('./dll/vendor-manifest.json')
  77. }),
  78. new CopyWebpackPlugin([
  79. { from: 'dll', ignore: ['template.html', 'vendor-manifest.json'] }
  80. ]),
  81. //编译前先清除 dist 发布目录
  82. new CleanWebpackPlugin(['dist']),
  83. //生成视频广场首页, 在这个页面中自动引用入口 index --> dist/js/index.[chunkhash:8].js
  84. //以 src/index.html 这个文件作为模板
  85. new HtmlWebpackPlugin({
  86. filename: 'index.html',
  87. title: '视频广场',
  88. inject: true, // head -> Cannot find element: #app
  89. chunks: ['index'],
  90. template: './dll/template.html',
  91. minify: {
  92. removeComments: true,
  93. collapseWhitespace: false
  94. }
  95. }),
  96. new HtmlWebpackPlugin({
  97. filename: 'player.html',
  98. title: 'HLS 播放器',
  99. inject: true,
  100. chunks: ['player'],
  101. template: './dll/template.html',
  102. minify: {
  103. removeComments: true,
  104. collapseWhitespace: false
  105. }
  106. }),
  107. //生成版本信息页面, 在这个页面中自动引用入口 about --> dist/js/about.[chunkhash:8].js
  108. //以 src/index.html 这个文件作为模板
  109. new HtmlWebpackPlugin({
  110. filename: 'about.html',
  111. title: '版本信息',
  112. inject: true,
  113. chunks: ['about'],
  114. template: './dll/template.html',
  115. minify: {
  116. removeComments: true,
  117. collapseWhitespace: false
  118. }
  119. })
  120. ]
  121. };

重点是在 externals 块下面声明 videojs 作为外部资源使用

然后, 我们添加一个新的静态页面配置, 用做 HLS 播放器的入口

添加左侧菜单项

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

  1. import Vue from "vue";
  2. import Vuex from "vuex";
  3. Vue.use(Vuex);
  4. const store = new Vuex.Store({
  5. state: {
  6. logoText: "EasyDSS",
  7. logoMiniText: "DSS",
  8. menus: [
  9. {
  10. path: "/index.html",
  11. icon: "mouse-pointer",
  12. text: "视频广场"
  13. }, {
  14. path: "/player.html",
  15. icon: "play",
  16. text: "HLS 播放器"
  17. }, {
  18. path: "/about.html",
  19. icon: "support",
  20. text: "版本信息"
  21. }
  22. ]
  23. },
  24. getters : {
  25. },
  26. mutations: {
  27. },
  28. actions : {
  29. }
  30. })
  31. export default store;

编写HLS 播放器 页面

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

  1. <template>
  2. <div class="player-wrapper">
  3. <div class="video-wrapper" style="padding-bottom:55%;position:relative;margin:0 auto;overflow:hidden;">
  4. <div class="video-inner" style="position:absolute;top:0;bottom:0;left:0;right:0;">
  5. </div>
  6. </div>
  7. </div>
  8. </template>
  9. <script>
  10. videojs.options.flash.swf = '/video-js-5.19.2/video-js-fixed.swf';
  11. videojs.options.techOrder = ['html5', 'flash'];
  12. if (videojs.browser.IE_VERSION) { // if IE use flash first
  13. videojs.options.techOrder = ['flash', 'html5'];
  14. }
  15. export default {
  16. data() {
  17. return {
  18. player: null
  19. }
  20. },
  21. props: {
  22. videoUrl: {
  23. default: ""
  24. },
  25. autoplay: {
  26. default: true
  27. }
  28. },
  29. beforeDestroy() {
  30. this.destroyVideoJS();
  31. },
  32. deactivated() {
  33. this.destroyVideoJS();
  34. },
  35. watch: {
  36. videoUrl: function(val) {
  37. this.destroyVideoJS();
  38. this.initVideoJS();
  39. }
  40. },
  41. mounted() {
  42. this.initVideoJS();
  43. },
  44. computed: {
  45. type() {
  46. let _type = "application/x-mpegURL";
  47. if (this.rtmp) {
  48. _type = "rtmp/mp4";
  49. }
  50. return _type;
  51. },
  52. rtmp() {
  53. return (this.src || "").indexOf("rtmp") == 0;
  54. },
  55. src() {
  56. if (!this.videoUrl) {
  57. return "";
  58. }
  59. if (this.videoUrl.indexOf("/") === 0) {
  60. return location.protocol + "//" + location.host + this.videoUrl;
  61. }
  62. return this.videoUrl;
  63. },
  64. videoHtml() {
  65. return `
  66. <video class="video-js vjs-default-skin vjs-big-play-centered" style="width: 100%; height: 100%;" controls preload="none">
  67. <source src="${this.src}" type="${this.type}"></source>
  68. <p class="vjs-no-js">
  69. To view this video please enable JavaScript, and consider upgrading to a web browser that
  70. <a href="http://videojs.com/html5-video-support/" target="_blank">
  71. supports HTML5 video
  72. </a>
  73. </p>
  74. </video>
  75. `;
  76. }
  77. },
  78. methods: {
  79. destroyVideoJS() {
  80. if (this.player) {
  81. this.player.dispose();
  82. this.player = null;
  83. }
  84. },
  85. initVideoJS() {
  86. $(this.$el).find(".video-inner").empty().append(this.videoHtml);
  87. if (!this.src) {
  88. return;
  89. }
  90. if (this.rtmp) {
  91. this.player = videojs($(this.$el).find("video")[0], {
  92. notSupportedMessage: '您的浏览器没有安装或开启Flash',
  93. tech: ['flash'],
  94. autoplay: this.autoplay
  95. });
  96. this.player.on("error", e => {
  97. var $e = $(this.$el).find(".vjs-error .vjs-error-display .vjs-modal-dialog-content");
  98. var $a = $("<a href='http://www.adobe.com/go/getflashplayer' target='_blank'></a>").text($e.text());
  99. $e.empty().append($a);
  100. })
  101. } else {
  102. this.player = videojs($(this.$el).find("video")[0], {
  103. autoplay: this.autoplay
  104. });
  105. }
  106. }
  107. }
  108. }
  109. </script>

封装 video.js api

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

  1. <template>
  2. <div class="modal fade" data-keyboard="false" data-backdrop="static">
  3. <div class="modal-dialog modal-lg">
  4. <div class="modal-content">
  5. <div class="modal-header">
  6. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  7. <span aria-hidden="true">&times;</span>
  8. </button>
  9. <h4 class="modal-title text-success text-center">{{videoTitle}}</h4>
  10. </div>
  11. <div class="modal-body">
  12. <VideoJS v-if="bShow" :videoUrl="videoUrl"></VideoJS>
  13. </div>
  14. <div class="modal-footer">
  15. <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
  16. </div>
  17. </div>
  18. </div>
  19. </div>
  20. </template>
  21. <script>
  22. import VideoJS from './VideoJS.vue'
  23. export default {
  24. data() {
  25. return {
  26. videoUrl: "",
  27. videoTitle: "",
  28. bShow: false
  29. }
  30. },
  31. mounted() {
  32. $(document).on("hide.bs.modal", this.$el, () => {
  33. this.bShow = false;
  34. }).on("show.bs.modal", this.$el, () => {
  35. this.bShow = true;
  36. })
  37. },
  38. components: { VideoJS },
  39. methods: {
  40. play(src,title) {
  41. this.videoUrl = src||"";
  42. this.videoTitle = title||"";
  43. $(this.$el).modal("show");
  44. }
  45. }
  46. }
  47. </script>

封装 bootstrap 模态框

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

  1. <template>
  2. <div class="container-fluid no-padding">
  3. <br>
  4. <div class="col-sm-8 col-sm-offset-2">
  5. <form role="form" class="form-horizontal" id="url-form">
  6. <div class="form-group">
  7. <div class="input-group" id="input-url-group">
  8. <input type="text" class="form-control" id="input-url" name="url" placeholder="输入播放地址" v-model.trim="url" @keydown.enter.prevent="play">
  9. <span class="input-group-btn">
  10. <a class="btn btn-primary" role="button" @click.prevent="play">
  11. <i class="fa fa-play"></i> 播放</a>
  12. </span>
  13. </div>
  14. </div>
  15. </form>
  16. </div>
  17. </div>
  18. </template>
  19. <script>
  20. import Vue from 'vue'
  21. import { Message } from 'element-ui'
  22. Vue.prototype.$message = Message;
  23. export default {
  24. data() {
  25. return {
  26. url: ""
  27. }
  28. },
  29. methods: {
  30. play() {
  31. if (!this.url) {
  32. this.$message({
  33. type: 'error',
  34. message: "播放地址不能为空"
  35. });
  36. return;
  37. }
  38. this.$emit("play", { videoUrl: this.url, videoTitle: this.url});
  39. }
  40. }
  41. }
  42. </script>

这里顺带演示了 element-ui 的 Message 用法

点击播放按钮, 消息向父组件传递, 播放地址作为参数一起传递

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

  1. import Vue from 'vue'
  2. import store from "./store";
  3. import AdminLTE from './components/AdminLTE.vue'
  4. import Player from './components/Player.vue'
  5. import VideoDlg from './components/VideoDlg.vue'
  6. new Vue({
  7. el: '#app',
  8. store,
  9. template: `
  10. <AdminLTE>
  11. <VideoDlg ref="videoDlg"></VideoDlg>
  12. <Player @play="play"></Player>
  13. </AdminLTE>`,
  14. components: {
  15. AdminLTE, Player, VideoDlg
  16. },
  17. methods: {
  18. play(video){
  19. this.$refs.videoDlg.play(video.videoUrl, video.videoTitle);
  20. }
  21. }
  22. })

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

运行

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

  1. npm run dll

然后

  1. 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

高性能流媒体服务器EasyDSS前端重构(四)- webpack + video.js 打造流媒体服务器前端的更多相关文章

  1. 高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui

    接上篇 接上篇<高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间> 本文围绕着实现EasyDSS高性能流 ...

  2. 高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间

    本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到! 接上回 <高性能流媒体服务器EasyDSS前端重构(一 ...

  3. EasyDSS高性能流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载 - 副本

    为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...

  4. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载

    为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...

  5. 高性能流媒体服务器EasyDSS前端重构(一)-从零开始搭建 webpack + vue + AdminLTE 多页面脚手架

    本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到! EasyDSS 高性能流媒体服务器前端架构概述 EasyDS ...

  6. EasyDSS高性能流媒体服务器前端重构(六)- webpack-dev-server 支持手机端访问

    很多时候,前端开发的页面,不仅要在PC端测试效果, 还要在手机端测试效果. 在开发阶段, 我们以 webpack-dev-server 来启动浏览器, 打开正在开发的页面. webpack-dev-s ...

  7. 前端工程化之webpack中配置babel-loader(四)

    安装 安装:npm i -D babel-core babel-loader babel-plugin-transform-runtime 安装:npm i -D babel-preset-es201 ...

  8. 使用webpack+vue.js构建前端工程化

    参考文章:https://blog.csdn.net/qq_40208605/article/details/80661572 使用webpack+vue.js构建前端工程化本篇主要介绍三块知识点: ...

  9. 前端模块化工具-webpack

    详解前端模块化工具-webpack webpack是一个module bundler,抛开博大精深的汉字问题,我们暂且管他叫'模块管理工具'.随着js能做的事情越来越多,浏览器.服务器,js似乎无处不 ...

随机推荐

  1. 微信公众号开发C#系列-12、微信前端开发利器:WeUI

    1.前言 通过前面系列文章的学习与讲解,相信大家已经对微信的开发有了一个全新的认识.后端基本能够基于盛派的第三方sdk搞定大部分事宜,剩下的就是前端了.关于手机端的浏览器的兼容性问题相信一直是开发者们 ...

  2. Network | 协议栈

    因特网协议栈Internet protocol stack: 应用层Application layer.运输层Transport layer.网络层Network layer.链路层Data link ...

  3. Maven 多模块父子工程 (含Spring Boot示例)

    一.为什么要用Maven多模块 假设有这样一个项目,很常见的Java Web应用.在这个应用中,我们分了几层: Dao Service Web 对应的,在一个项目中,我们会看到一些包名: org.xx ...

  4. c# datetime是一年中的第几周

    public static int WeekOfYear(DateTime dt, CultureInfo ci) { return ci.Calendar.GetWeekOfYear(dt, ci. ...

  5. Tmux常用快捷键及命令

    Exported from workflowy! tmux session start/create session- tmux- tmux new-session -s portage listin ...

  6. Windows 10系统出现:“出现系统还原使用的卷影复制服务无法运行...”的问题解决

    在服务中是:Volume Shadow Copy和Microsoft Software Shadow Copy Provider这两项.把它开启.

  7. Android 日期对话框DatePickerDialog

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout android:id="@ ...

  8. selinue引起的ssh连接错误

    在客户端执行ssh依然报错: Permission denied (publickey,gssapi-keyex,gssapi-with-mic). 在这个页面不小心看到了原因: http://ser ...

  9. NVIDIA® Quadro® 四路缓冲 3D立体方案

    http://www.nvidia.cn/object/quadro_pro_graphics_boards_cn.html NVIDIA® Quadro® 专业显卡让地球学家以及时装设计师等许多专业 ...

  10. WEB API 返回类型设置为JSON 【转】

    http://blog.sina.com.cn/s/blog_60ba16ed0102uzc7.html web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返 ...