博文記錄
HTML5 2019-06-14 11:34:02 2484 0

前文

解码爱奇艺弹幕为明文字符串,涉及 ArrayBuffer、gzip、字符数组转换,主要是前端部分范畴,很多东西不太了解,做此记录。

某剧弹幕资源地址

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 请求完毕后处理的逻辑进行调试。

http://static.iqiyi.com/js/player_v1/skin.default.af360b73f3e281095e89.js

可悲的是测试了许久却始终走不进这段代码。

最后发现同解码逻辑一致还有其他几处逻辑,经测试,拖一拖进度条开关弹幕等待即可进入这段调试断点。

这里首先异步请求后端拿到弹幕编码资源,将其转换成为了 Unit8Array 的资源类型,之后将其 ungzip,再之将其转换成 string 即可获得弹幕 XML。

即分三步得到弹幕明文:

1.获得 Unit8Array 资源类型

爱奇艺原代码是

var r = new Uint8Array(i)

令我没有想到的是这个函数是 javascript 内置的,也白白在这上面耗费了很多时间。

这里 变量 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()

2.ungzip 解压数据

这里实现拿到的数组进行 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()

解压之后数据量会变的很大。

3.Unit8ArrayToStr

最后我们再将其转换为肉眼可识别的 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
}

 

后文

这个解码即如此,反向来看这个编码并且压缩之后体积会小很多,很适合一些大数据量场景向后端提交时使用。

扩展文章

TextEncoder

Conversion between UTF-8 ArrayBuffer and String

使用 jQuery AJAX 读取二进制数据

Http 请求中 Content-Type 讲解

javascript 中 string 转 UTF8 格式 byte 数组

READING BINARY DATA USING JQUERY AJAX

pako.js 对数据进行 gzip 压缩传递到后台解析,解决数据量大的请求问题

media iamge
StudioEIM - 冒险者讲习所
0:00