详解Vue实现直播功能
作者:前端小东
最近公司刚好在做直播,那么今天就记录一下遇到的坑,公司服务器用的亚马逊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="https://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="https://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="https://player.live-video.net/1.4.0/amazon-ivs-videojs-tech.min.js"></script>
end
加油~~~~
到此这篇关于Vue实现直播功能的文章就介绍到这了,更多相关Vue实现直播内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!