原文地址
前言
這里以bilibili的彈幕文件為例,彈幕文件一般為兩種,ass文件和xml文件,前者對于普通用戶更友好,后者對于程序員更友好。如果經常下載視頻資源的小伙伴應該知道,ass文件常常作為外掛字幕文件使用,同文件下一般視頻播放器會自動加載同名ass文件,可以通過這個直接實現(xiàn)彈幕播放,但是這個web端是不行,現(xiàn)在web播放器默認只支持webvtt文件作為字幕資源。扯遠了,我們這里以B站彈幕 + DPlayer彈幕源引擎的背景簡單說下,如何在自己個人網站實現(xiàn)彈幕播放效果
實現(xiàn)
彈幕獲取
獲取彈幕源的方式有很多,這個最簡單的還是使用瀏覽器插件,這個比較簡單就不多說了
彈幕上傳
本文使用的是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();
}
這里只是簡單示例,缺少很多穩(wěn)定性代碼,大家自行根據(jù)需求添加
下方為彈幕的實際解析,xml文件的解析參考本站Java解析Xml文件轉為數(shù)據(jù)對象,這里就不重復了。該工具類還進行了彈幕壓縮,不需要的小伙伴也可以去掉,本來應該在輸出時隨機壓縮彈幕的,但是考慮到性能問題改在彈幕上傳時候壓縮。一般比較火的視頻幾萬條彈幕是很正常的,所以如果在輸出時候進行隨機壓縮,小服務器是沒辦法很快響應的
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;
}
彈幕發(fā)放
這里沒啥,就按照前端數(shù)據(jù)結果直接取出來即可,這個給個簡易示例
@Override
public Flux<BarrageDto> getBarrageList(String videoId) {
return videoBarrageRefMapper.getByVideoId(videoId)
.map(data -> JSON.parseArray(data.getBarrageTextSub(), BarrageDto.class))
.flatMapMany(Flux::fromIterable);
}