解码爱奇艺弹幕为明文字符串,涉及ArrayBuffer、gzip、字符数组转换,主要是前端部分范畴,不少东西不太了解,作此记录。javascript
某剧弹幕资源地址php
http://cmts.iqiyi.com/bullet/77/00/874217700_300_3.z?business=danmu&is_iqiyi=true&is_video_page=true&tvid=874217700&albumid=205025001&categoryid=2
尝试请求这个地址,发现资源结果是这样儿的,而很是规的xml/Json。前端
最后抓包,在JS中xhr请求完毕后处理的逻辑进行调试。java
http://static.iqiyi.com/js/player_v1/skin.default.af360b73f3e281095e89.js
可悲的是测试了许久却始终走不进这段代码。node
最后发现同解码逻辑一致还有其余几处逻辑,经测试,拖一拖进度条开关弹幕等待便可进入这段调试断点。jquery
这里首先异步请求后端拿到弹幕编码资源,将其转换成为了Unit8Array的资源类型,以后将其ungzip,再之将其转换成string便可得到弹幕XML。webpack
即分三步获得弹幕明文:git
爱奇艺原代码是github
var r = new Uint8Array(i)
令我没有想到的是这个函数是javascript内置的,也白白在这上面耗费了不少时间。web
这里 变量 i 被赋值的是异步的请求结果
i = t.response
i的类型是ArrayBuffer,也就意味着返回结果一样这样,而Jquery的ajax资源类型不包含ArrayBuffer,返回的Text对应不上,转换貌似也有些问题,因此也就没有采用,最后采用原生XHR请求访问。
// 字符串转为ArrayBuffer对象,参数为字符串 function str2ab(str) { var buf = new ArrayBuffer(str.length*2); // 每一个字符占用2个字节 var bufView = new Uint16Array(buf); for (var i=0, strLen=str.length; i<strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
该代码将text转换为ArrayBuffer,摘自这里。
实现简单的XHR请求,设定返回资源为 ArrayBuffer。避免跨域,资源将由gzip.php代理请求。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/gzip.php', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { console.log(this.response) }; xhr.send()
测试返回资源类型是OK的,只须要再将其转换为Uint8Array就好。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/gzip.php', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { // response is unsigned 8 bit integer var responseArray = new Uint8Array(this.response); console.log(responseArray); }; xhr.send()
这里实现拿到的数组进行ungzip解压。
这里无法直接用爱奇艺的代码了,webpack打包的不成样子,无法剥离。直接引用现成的库。
https://raw.githubusercontent.com/nodeca/pako/master/dist/pako.js
将其下载到项目目录里再使用,不然出现跨域没法执行。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/gzip.php', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { // response is unsigned 8 bit integer var responseArray = new Uint8Array(this.response); console.log(pako.ungzip(responseArray)); }; xhr.send()
解压以后数据量会变的很大。
最后咱们再将其转换为肉眼可识别的XML文本。
var xhr = new XMLHttpRequest(); xhr.open('GET', '/gzip.php', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { // response is unsigned 8 bit integer var responseArray = new Uint8Array(this.response); var responseString = new TextDecoder().decode(pako.ungzip(responseArray)); responseString = responseString.replace(/&#\d{2};/g, ""); console.log(responseString); }; xhr.send()
这里TextDecoder也是JavaScript内置的功能类。
最终返回结果
至此,弹幕解码完毕。
爱奇艺utf8ArrayToStr 转换剥离代码
function utf8ArrayToStr(e) { var t, a, i, r, n, o; for (t = "", i = e.length, a = 0; a < i; ) switch ((r = e[a++]) >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: t += String.fromCharCode(r); break; case 12: case 13: n = e[a++], t += String.fromCharCode((31 & r) << 6 | 63 & n); break; case 14: n = e[a++], o = e[a++], t += String.fromCharCode((15 & r) << 12 | (63 & n) << 6 | (63 & o) << 0) } return t }
这个解码即如此,反向来看这个编码而且压缩以后体积会小不少,很适合一些大数据量场景向后端提交时使用。
扩展文章
Conversion between UTF-8 ArrayBuffer and String
javascript中string转UTF8格式byte数组