直播弹幕实现
简要说明
之前实现了点播弹幕实现,在之前基础上利用WebSocket
长连接实现了直播弹幕,具体采用websocket+stomp,而SpringBoot对着两者有着非常好的支持。
使用技术
前端:…(之前的),sockjs(尝试建立websocket连接,如果客户端不支持退化为轮询或长连接),webstomp-client(建立基于stomp协议的连接)。
后端: SpringBoot.
后端使用了SpringBoot一套实现,另外的话还想用底层基于Netty的一套异步实现,不过不是裸Netty,应该又要简单学一套异步框架,或许有空的话学习Go实现一下,感受一下协程。
前端实现
只需要在读弹幕库时建立websocket+stomp连接,订阅相关房间消息,send操作时发送stomp消息即可。
const dp = new DPlayer({
container: document.getElementById("live-player"),
live: true,
video: {
url: liveInfo.url,
type: "customFlv",
customType: {
customFlv: function (video: HTMLMediaElement, player: any) {
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: video.src,
isLive: true
},{
autoCleanupSourceBuffer: true
});
flvPlayer.attachMediaElement(video);
flvPlayer.load();
},
},
},
danmaku: {
id: String(liveInfo.id),
api: "live",
user: "xzz"
},
apiBackend: {
read: (options) => {
stompClient.connect({}, () => {
stompClient.subscribe("/topic/live/" + liveInfo.id, (frame) => {
const barrage: Barrage = JSON.parse(frame.body);
if(barrage.sign == sign) return;
const dan = {
type: barrage.barrageType,
color: barrage.color,
text: barrage.content,
time: barrage.time,
author: barrage.author
}
dp.danmaku.draw((dan as any) as DPlayerDanmakuItem);
});
})
options.success();
},
send: (options) => {
const danmu = options.data;
const barrage: Barrage = {
barrageType: danmu.type,
content: danmu.text,
time: dp.video.currentTime,
color: danmu.color,
author: "xzz",
videoId: liveInfo.id,
sign: sign
}
stompClient.send("/live/send/"+liveInfo.id,JSON.stringify(barrage),{sign: sign});
options.success();
}
}
});
关于其中的 nanoid
,Dplayer的设置发送弹幕时只要发送成功就会渲染已发送的弹幕,但由于订阅了相关房间的消息,所以也会接受到自己的消息,这时就需要识别自己发的消息不进行渲染,于是就有了生成nanoid作sign属性,识别自己。
后端实现
SpringBoot对WebSocket和Stomp协议提供了非常好的开箱即用支持,具体可以参照官方文档学习。
WebSocket+Stomp设置:
实际上SpringBoot使用Stomp是利用内存实现了一个简易消息队列,当然也提供了相应接口使用外部消息队列,这里使用内存消息队列,下面的配置还配置了一些监视器,文档中有具体解释。
@Configuration
@EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/connect").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/live");
registry.enableSimpleBroker("/topic","/queue");
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new SubListener());
}
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.interceptors(new SubListener());
registration.taskExecutor(new ThreadPoolTaskExecutor());
}
}
Controller方法
非常简单,只需要对客户端发来的消息即弹幕进行忠实的转发给所有订阅了该房间的客户端就行了。
@MessageMapping("/send/{id}")
@SendTo("/topic/live/{id}")
public Barrage liveBarrage(Message<Barrage> message, @DestinationVariable("id") Integer id) {
return message.getPayload();
}
}
Ok,这样实际上就已经简单完成了直播弹幕功能了,之后计划编写一个底层基于Netty并且外接消息队列的实现做练习。
杂项
直播过程中浏览器报错(关键字:append buffer)
虽然上面没有提及,但我在我的服务器上部署了直播服务器并实现了直播,过程中出现了上述错误,解决方案,利用flv.js创建player时config中加入autoCleanupSourceBuffer: true
,还可以设置其相关时间参数,详情参考github中api文档。
关于Sockjs导入
直接导入import SockJS from 'sockjs-client';
这个是需要node环境才能正常运行的,在浏览器中会报错。改为import SockJS from 'sockjs-client';
即可。