Vue实现直播 详解Vue实现直播功能
前端小东 人气:7最近公司刚好在做直播,那么今天就记录一下遇到的坑,公司服务器用的亚马逊aws,所以直接看官方的api就可以了,aws官方地址aws直播api
先看下具体的实现后的效果图把
按照网上成熟的方法,使用的是video.js,然后aws做了一层封装,那么我们直接拿来使用把,这里使用vue版本的vue-video-player
先安装下相关的包
npm install vue-video-player --save
在main.js引入vue-video-player
// 第一个是videoJs的样式,后一个是vue-video-player的样式,因为考虑到我其他业务组件可能也会用到视频播放,所以就放在了main.js内 require('video.js/dist/video-js.css') require('vue-video-player/src/custom-theme.css') /*导入视频播放组件*/ import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer)
导入组件,修改配置参数
<video-player class="video-player vjs-custom-skin" ref="videoPlayer" :options="playerOptions" @play="onPlayerPlay($event)" @pause="onPlayerPause($event)" @statechanged="playerStateChanged($event)" ></video-player>
修改参数,添加src
playerOptions: { playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度 autoplay: false, //如果true,浏览器准备好时开始回放。 controls: true, //控制条 preload: "auto", //视频预加载 muted: true, //默认情况下将会消除任何音频。 loop: false, //导致视频一结束就重新开始。 language: "zh-CN", aspectRatio: "16:9", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [ { withCredentials: false, type: "application/x-mpegURL", //src: this.liveSrc src: "https://50f5175980ea.us-east-1.playback.live-video.net/api/video/v1/us-east-1.003054160756.channel.bSt8OCsmBtFq.m3u8" } ], poster: this.image, //你的封面地址 //width: 200 || document.documentElement.clientWidth, notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。 controlBar: { timeDivider: true, // 当前时间和持续时间的分隔符 durationDisplay: true, // 显示持续时间 remainingTimeDisplay: false, // 是否显示剩余时间功能 fullscreenToggle: true // 是否显示全屏按钮 } },
注意要先测试直播源可以成功播放才可以,否则就会报下这些错误
那么我们先按照官方的搭建一个本地的直播源测试吧
先搭建html界面,注意要引入相关的js库和文件,我这里用hbuilder,因为用的比较顺手,而且预览模式类似于开了一个端口,通过get方式的方法,返回了一个本地服务,而不是直接本地双击打开html文件,访问静态文件哦~~~~
<!doctype html> <html lang="en"> <head> <link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video-js.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video.min.js"></script> <script src="http://player.live-video.net/1.4.0/amazon-ivs-videojs-tech.min.js"></script> </head> <body> <div class="video-container"> <video id="amazon-ivs-videojs" class="video-js vjs-4-3 vjs-big-play-centered" controls autoplay playsinline></video> </div> <style> body { margin: 0; } .video-container { width: 640px; height: 480px; margin: 15px; } </style> <script> (function play() { // Get playback URL from Amazon IVS API //var PLAYBACK_URL = 'https://50f5175980ea.us-east-1.playback.live-video.net/api/video/v1/us-east-1.003054160756.channel.bSt8OCsmBtFq.m3u8'; var PLAYBACK_URL = 'https://50f5175980ea.us-east-1.playback.live-video.net/api/video/v1/us-east-1.003054160756.channel.bSt8OCsmBtFq.m3u8' // Register Amazon IVS as playback technology for Video.js registerIVSTech(videojs); // Initialize player var player = videojs('amazon-ivs-videojs', { techOrder: ["AmazonIVS"] }, () => { console.log('Player is ready to use!'); // Play stream player.src(PLAYBACK_URL); }); })(); </script> </body> </html>
通过端口访问,
后来发现通过本地静态文件,也可以实现在线直播源播放
ps:如果不想自己搭建本机服务测试,也可以直接使用awd提供的在线测试
https://replit.com/@changdong0524/amazon-ivs-player-web-sample#samples/common/form-control.ts,但是要自己注册账号哦
大概就是下面这样子哦
大家自己去摸索一下就会了,修改input.value为直播源地址,然后在右边shell控制台启动就可以了
npm install && npm run start
效果如下,是一模一样的
load这里的地址换成你自己的直播源m3u8格式就好了,我这里是已经搭建好的在线直播源
直播源没问题后,接下来就直接继续写项目代码
<template> <div class='demo'> <video-player class="video-player vjs-custom-skin" ref="videoPlayer" :playsinline="true" :options="playerOptions" @play="onPlayerPlay($event)" @pause="onPlayerPause($event)" @ended="onPlayerEnded($event)" @waiting="onPlayerWaiting($event)" @playing="onPlayerPlaying($event)" @loadeddata="onPlayerLoadeddata($event)" @timeupdate="onPlayerTimeupdate($event)" @canplay="onPlayerCanplay($event)" @canplaythrough="onPlayerCanplaythrough($event)" @statechanged="playerStateChanged($event)" @ready="playerReadied" > </video-player> </div> </template> <script> export default { methods: { // 播放回调 onPlayerPlay(player) { console.log('player play!', player) }, // 暂停回调 onPlayerPause(player) { console.log('player pause!', player) }, // 视频播完回调 onPlayerEnded($event) { console.log(player) }, // DOM元素上的readyState更改导致播放停止 onPlayerWaiting($event) { console.log(player) }, // 已开始播放回调 onPlayerPlaying($event) { console.log(player) }, // 当播放器在当前播放位置下载数据时触发 onPlayerLoadeddata($event) { console.log(player) }, // 当前播放位置发生变化时触发。 onPlayerTimeupdate($event) { console.log(player) }, //媒体的readyState为HAVE_FUTURE_DATA或更高 onPlayerCanplay(player) { // console.log('player Canplay!', player) }, //媒体的readyState为HAVE_ENOUGH_DATA或更高。这意味着可以在不缓冲的情况下播放整个媒体文件。 onPlayerCanplaythrough(player) { // console.log('player Canplaythrough!', player) }, //播放状态改变回调 playerStateChanged(playerCurrentState) { console.log('player current update state', playerCurrentState) }, //将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。 playerReadied(player) { console.log('example player 1 readied', player); } }, } </script>
定义相关的监听函数,可以根据自己需要加上,常用的有下面几个
onPlayerPlay(player) { console.log("onPlayerPlay", player); }, onPlayerPause(player) { console.log("onPlayerPause", player); }, playerStateChanged(player) { console.log("playerStateChanged", player); },
然后启动服务
npm run start
发现报错,无法找到相关的视频,于是发现缺少相关的库,还得加上aws的库才可以
在整个项目的index.html中加入下面的库支持文件
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video-js.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video.min.js"></script> <script src="http://player.live-video.net/1.4.0/amazon-ivs-videojs-tech.min.js"></script>
最后完整效果就出来了
注意事项:
video-player标签的class必须设置成“video-player vjs-custom-skin”,你引入的样式才能起作用。 增加hls的支持。支持流媒体m3u8g等等格式播放。
增加hls.js支持,故此要安装依赖,如下:
npm install --save videojs-contrib-hls
这里提供下aws的官方仓库啊,需要自取哦
https://github.com/aws-samples
补充一下:如果直接在页面中实现的话,可能无法直接播放,会报错无法播放视频,我猜测可能有2个原因,见截图
1:异步获取后台返回的拉流地址的时候,需要一定的时间,这个时间直播组件已经初始化完毕,但是还没有获取到直播源地址,所以会报错找不到直播地址
2:直播组件也有自己一整套完整的生命周期,我们可以检测不同的生命周期,然后把直播源地址,在请求完毕后放入合适的时间,直播组件会一直请求这个直播地址,从而实现在线直播
这里我为了偷懒,暂时没有那么多时间去研究一下,等空了会去仔细研究一下,我是把他抽离出一个单组的子组件,通过props来实现地址的传递
效果一样一样的,也可以方便其他组件调用
最后为了方便管理,双手奉上最后的全部代码
start
1:main.js
// 第一个是videoJs的样式,后一个是vue-video-player的样式,因为考虑到我其他业务组件可能也会用到视频播放,所以就放在了main.js内 require('video.js/dist/video-js.css') require('vue-video-player/src/custom-theme.css') /*导入视频播放组件*/ import VideoPlayer from 'vue-video-player' Vue.use(VideoPlayer)
2:videoPlayer.vue
<template> <video-player class="video-player vjs-custom-skin" ref="videoPlayer" :options="playerOptions" @play="onPlayerPlay($event)" @pause="onPlayerPause($event)" @statechanged="playerStateChanged($event)" ></video-player> </template> <script > //import { registerIVSTech } from "amazon-ivs-player"; export default { name: "", props: ["src", "image"], data() { return { // liveSrc: // "https://50f5175980ea.us-east-1.playback.live-video.net/api/video/v1/us-east-1.003054160756.channel.bSt8OCsmBtFq.m3u8", playerOptions: { playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度 autoplay: false, //如果true,浏览器准备好时开始回放。 controls: true, //控制条 preload: "auto", //视频预加载 muted: false, //默认情况下将会消除任何音频。 loop: false, //导致视频一结束就重新开始。 language: "zh-CN", aspectRatio: "16:9", // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。 sources: [ { withCredentials: false, type: "application/x-mpegURL", src: this.src // "https://50f5175980ea.us-east-1.playback.live-video.net/api/video/v1/us-east-1.003054160756.channel.bSt8OCsmBtFq.m3u8" } ], poster: this.image, //你的封面地址 //width: 200 || document.documentElement.clientWidth, notSupportedMessage: "此视频暂无法播放,请稍后再试", //允许覆盖Video.js无法播放媒体源时显示的默认信息。 controlBar: { timeDivider: true, // 当前时间和持续时间的分隔符 durationDisplay: true, // 显示持续时间 remainingTimeDisplay: false, // 是否显示剩余时间功能 fullscreenToggle: true // 是否显示全屏按钮 } } }; }, // livePlays() { // this.playerOptions.sources[0].src = this.liveSrc; // var obj = {}; // obj.withCredentials = false; // obj.type = "application/x-mpegURL"; // obj.src = this.pullUrl; // this.playerOptions.sources.append(obj); // }, computed: { player() { return this.$refs.videoPlayer.player; } }, computed: { player() { return this.$refs.videoPlayer.player; } }, methods: { onPlayerPlay(player) { console.log("onPlayerPlay", player); }, onPlayerPause(player) { console.log("onPlayerPause", player); }, playerStateChanged(player) { console.log("playerStateChanged", player); } } }; </script>
3:detail.vue 父组件
<template> <d2-container> <div> <div class="webTitle">直播管理 > 大型直播 > 详情</div> <el-table :data="list" border stripe> <el-table-column align="center" label="直播ID"> <template slot-scope="scope"> <span>{{ scope.row.id }}</span> </template> </el-table-column> <el-table-column align="center" label="直播标题"> <template slot-scope="scope"> <span>{{ scope.row.title }}</span> </template> </el-table-column> <el-table-column align="center" label="账号"> <template slot-scope="scope"> <span>{{ scope.row.name }}</span> </template> </el-table-column> <el-table-column align="center" label="直播开始时间"> <template slot-scope="scope"> <span>{{ scope.row.liveStart | timestampFormat }}</span> </template> </el-table-column> <el-table-column align="center" label="观看人数"> <template slot-scope="scope"> <span>{{ scope.row.watchNumber }}</span> </template> </el-table-column> <el-table-column align="center" label="评论数"> <template slot-scope="scope"> <span>{{ scope.row.reserveNumber }}</span> </template> </el-table-column> <el-table-column align="center" label="购票金额(GP)"> <template slot-scope="scope"> <span>{{scope.row.preSaleType == 1 ? scope.row.preSaleBalance*1 + scope.row.preSaleDeposit *1+ scope.row.fullPayment*1 : scope.row.fullPayment}}</span> </template> </el-table-column> <el-table-column align="center" label="礼物金额"> <template slot-scope="scope"> <span>{{ scope.row.reserveNumber }}</span> </template> </el-table-column> </el-table> <div class="playWrap"> <div class="livePicture"> <vueVideoPlayers :src="src" :image="image" /> </div> <div class="liveCommet"></div> </div> <div class="playWrap"> <div class="playLeft"> <p>基本信息</p> <ul class="leftInfo"> <li class="playItem"> <span class="playTitle">分类</span> <span class="playContent">{{typeName}}</span> </li> <li class="playItem"> <span class="playTitle">预售类型</span> <span class="playContent">{{formData.preSaleType == 1 ? "预售" :"非预售"}}</span> </li> <li class="playItem"> <span class="playTitle">是否录播</span> <span class="playContent">{{formData.isRecordedBroadcast ==1 ? "录播" : "不录播"}}</span> </li> <li class="playItem"> <span class="playTitle">演员列表</span> <span class="playContent">{{formData.actor}}</span> </li> <li class="playItem"> <span class="playTitle">直播介绍</span> <span class="playContent">{{formData.liveIntroduce}}</span> </li> </ul> <p>预售信息</p> <ul class="leftInfo"> <li class="playItem"> <span class="playTitle">预售时段</span> <span class="playContent"> {{formData.preSaleStart}} <span style="color:#333;margin:0 5px">-</span> {{formData.preSaleEnd}} </span> </li> <li class="playItem"> <span class="playTitle">成型人数</span> <span class="playContent">{{formData.formingNum ? formData.formingNum : 0}}</span> </li> <li class="playItem"> <span class="playTitle">成型状态</span> <span class="playContent" >{{formData.reserveNumber > formData.reserveNumber ? "已成型":"未成型" }}</span> </li> </ul> <p>非预售信息</p> <ul class="leftInfo"> <li class="playItem"> <span class="playTitle">售票开始时间</span> <span class="playContent">{{formData.ticketingStart}}</span> </li> </ul> <p>票价</p> <ul class="leftInfo"> <li class="playItem"> <span class="playTitle">预售定金</span> <span class="playContent">{{formData.preSaleDeposit ? formData.preSaleDeposit : 0}}</span> </li> <li class="playItem"> <span class="playTitle">预售尾款</span> <span class="playContent">{{formData.preSaleBalance ? formData.preSaleBalance : 0}}</span> </li> <li class="playItem"> <span class="playTitle">全款价格</span> <span class="playContent">{{formData.fullPayment ? formData.fullPayment : 0}}</span> </li> <li class="playItem"> <span class="playTitle">回放价格</span> <span class="playContent">{{formData.playbackPrice ? formData.playbackPrice : 0}}</span> </li> </ul> </div> <div class="playRight"> <p>图像资料</p> <ul class="leftInfo"> <li class="playItem"> <span class="playTitle">宣传视频</span> <span class="playContent"> <img v-if="formData.propagandaVideoUrl" :src="videoPng" class="playImage" @click="showVideo(formData.propagandaVideoUrl,true)" /> <span v-else style="color:#cfcfcf">暂无视频</span> </span> </li> <li class="playItem"> <span class="playTitle">回访视频</span> <span class="playContent"> <img v-if="formData.recordedBroadcastUrl" :src="videoPng" class="playImage" @click="showVideo(formData.recordedBroadcastUrl,false)" /> <span v-else style="color:#cfcfcf">暂无视频</span> </span> </li> <li class="playItem"> <span class="playTitle">分享海报</span> <span class="playContent"> <el-image class="matchImg" :src="formData.shareImage" :preview-src-list="[formData.shareImage]" /> </span> </li> <li class="playItem"> <span class="playTitle">封面图片</span> <span class="playContent"> <el-image class="matchImg" v-for="(item,index) in JSON.parse(formData.coverImage)" :src="item" :key="index" :preview-src-list="[item]" /> </span> </li> </ul> <!-- <p>图像资料</p> <ul class="leftInfo"></ul>--> </div> </div> </div> <el-button @click="backPage">返回</el-button> <el-dialog title="查看" :visible.sync="videoVisible" width="850px"> <div v-if="video"> <video :src="tempSrc" controls="controls" width="800" height="600">您的浏览器不支持 video 标签。</video> </div> <div v-else> <vueVideoPlayers :src="tempSrc" :image="image" /> </div> </el-dialog> </d2-container> </template> <script > import { getLiveDetail, getLiveSellDetail } from "@/api/3d/liveApi"; import videoPng from "@/assets/img/video.jpg"; import { timestampFormat } from "@/common/filters"; //import { registerIVSTech } from "amazon-ivs-player"; import vueVideoPlayers from "./videoPlayer"; export default { name: "", data() { return { src: "", //直播源视频 image: "", videoPng: videoPng, video: true, videoVisible: false, // videoSrc: "", //宣传视频 // recordedBroadcastUrl:'', //回放视频 tempSrc: "", list: [], id: "", typeName: "", pullUrl: "", formData: { id: "", pullUrl: "", pushUrl: "", title: "", liveIntroduce: "", actor: "", typeId: "", isRecordedBroadcast: 2, coverImage: "", propagandaVideoUrl: "", formingNum: "", preSaleDeposit: "", //预售定金价格 preSaleBalance: "", //预售尾款价格 fullPayment: "", //全款价格 playbackPrice: "", //回放价格 preSale: [], //预售时间 preSaleStart: "", preSaleEnd: "", liveStart: "", //直播开始时间 isSpeak: 1, priority: "", shareImage: "" } }; }, created() { this.getLiveSell(); this.getData(); }, mounted() {}, components: { vueVideoPlayers }, methods: { backPage() { this.$router.push("/liveMange/largeBrand"); }, //售票情况 getLiveSell() { var id = this.$route.params.id; getLiveSellDetail(id).then(res => { const result = res.data; }); }, //弹框打开看视频 showVideo(playSrc, mark) { this.videoVisible = true; this.video = mark; this.tempSrc = playSrc; }, getData() { var id = this.$route.params.id; this.id = id; //var localMatchTypeId=localStorage.getItem('matchTypeId') //var localPriority = localStorage.getItem('priority') // var data = { id, page: 1, limit: 10 }; getLiveDetail(id).then(res => { const result = res.data; //没有分类ID取本地 // if(!result.matchTypeId){ // result.matchTypeId = localMatchTypeId // } // if(!result.priority){ // result.priority = localPriority // } this.formData = result; let { pullUrl, pushUrl, coverImage } = result; this.src = pullUrl; this.image = JSON.parse(coverImage)[0]; const { id, title, liveStart, ticketingStart, playbackPrice, preSaleDeposit, preSaleBalance, fullPayment } = result; const objData = { id, title, name: "admin", liveStart, watchNumber: localStorage.getItem("watchNumber") | 0, reserveNumber: localStorage.getItem("reserveNumber") | 0, preSaleDeposit, preSaleBalance, fullPayment, ticketingStart, playbackPrice }; this.list.push(objData); // this.formData.registrationStart=result.registrationStart + '' // this.formData.registrationEnd = result.registrationEnd + '' // this.formData.voteStart = result.voteStart + '' // this.formData.voteEnd = result.voteEnd + '' //投票时段 // var preSaleStart = moment(parseInt(result.preSaleStart)).format( // "YYYY-MM-DD hh:mm:ss:SSS" // ); // var preSaleEnd = moment(parseInt(result.preSaleEnd)).format( // "YYYY-MM-DD hh:mm:ss:SSS" // ); //赛事结束时段 // this.formData.liveStart = new Date(result.liveStart); //this.formData.registration.push(start) //this.formData.registration.push(end) //手动赋值 // this.$set(this.formData, "preSale", [preSaleStart, preSaleEnd]); //this.$set(this.formData, "vote", [voteStart, voteEnd]); //日期格式化 //预售 时间段 this.formData.preSaleStart = result.preSaleStart ? timestampFormat(result.preSaleStart) : ""; this.formData.preSaleEnd = result.preSaleEnd ? timestampFormat(result.preSaleEnd) : ""; //非预售 开始售票时间 this.formData.ticketingStart = result.ticketingStart ? timestampFormat(result.ticketingStart) : ""; this.typeName = localStorage.getItem("typeName") || ""; }); } } }; </script> <style scoped> .playWrap { display: flex; background: #fff; margin-top: 20px; } .leftInfo { list-style: none; border: 1px solid #cfcfcf; } .playLeft { width: 48%; /* border: 1px solid #f5f5f5; */ } .playRight { width: 48%; margin-left: 2%; } .playItem { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #cfcfcf; } .playItem:last-child { border-bottom: none; } .playContent { margin-left: 20px; color: #999; } .matchImg { width: 80px; height: 80px; margin-right: 10px; } .playImage { width: 80px; height: 80px; } .playWrap { display: flex; } .livePicture { width: 40%; height: 400px; } </style>
3:index.html记得加入如下代码
<link href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video-js.css" rel="stylesheet"> <script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.4/video.min.js"></script> <script src="http://player.live-video.net/1.4.0/amazon-ivs-videojs-tech.min.js"></script>
end
加油~~~~
加载全部内容