【干货篇】Vue实现直播弹幕功能
Vue实现直播弹幕前言上一篇文章我们讲述了如何用UniApp和Vue在搭建Nginx直播流媒体服务器后实现直播的功能,那么直播中必不可少的一部分就是弹幕,能够增加直播气氛同时,了解用户对于直播或者视频的反馈,本篇文章我们通过代码来讲讲,如何在视频/直播的基础上实现弹幕功能。提示:如果你有需要通过Vue做直播功能,不妨看看上一篇文章地址:UniApp+Vue实现直播功能一、实现弹幕,我们需要什么?对
·
Vue实现直播弹幕
前言
上一篇文章我们讲述了如何用UniApp和Vue在搭建Nginx直播流媒体服务器后实现直播的功能,那么直播中必不可少的一部分就是弹幕,能够增加直播气氛同时,了解用户对于直播或者视频的反馈,本篇文章我们通过代码来讲讲,如何在视频/直播的基础上实现弹幕功能。
提示:如果你有需要通过Vue做直播功能,不妨看看上一篇文章
地址:UniApp+Vue实现直播功能
一、实现弹幕,我们需要什么?
对没错,有些同学可能知道这个组件,叫:vue-barrage。
这是一款基于vue的弹幕组件,我们本次实现直播就需要使用这个组件。
功能介绍
- 支持弹幕发射的方向:滑动、顶部
- 支持全局弹幕暂停和局部鼠标浮动弹幕暂停
- 支持js弹幕, 解析html标签
- 性能良好,滑动弹幕采用dom按需加载和队列复用的方式,动画采用transform3d 开启css硬件加速
功能演示
功能演示
这里本来是gif图片,但是不知道为什么没有动起来🤭,有知道的小伙伴可以留言作者学习一下!
二、引入方法
1.下载源码
2.拷贝文件
- 解压下载好的压缩包,打开vue-barrage—>src,将components里面VBarrage文件夹到自己的项目的components中
- 可能会报错,见下方
三、具体使用
1.导入
- 上一步拷贝文件后,在你的直播/视频VUE文件中,导入VBarrage
import VBarrage from '@/components/VBarrage/index.vue';
- 在export default 中引入
components: { VBarrage },
- 接下来定义需要使用到的变量
bulletChat: {
arr: [], // 传入的弹幕源数组
isPause: false, // 控制是否暂停弹幕
sendContent: null, // 自己发送的弹幕内容
isJs: false, // 是否解析html
direction: 'default'
}
- 函数定义
// 初始化模拟弹幕数据
bulletChatData() {
let arr = ['我就说了,主播真帅', '主播表演跳舞吗,红红火火恍恍惚惚', '圆圈它不圆圆圈它不圆圆圈它不圆', '河南加油河南加油河南加油河南加油'];
for (let i = 0; i < 6; i++) {
for (let index = 0; index < 1000; index++) {
if (index % 2 == 0) {
this.bulletChat.arr.push({
direction: 'top',
content: arr[parseInt(Math.random() * arr.length)]
});
} else {
this.bulletChat.arr.push({
direction: 'default',
content: arr[parseInt(Math.random() * arr.length)]
});
}
}
}
},
// 发送弹幕
sendBarrage() {
if (this.bulletChat.arr.length > 1 && this.bulletChat.sendContent != '' && this.bulletChat.sendContent != null) {
this.bulletChat.arr.unshift({
content: this.bulletChat.sendContent, // 弹幕内容
direction: this.bulletChat.direction, // 方向 default | top
isSelf: true, // 是否是自己发的弹幕
style: {
color: 'red', // 弹幕颜色
fontSize: '25px'
},
isJs: this.bulletChat.isJs // 是否解析html
});
} else {
this.bulletChat.arr.push({
content: this.bulletChat.sendContent,
direction: this.bulletChat.direction,
isSelf: true,
style: {
color: 'red'
},
isJs: this.bulletChat.isJs
});
}
this.bulletChat.sendContent = null;
},
- mounted() 中调用初始化弹幕的方法
mounted() {
// 初始化弹幕
this.bulletChatData();
},
2.VUE-HTML区使用
- 我们在上篇文章的代码中继续补充,别急,后面会附上完整代码
- 下面是vue页面视频弹幕包裹代码,我们需要用一个代码中的第二层div将视频包起来
<!-- 视频区 -->
<div class="video-class">// 这行可以不关注,作者自己写的样式,主要看下一层
// 包裹视频代码 start
<div style=" position: relative;margin:0px auto;background:#000;">
<videoPlayer class="vjs-custom-skin videoPlayer"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@ended="onPlayerEnded($event)"
@loadeddata="onPlayerLoadeddata($event)"
@waiting="onPlayerWaiting($event)"
@playing="onPlayerPlaying($event)"
@timeupdate="onPlayerTimeupdate($event)"
@canplay="onPlayerCanplay($event)"
@canplaythrough="onPlayerCanplaythrough($event)"
@ready="playerReadied"
@statechanged="playerStateChanged($event)"
></videoPlayer>
<!-- 确保父元素是相对定位,弹幕容器是绝对定位 -->
// 使用v-barrage放置弹幕
<v-barrage :arr="bulletChat.arr" :isPause="bulletChat.isPause" :percent="100"></v-barrage>
</div>
// 包裹视频代码 end
</div>
- 弹幕的style定义
<style lang="scss" scoped>
.barrage-control {
text-align: center;
margin: 10px 0px;
}
3.可能会报错
- 如果你在拷贝文件后,或者写好上面的代码后,提示报错,有可能是因为sass样式代码组件没有引入导致的,那么我们的解决方案就是在控制台引入以下两个组件:
npm install node-sass --save
npm install style-loader --save
- 接下来,在build/webpack.base.config.js中添加:
{
test: /\.scss$/,
loaders: ['style', 'css', 'sass']
}
- 保存所有未保存的文件,重启项目,可以得到解决
如果有其它报错,或者组件使用有问题,请在下方评论区留言
四、完整代码
- 我们是直接在上篇文章直播代码中添加的弹幕功能,没有删除弹幕以外的其他代码,如果你跟着学习了上一篇文章,可以直接粘贴这个完整代码,稍作修改,放心使用!
<template>
<div>
<div style="overflow: hidden; display: flex;width: 93%; margin: 0 auto;">
<!-- 直播间信息区 -->
<div class="live" :style="liveHeight">
<div class="live-avatar">
<a-avatar shape="square" :size="84" v-if="liveRoomInfo" :src="imageUrl + liveRoomInfo.anchorPhoto" />
<div style="margin-left: 10px;" v-if="liveRoomInfo">
<p style="font-size: 18px;">
<b>{{ liveRoomInfo.liveroom.recordList[0].liveroomTitle }}</b>
</p>
<p>
<a-icon type="user" />
{{ liveRoomInfo.anchorName }}
</p>
</div>
</div>
<!-- 视频区 -->
<div class="video-class">
<div style=" position: relative;margin:0px auto;background:#000;">
<videoPlayer
class="vjs-custom-skin videoPlayer"
:options="playerOptions"
@play="onPlayerPlay($event)"
@pause="onPlayerPause($event)"
@ended="onPlayerEnded($event)"
@loadeddata="onPlayerLoadeddata($event)"
@waiting="onPlayerWaiting($event)"
@playing="onPlayerPlaying($event)"
@timeupdate="onPlayerTimeupdate($event)"
@canplay="onPlayerCanplay($event)"
@canplaythrough="onPlayerCanplaythrough($event)"
@ready="playerReadied"
@statechanged="playerStateChanged($event)"
></videoPlayer>
<!-- 确保父元素是相对定位,弹幕容器是绝对定位 -->
<v-barrage :arr="bulletChat.arr" :isPause="bulletChat.isPause" :percent="100"></v-barrage>
</div>
</div>
<!-- 操作区 -->
<div class="live-button">
<a-button type="dashed">强制下播</a-button>
<!-- <select style="margin:0px 12px;" v-model="bulletChat.direction">
<option value="default">默认</option>
<option value="top">顶部</option>
</select> -->
</div>
</div>
<!-- 弹幕区 -->
<div class="chat" :style="liveHeight">
<a-list item-layout="horizontal" :data-source="chatData" :split="false">
<a-list-item slot="renderItem" slot-scope="item, index">
<a-list-item-meta>
<span slot="description">
<span style="color: #2b94ff;">{{ item.name }}:</span>
<span style="margin-left: 5px;color: #000000;">{{ item.content }}</span>
</span>
</a-list-item-meta>
</a-list-item>
</a-list>
</div>
</div>
</div>
</template>
<script>
import 'video.js/dist/video-js.css';
import 'vue-video-player/src/custom-theme.css';
import { videoPlayer } from 'vue-video-player';
import 'videojs-contrib-hls';
import 'videojs-flash';
import VBarrage from '@/components/VBarrage/index.vue';
const chatData = [
{
name: '郭大炮',
content: '我就说了,主播真帅'
},
{
name: '一只小团团',
content: '主播表演跳舞吗,红红火火恍恍惚惚'
},
{
name: '圆圈它不圆',
content: '圆圈它不圆圆圈它不圆圆圈它不圆'
},
{
name: '河南加油',
content: '河南加油河南加油河南加油河南加油'
}
];
export default {
name: '',
components: { videoPlayer, VBarrage },
props: ['liveroomIds'],
data() {
return {
imageUrl: process.env.MY_APP_IMAGE,
liveroomId: this.liveroomIds,
liveRoomInfo: null, // 直播间信息
playerOptions: {
height: '500px', //播放器高度
language: 'zh-CN',
muted: false, //默认情况下将会消除任何音频。
aspectRatio: '16:9', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
sources: [
{
type: ['application/x-mpegURL', 'rtmp/flv', 'video/x-flv', 'rtmp/mp4'], //视频流协议,如果是hls,需要后端开启跨域
src: '' //拉流地址
}
],
autoplay: true, //自动播放
controls: true, //编辑器控件
notSupportedMessage: '此视频暂无法播放,请稍后再试', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
controlBar: {
timeDivider: true, //当前时间和持续时间的分隔符
durationDisplay: true, //显示持续时间
remainingTimeDisplay: false, //是否显示剩余时间功能
fullscreenToggle: true // 全屏按钮
},
flash: { hls: { withCredentials: false } },
html5: { hls: { withCredentials: false } }
},
chatData: chatData, // 弹幕列表
liveHeight: { height: 0 },
bulletChat: {
arr: [], // 传入的弹幕源数组
isPause: false, // 控制是否暂停弹幕
sendContent: null, // 自己发送的弹幕内容
isJs: false, // 是否解析html
direction: 'default'
}
};
},
mounted() {
window.addEventListener('resize', this.changeHeight);
this.changeHeight();
this.init();
// 初始化弹幕
this.bulletChatData();
},
methods: {
// 视频高度
changeHeight() {
this.liveHeight.height = window.innerHeight - 110 + 'px';
},
async init() {
let resData = await this.getLiveRoomInfo();
this.liveRoomInfo = resData;
// 将url赋值给src
this.playerOptions['sources'][0]['src'] = this.liveRoomInfo.liveroom.pullUrl;
},
// 获取直播间信息
async getLiveRoomInfo() {
return new Promise((resolve, reject) => {
this.$axios
.post(process.env.MY_API_LIVEBROADCAST + 'manage/liveroom/getLiveRoomInfo?liveroomId=' + this.liveroomId)
.then(res => {
if (res.data.status == 200) {
console.log(res.data.data);
resolve(res.data.data);
} else {
if (res.data.msg == '主播已下播!') {
this.closeModal();
}
this.$message.error({ content: res.data.msg });
}
})
.catch(res => {
console.log(res);
});
});
},
// 初始化模拟弹幕数据
bulletChatData() {
let arr = ['我就说了,主播真帅', '主播表演跳舞吗,红红火火恍恍惚惚', '圆圈它不圆圆圈它不圆圆圈它不圆', '河南加油河南加油河南加油河南加油'];
for (let i = 0; i < 6; i++) {
for (let index = 0; index < 1000; index++) {
if (index % 2 == 0) {
this.bulletChat.arr.push({
direction: 'top',
content: arr[parseInt(Math.random() * arr.length)]
});
} else {
this.bulletChat.arr.push({
direction: 'default',
content: arr[parseInt(Math.random() * arr.length)]
});
}
}
}
},
// 发送弹幕
sendBarrage() {
if (this.bulletChat.arr.length > 1 && this.bulletChat.sendContent != '' && this.bulletChat.sendContent != null) {
this.bulletChat.arr.unshift({
content: this.bulletChat.sendContent, // 弹幕内容
direction: this.bulletChat.direction, // 方向 default | top
isSelf: true, // 是否是自己发的弹幕
style: {
color: 'red', // 弹幕颜色
fontSize: '25px'
},
isJs: this.bulletChat.isJs // 是否解析html
});
} else {
this.bulletChat.arr.push({
content: this.bulletChat.sendContent,
direction: this.bulletChat.direction,
isSelf: true,
style: {
color: 'red'
},
isJs: this.bulletChat.isJs
});
}
this.bulletChat.sendContent = null;
},
// 播放回调
onPlayerPlay(player) {
console.log('player play!', player);
},
// 暂停回调
onPlayerPause(player) {
console.log('player pause!', player);
},
// 视频播完回调
onPlayerEnded(player) {
console.log('player ended!', player);
},
// DOM元素上的readyState更改导致播放停止
onPlayerLoadeddata(player) {
console.log('player Loadeddata!', player);
},
// 已开始播放回调
onPlayerWaiting(player) {
console.log('player Waiting!', player);
},
// 当播放器在当前播放位置下载数据时触发
onPlayerPlaying(player) {
console.log('player Playing!', player);
},
// 当前播放位置发生变化时触发
onPlayerTimeupdate(player) {
// console.log('player Timeupdate!', player.currentTime())
},
// 媒体的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);
// if(playerCurrentState.waiting != null && playerCurrentState.waiting == true){
// this.$message.error({ content: "主播已离开!" });
// this.closeModal();
// }
},
//将侦听器绑定到组件的就绪状态。与事件监听器的不同之处在于,如果ready事件已经发生,它将立即触发该函数。。
playerReadied(player) {
var hls = player.tech({ IWillNotUseThisInPlugins: true }).hls;
player.tech_.hls.xhr.beforeRequest = function(options) {
return options;
};
},
// 关闭按钮
closeModal() {
this.$emit('fatherRefresh', false);
}
}
};
</script>
<style scoped="scoped" less>
.live {
width: 70%;
/* height: 830px; */
border: 1px solid #e2e2e2;
padding: 10px;
}
/deep/ .live-avatar {
padding: 5px;
overflow: hidden;
display: flex;
border-bottom: 1px solid #e2e2e2;
}
.live /deep/ .video-class {
width: 100%;
}
.chat {
width: 30%;
height: 830px;
border: 1px solid #e2e2e2;
margin-left: 26px;
padding: 5px 5px 5px 25px;
}
.live-button {
height: 70px;
}
</style>
<style lang="scss" scoped>
.barrage-control {
text-align: center;
margin: 10px 0px;
}
</style>
结语
至此,我们的直播+基本弹幕功能就实现了,然后大家可以对代码进行扩展,比如弹幕目前只是定义了一个数组,大家可以接入WebSocket,真正的实现直播弹幕。
如果您觉得这篇文章对您有帮助的话,麻烦点个赞吧!!!
更多推荐
已为社区贡献2条内容
所有评论(0)