EasyPlayer播放器是基於EasyDSS流媒體服務器視頻強大的後臺管理能力,提供視頻點播和直播播放能力的強大播放載體。流暢穩定的播放性能,集廣告植入、數據監測等功能於一身,爲開發者提供端到端的一站式品視頻直播解決方案。覆蓋多類應用場景,包括Web、H五、iOS、Android等多平臺終端,提供快速接入的js代碼和SDK,知足客戶多樣需求,讓客戶輕鬆聚焦於業務發展自己,暢享極速高清播放新體驗。
css
Video.js - open source HTML5 & Flash video playerhtml
做爲一款高性能流媒體服務器的前端, 必不可少會用到流媒體播放器. 在播放器的選擇上, 咱們選中了功能強大而且開源的 video.js . 它能夠用來播放 RTMP/HLS 直播流.前端
本篇介紹在 webpack 中集成 video.js 播放器組件, 咱們將要完成一個 HLS 播放器
的小例子. 先來看一下效果圖吧:vue
咱們要開發的 HLS 播放器 須要用到 video.js 的一個官方插件: videojs-contrib-hlshtml5
儘管 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 的用法. 這裏介紹的也就是 「外部依賴法」.jquery
既是"外部依賴法", 那咱們首先把外部依賴的 video.js 文件準備好. 在 src 目錄下新建 externals 目錄, 把事先下載好的 video-js-5.19.2 目錄文件拷貝到這裏.webpack
修改 template.html 以下:git
<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 以下:es6
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 目錄, 最終, 這些外部依賴文件將被拷貝到發佈目錄下github
修改 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;
將 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">×</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
EasyDSS(http://www.easydss.com)流媒體解決方案採用業界優秀的流媒體框架模式設計,服務運行輕量、高效、穩定、可靠、易維護,支持RTMP直播、RTMP推送、HTTP點播、HTTP-FLV直播、HLS直播,並支持關鍵幀緩衝,畫面秒開等多種特性,可以接入Web、Android、iOS、H五、微信等全平臺客戶端,是移動互聯網時代貼近企業點播/直播需求的一款接地氣的流媒體服務器,配套OBS、EasyRTMP等直播推流工具以及EasyPlayer等網絡播放器,能夠造成一套完整的視頻直播、錄播解決方案,知足用戶在各類行業場景的流媒體業務需求。