解碼愛奇藝彈幕爲明文字符串,涉及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數組