弹幕需要用到canvas 标签
<canvas id="canvas" width="640" height="360" style="background:#333;"></canvas>
<input type="text" id="dminput" placeholder="输入弹幕内容">
<button id="btn_dm">发送弹幕</button>
let cvs = document.getElementById('canvas');
let ctx = cvs.getContext('2d');
// 声明dmlist, 保存所有的弹幕信息
let dmlist = [];
// 为弹幕按钮绑定事件,发送弹幕内容
btn_dm = document.getElementById('btn_dm');
btn_dm.addEventListener('click', ()=>{
let val = document.getElementById('dminput').value;
// 构造弹幕对象,并存储到dmlist中
dmlist.push({
text: val, x: 600,
y: (Math.floor(Math.random()*12)+1)*30
})
console.log(dmlist);
})
function draw(){
//把canvas上的所有像素点都给抹掉
ctx.clearRect(0, 0, 640, 360);
dmlist.forEach(item=>{ //item: {text:'343', x:0, y:0}
item.x--;
// 将当前item对象输出到canvas上
ctx.font = '22px 微软雅黑';
ctx.fillStyle = "white";
ctx.fillText(item.text, item.x, item.y);
})
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
这是一个vue 可本地播放MP4视频和播放webRTC推流地址的播放器,可以发送弹幕的demo
使用的是srs的脚本
srs官网的GitHub上有相关的依赖脚本
<template>
<div>
<el-card class="box-card">
<div>
<div style="padding-bottom: 20px;">
<el-input style="width: 360px;" v-model="input" placeholder="输入HTTP-FLV/HLS地址后播放视频"></el-input>
<el-button style="margin-left: 10px;" @click="startPlay" type="primary">播放FLV地址</el-button>
<input ref="Btn" id="fileurl" type="file" @change="onInputFileChange"/>
<el-button style="margin-left: 10px;" type="primary" @click="InputCkick">播放本地文件</el-button>
</div>
<div @mouseenter="handleEnter" @mouseleave="handleLeave" style="position: relative;display: inline-flex;background: rgb(51, 51, 51);">
<!-- loop muted controller controls autoplay -->
<video @timeupdate="gettimeupdate" ref="reference" style="
width: 1200px;
min-width: 600px;"></video>
<div v-if="isPlay" class="centerIcon" @click="getisPlay">
<i class="iconfont icon-bofangqi-bofang" style="font-size: 120px;" ></i>
</div>
<div style="position: absolute;left:0;right:0;bottom: -14px;padding: 8px 10px;background: #000;color: #fff;">
<el-slider :format-tooltip="formatDuring" @change="getMenuLong" :max="maxVal" style="width: 100%;position: absolute;top: 0;" v-model="time"></el-slider>
<div v-if="active" style="display: flex;justify-content: space-between;">
<div>
<i @click="getisPlay" :class="isPlay ? 'icon-bofangqi-bofang':'icon-iconstop'" class="iconfont leftIcon"></i>
<i class="iconfont icon-zuihouyiyemoyexiayishou leftIcon"></i>
<span class="leftText">{{qi|formatDuring}} / {{shi|formatDuring}}</span>
</div>
<div>
<i class="iconfont icon-danmuguanbi rightIcon"></i>
<i @mouseenter="handleMenu" @click="getvolume" class="iconfont icon-shengyin_shiti rightIcon"></i>
<i @click="Fullscreen" class="iconfont icon-24gf-fullScreenEnter rightIcon"></i>
<div @mouseleave="MenuLeave" v-if="Menu" class="topBottom" style="position: absolute;
bottom: 46px;
right: 70px;
height: 120px;">
<el-slider
v-model="Menuvalue"
vertical
width="2px"
:min="0"
:max="10"
@change="getMenuvalue"
height="120px">
</el-slider>
</div>
</div>
</div>
</div>
<canvas width="1200" height="560" id="canvas" @click="getisPlay" style="background:none"></canvas>
</div>
<div style="padding: 20px 0;">
<el-input style="width: 360px;margin-right: 10px;" v-model="valInput" placeholder="请输入弹幕内容"></el-input>
<el-button @click="_btnDm" type="primary">发送弹幕</el-button>
</div>
</div>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
input:'',
qi:0,
shi:0,
isPlay:true,
time:0,
sdk:null,
active:true,
valInput:'',
dmlist:[],
Menuvalue:10,
Menu:false,
maxVal:0,
ctx:null
}
},
filters:{
formatDuring(mss){
var arr = mss*1000
if(arr === 0) return '00:00:00'
var days = parseInt(arr / (1000 * 60 * 60 * 24));
var hours = parseInt((arr % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = parseInt((arr % (1000 * 60 * 60)) / (1000 * 60));
var seconds = ((arr % (1000 * 60)) / 1000).toFixed(0);
// days + ":" + .toFixed(0)
var str = (hours<9 ? `0${hours}`:hours) + ":" + (minutes<9 ? `0${minutes}`:minutes) + ":"+ (seconds<9 ? `0${seconds}`:seconds)
return str;
},
},
watch:{
Menuvalue(){
this.$refs.reference.volume = this.Menuvalue / 10
}
},
mounted() {
this.ctx = document.getElementById('canvas').getContext('2d')
window.requestAnimationFrame(this.draw);
},
methods: {
startPlay() {
if (this.sdk) {
this.sdk.close();
}
this.sdk = new SrsRtcPlayerAsync();
var thas = this
this.sdk.play(this.input).then(function(session){
thas.$refs.reference.srcObject = thas.sdk.stream;
}).catch(function (reason) {
thas.sdk.close();
console.error(reason);
});
},
handleEnter(){
this.active = true
},
handleLeave(){
this.active = false
},
onInputFileChange(file) {
this.$refs.reference.src = URL.createObjectURL(file.target.files[0]);
this.isPlay=false
this.$refs.reference.play()
this.$refs.reference.volume = this.Menuvalue / 10
this.$refs.reference.addEventListener('loadedmetadata',()=>{
let dt=this.$refs.reference.duration;
this.shi=dt
this.maxVal = dt
})
},
InputCkick(){
this.$refs.Btn.click()
},
getisPlay(){
if(this.$refs.reference.paused){
this.$refs.reference.play()
this.isPlay = false
}else{
this.$refs.reference.pause()
this.isPlay = true
}
},
_btnDm(){
this.dmlist.push({
text: this.valInput,
x: 1120,
y: (Math.floor(Math.random()*12)+1)*60
})
console.log(this.dmlist);
},
draw(){
//把canvas上的所有像素点都给抹掉
this.ctx.clearRect(0, 0, 1200, 560);
this.dmlist.forEach(item=>{ //item: {text:'343', x:0, y:0}
item.x--;
// 将当前item对象输出到canvas上 white
this.ctx.font = '14px 微软雅黑';
this.ctx.fillStyle = "white";
this.ctx.fillText(item.text, item.x, item.y);
})
window.requestAnimationFrame(this.draw);
},
Fullscreen(){
this.$refs.reference.requestFullscreen();
},
getvolume(){
this.$refs.reference.volume = Math.min(this.$refs.reference.volume+0.1,1)
console.log(this.$refs.reference.volume)
},
getMenuvalue(e){
console.log(e);
this.Menuvalue = e
},
handleMenu(){
this.Menu= true
},
MenuLeave(){
this.Menu= false
},
gettimeupdate(){
this.time = this.$refs.reference.currentTime
this.qi = this.$refs.reference.currentTime
},
getMenuLong(e){
console.log(e);
this.$refs.reference.currentTime = e
},
formatDuring(mss){
var arr = mss*1000
if(arr === 0) return '00:00:00'
var days = parseInt(arr / (1000 * 60 * 60 * 24));
var hours = parseInt((arr % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
var minutes = parseInt((arr % (1000 * 60 * 60)) / (1000 * 60));
var seconds = ((arr % (1000 * 60)) / 1000).toFixed(0);
// days + ":" + .toFixed(0)
var str = (hours<9 ? `0${hours}`:hours) + ":" + (minutes<9 ? `0${minutes}`:minutes) + ":"+ (seconds<9 ? `0${seconds}`:seconds)
return str;
},
},
}
</script>
<style>
.rightIcon{
margin-right:20px;font-size: 20px;vertical-align: middle;
}
.leftIcon{
font-size: 20px;vertical-align: middle;margin-right:10px;
}
.leftText{
font-size: 13px;vertical-align: middle;
}
.centerIcon{
position: absolute;
top: 50%;left: 50%;
transform: translate(-50%,-50%);
color: #fff;
font-size: 120px;
}
.topBottom{
padding: 10px;
background: #000;
}
.topBottom .el-slider__runway{
width: 2px !important;
height: 120px !important;
}
.topBottom .el-slider__button{
width: 10px !important;
height: 8px !important;
margin-left: -4px !important;
border: none !important;
background-color: #409EFF !important;
border-radius: 0 !important;
}
.topBottom .el-slider__bar{
width: 2px !important;
}
.el-slider{
width: 98.4% !important;
}
.el-slider__runway{
height: 2px !important;
margin: 0 !important;
}
.el-slider__bar{
height: 2px !important;
}
.el-slider__button{
width: 16px !important;
height: 8px !important;
margin-top: -4px !important;
border: none !important;
background-color: #409EFF !important;
border-radius: 0 !important;
}
video::-webkit-media-controls {
display: none;
}
/* 取消点击暂停 */
video{
pointer-events: none;
}
#fileurl{
margin-left: 20px;
color: #fff;
font-size: 13px;
border: none;
padding: 10px;
background: #409eff;
border-radius: 2px;
display: none;
}
#canvas{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 60px;
}
</style>