原文地址
前言
这里以bilibili
的弹幕文件为例,弹幕文件一般为两种,ass
文件和xml
文件,前者对于普通用户更友好,后者对于程序员更友好。如果经常下载视频资源的小伙伴应该知道,ass
文件常常作为外挂字幕文件使用,同文件下一般视频播放器会自动加载同名ass
文件,可以通过这个直接实现弹幕播放,但是这个web
端是不行,现在web
播放器默认只支持webvtt
文件作为字幕资源。扯远了,我们这里以B站弹幕 + DPlayer弹幕源引擎的背景简单说下,如何在自己个人网站实现弹幕播放效果
实现
弹幕获取
获取弹幕源的方式有很多,这个最简单的还是使用浏览器插件,这个比较简单就不多说了
弹幕上传
本文使用的是xml
解析的方式,如果小伙伴觉得ass
文件解析更简单那么使用它也是不错的方法。由于我们的多媒体服务是反应式的,这里我们的示例代码都是反应式的,大家需要稍作调整
@PostMapping(value = "/your_path", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> subBarInsert(@RequestPart("file") FilePart file,
@PathVariable String videoId) {
return barrageService.insertSubBarrageList(file, videoId);
}
@Override
public Mono<Void> insertSubBarrageList(FilePart file, String videoId) {
@SuppressWarnings("deprecation")
Mono<byte[]> fileDataByte = DataBufferUtils.join(file.content())
.map(dataBuffer -> dataBuffer.asByteBuffer().array());
//save
return fileDataByte.map(BarrageParseUtils::genBarrageList)
.map(barrageDtosList -> {
VideoBarrageRef ref = new VideoBarrageRef();
//set data ...
return ref;
})
.flatMap(entity -> videoBarrageRefMapper.save(entity))
.then();
}
这里只是简单示例,缺少很多稳定性代码,大家自行根据需求添加
下方为弹幕的实际解析,xml
文件的解析参考本站Java解析Xml文件转为数据对象,这里就不重复了。该工具类还进行了弹幕压缩,不需要的小伙伴也可以去掉,本来应该在输出时随机压缩弹幕的,但是考虑到性能问题改在弹幕上传时候压缩。一般比较火的视频几万条弹幕是很正常的,所以如果在输出时候进行随机压缩,小服务器是没办法很快响应的
public class BarrageParseUtils {
public static final double MAX_BARRAGE_SIZE = 3000.0;
public static final int ZIP_OFFSET = 10;
public static final List<Integer> ZIP_INDEX = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
public static List<BarrageDto> genBarrageList(byte[] file) {
List<BarrageDto> barrageDtoList = new ArrayList<>();
try {
//convert to obj
XmlMapper xmlMapper = new XmlMapper();
SubBarrageCollection poppy = xmlMapper.readValue(file, SubBarrageCollection.class);
List<SubBarrage> subBarrageList = poppy.getSubBarrageList();
//zip ratio
double zipRatio = MAX_BARRAGE_SIZE / subBarrageList.size();
zipRatio = zipRatio > 1 ? 1 : zipRatio;
List<Integer> dynamicZipRate = ZIP_INDEX.subList(0, (int) (zipRatio * ZIP_OFFSET) + 1);
//convert to entity
for (SubBarrage subBarrage : subBarrageList) {
try {
BarrageDto barrageDto = new BarrageDto();
barrageDto.setText(subBarrage.getContent());
String[] barrageDetail = subBarrage.getProperty().split(",");
barrageDto.setTime(Double.valueOf(barrageDetail[0]));
if (!dynamicZipRate.contains((int) (barrageDto.getTime() * ZIP_OFFSET) % ZIP_OFFSET)) {
continue;
}
barrageDto.setColor(Integer.valueOf(barrageDetail[3]));
//todo https://github.com/DIYgod/DPlayer/blob/master/src/js/danmaku.js
barrageDto.setType(0);
barrageDto.setAuthor("");
barrageDtoList.add(barrageDto);
} catch (Exception ignored) {
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return barrageDtoList;
}
}
DPlayer
的弹幕结构在本站的Vue3下构建带有弹幕功能的Web播放器有介绍,同样不再赘述,这里简单给下后端实体类
public class BarrageDto {
/**
* video barrage time 5.312
*/
private Double time;
/**
* 233333
*/
private String text;
/**
* #fff
*/
private Integer color;
/**
* barrage site
*/
private Integer type;
/**
* author
*/
private String author;
}
弹幕发放
这里没啥,就按照前端数据结果直接取出来即可,这个给个简易示例
@Override
public Flux<BarrageDto> getBarrageList(String videoId) {
return videoBarrageRefMapper.getByVideoId(videoId)
.map(data -> JSON.parseArray(data.getBarrageTextSub(), BarrageDto.class))
.flatMapMany(Flux::fromIterable);
}