從零開始仿寫一個B站客戶端之-編譯ijkplayerandroid
從零開始仿寫一個B站客戶端之-抓包B站接口git
從零開始仿寫一個B站客戶端之-使用ijkplayer打造一個通用的播放器github
從零開始仿寫一個B站客戶端之-總體架構設計和網絡請求封裝web
既然要仿寫一個客戶端,那麼數據從哪兒來呢?剛開始打算用springboot本身寫後端,而後去學吧,第一個hello world項目就運行不起了(;′⌒`),這不是明示我勸退嗎。因此果斷抓包B站接口,不須要花費時間寫後端代碼,還還不缺數據來源。spring
抓包工具使用的是fiddler
,對手機上的B站客戶端進行抓包。須要注意的是必須確保安裝fiddler
的電腦和手機在同一個局域網環境下 。json
fiddler
代理端口號安裝好fiddler
以後,在Tools->Options->Connections中這樣配:後端
Fiddler listens on port是手機鏈接fiddler時的代理端口號,.Allow remote computers to connect是容許遠程(手機端)發送請求。api
配置Https:瀏覽器
點擊ok以後,在手機上訪問電腦ip+8888,電腦ip查看方式是在cmd中輸入ipconfig:安全
我這裏的ip是192.168.0.134,手機端用自帶的瀏覽器訪問: http://192.168.0.134:8888下載證書並安裝。點擊最下面那個藍色連接安裝證書:
emmm...小米手機不能直接安裝,須要從更多設置 ->系統安全->加密與憑據->從sd卡中安裝證書
安裝證書完成以後,須要修改wifi的網絡,手動設置代理,代理服務器主機名爲電腦的IP地址,代理端口爲在fiddler裏設置的端口號,保存後,fiddler將可以收到手機上的請求信息:
準備就緒,能夠在手機上打開B站客戶端開始抓包了:
以直播up主的粉絲榜接口爲例,抓到的接口是這個樣子的:
http://api.live.bilibili.com/rankdb/v2/RoomRank/mobileMedalRank?actionKey=appkey&appkey=1d8b6e7d45233436&build=5400000&channel=bilibiil140&device=android&mobi_app=android&page=1&platform=android&roomid=2910685&ruid=33588706&ts=1556159005&sign=fdc0eb4340508ad9a62d1a27146a4183
複製代碼
這太長了,能夠剔除一些無用信息,變爲下面這樣:
http://api.live.bilibili.com/rankdb/v2/RoomRank/mobileMedalRank?page=1&roomid=2910685&ruid=33588706
複製代碼
其中page
表示第一頁,roomid
表示房間號,ruid
表示up主的uid。
有些接口是能夠剔除的,可是有些接口是必需要一家人整整齊齊的,好比獲取直播up主的uid信息就須要所有參數:
http://api.live.bilibili.com/xlive/app-room/v1/index/getInfoByRoom?actionKey=appkey&appkey=1d8b6e7d45233436&build=5400000&channel=bilibiil140&device=android&mobi_app=android&platform=android&room_id=2910685&ts=1556157467&sign=811b018c9e54efad87e4ec16a76cd111
複製代碼
前面的參數幾乎都是固定的,除了最後的roomid
和ts
以及sign
,若是有一個參數不正確,服務器就會返回下面的錯誤:
{
"code": -3,
"message": "API校驗密匙錯誤",
"ttl": 1
}
複製代碼
API校驗密匙就是最後一個參數sign
,它是經過前面的參數排序以後,加上SecretKey
作md5生成的,其中SecretKey
存放在了so庫中,具體操做參考了@Misery_Dx的:
獲取sign的代碼:
fun getSign(map: Map<String, Any>): String {
//拼接參數(按順序) + SecretKey
val orignSign = getUrlParamsByMap(map) + SECRET_KEY
//進行MD5加密
var sign = ""
try {
sign = MD5Util.getMD5(orignSign).trim()
Log.i(TAG, "加密後的sign: $sign")
} catch (e: NoSuchAlgorithmException) {
Log.e(TAG, "sign encryption failed: ${e.printStackTrace()}")
}
return sign
}
/** * 將map轉換成url參數 * @param map * @return */
fun getUrlParamsByMap(map: Map<String, Any>): String {
var params = StringBuffer()
val it = map.iterator()
while (it.hasNext()) {
val str = it.next()
params.append(str.key)
params.append("=")
params.append(str.value)
if (it.hasNext()) {
params.append("&")
}
}
return params.toString()
}
複製代碼
能獲取到sign
,大部分的問題就解決了。
直播彈幕的獲取參考@lovelyyoshino 的直播彈幕 WebSocket 協議,嗯。。。沒搞定,不知道是我發送的封包數據有問題仍是B站直播彈幕協議變了,若是有大佬能搞定,但願能不吝賜教,感謝感謝~。
這是發送數據封包的方法:
/** * @param cmd 命令 * @param data 數據包 */
private fun sendCmd(cmd: Int, data: ByteArray, webSocket: WebSocket){
var buffer = ByteBuffer.allocate(16 + data.size)
buffer.order(ByteOrder.BIG_ENDIAN) //字節序爲大端模式
buffer.putInt(16 + data.size)
buffer.putShort(16) //頭部長度
buffer.putShort(1) //協議版本,目前是1
buffer.putInt(cmd) //操做碼(封包類型)
buffer.putInt(1) //sequence,能夠取常數1
buffer.put(data)
webSocket.send(ByteString.of(buffer))
}
複製代碼
這是使用okhttp3自帶的websocket實現的加入房間:
private var webSocket: okhttp3.WebSocket? =null
fun joinRoom(roomId:Int){
var client = OkHttpClient.Builder().build()
var request = Request
.Builder()
.url(GlobalProperties.LIVE_DANMAKU_URL)
.build()
webSocket = client.newWebSocket(request,object : WebSocketListener(){
override fun onOpen(webSocket: okhttp3.WebSocket, response: Response) {
super.onOpen(webSocket, response)
var inRoomMessage = JSONObject()
inRoomMessage.put("clientver","1.6.3")
inRoomMessage.put("platform","web")
inRoomMessage.put("protover",2)
inRoomMessage.put("roomid",roomId) //必填
inRoomMessage.put("type",2)
var bytes = inRoomMessage.toString().toByteArray(Charsets.UTF_8)
sendCmd(7, bytes, webSocket)
Log.d(TAG,"websocket鏈接成功,發送進房消息$inRoomMessage")
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
super.onMessage(webSocket, bytes)
Log.d(TAG,"websocket接收消息$bytes")
}
override fun onClosed(webSocket: okhttp3.WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
Log.d(TAG,"websocket斷開鏈接")
exitRoom()
}
override fun onFailure(webSocket: okhttp3.WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
Log.d(TAG,"websocket鏈接失敗: $response , throw: $t")
exitRoom()
}
})
}
複製代碼
第一次鏈接成功以後,發送進房消息,而後鏈接就當即斷開了,應該是我發送的數據不對,才致使服務端主動斷開鏈接的。
目前暫時是使用直播歷史評論抓取,而不是實時的:
http://api.live.bilibili.com/xlive/app-room/v1/dM/gethistory?room_id=2910685
複製代碼
直播彈幕的獲取暫時就放後面再說了~
目前我抓取的接口都放在GlobalProperties
這個類裏面,不想本身抓包的同窗能夠去這裏看:
項目地址:仿BiliBili客戶端