Skip to content

Commit

Permalink
修復近期B站直播彈幕抓取斷流的問題
Browse files Browse the repository at this point in the history
  • Loading branch information
zhimingshenjun committed Oct 10, 2021
1 parent 3e049e2 commit 0e7fe77
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 66 deletions.
28 changes: 11 additions & 17 deletions DD监控室.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ def __init__(self, title):
self.setWindowTitle(title)
self.setObjectName(f'dock-{title}')
self.setFloating(False)
self.setAllowedAreas(Qt.LeftDockWidgetArea |
Qt.RightDockWidgetArea | Qt.TopDockWidgetArea)
self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea | Qt.TopDockWidgetArea)


class StartLiveWindow(QWidget):
Expand Down Expand Up @@ -149,8 +148,7 @@ def __init__(self):
layout.addWidget(okButton, 2, 3, 1, 1)

def selectCopyPath(self):
savePath = QFileDialog.getExistingDirectory(
self, "选择备份缓存路径", None, QFileDialog.ShowDirsOnly)
savePath = QFileDialog.getExistingDirectory(self, "选择备份缓存路径", None, QFileDialog.ShowDirsOnly)
if savePath:
self.savePathEdit.setText(savePath)

Expand All @@ -167,9 +165,9 @@ def __init__(self):
self.resize(350, 150)
self.setWindowTitle('当前版本')
layout = QGridLayout(self)
layout.addWidget(QLabel('DD监控室 v2.6正式版'), 0, 0, 1, 2)
layout.addWidget(QLabel('DD监控室 v2.8正式版'), 0, 0, 1, 2)
layout.addWidget(QLabel('by 神君Channel'), 1, 0, 1, 2)
layout.addWidget(QLabel('特别鸣谢:大锅饭 美东矿业 inkydragon'), 2, 0, 1, 2)
layout.addWidget(QLabel('特别鸣谢:大锅饭 美东矿业 inkydragon 聪_哥'), 2, 0, 1, 2)
releases_url = QLabel('')
releases_url.setOpenExternalLinks(True)
releases_url.setText(_translate("MainWindow", "<html><head/><body><p><a href=\"https://space.bilibili.com/637783\">\
Expand All @@ -182,8 +180,7 @@ def __init__(self):
layout.addWidget(checkButton, 0, 2, 1, 1)

def checkUpdate(self):
QDesktopServices.openUrl(
QUrl(r'https://github.com/zhimingshenjun/DD_Monitor/releases/tag/DD_Monitor'))
QDesktopServices.openUrl(QUrl(r'https://github.com/zhimingshenjun/DD_Monitor/releases/tag/DD_Monitor'))


class HotKey(QWidget):
Expand All @@ -209,16 +206,14 @@ def __init__(self, config):

def run(self):
try:
configJSONPath = os.path.join(
application_path, r'utils/config.json')
configJSONPath = os.path.join(application_path, r'utils/config.json')
with codecs.open(configJSONPath, 'w', encoding='utf-8') as f:
f.write(json.dumps(self.config, ensure_ascii=False))
except:
logging.exception('config.json 写入失败')

try: # 备份 防止存储config时崩溃
configJSONPath = os.path.join(
application_path, r'utils/config_备份%d.json' % self.backupNumber)
configJSONPath = os.path.join(application_path, r'utils/config_备份%d.json' % self.backupNumber)
self.backupNumber += 1
if self.backupNumber == 4:
self.backupNumber = 1
Expand All @@ -234,12 +229,11 @@ class CheckDanmmuProvider(QThread):
"""检查弹幕服务器域名解析状态"""

def __init__(self):
super(CheckDanmmuProvider, self).__init__()

super(CheckDanmmuProvider,self).__init__()
def run(self):
try:
anwsers = dns.resolver.resolve(
'broadcastlv.chat.bilibili.com', 'A')
anwsers = dns.resolver.resolve('broadcastlv.chat.bilibili.com', 'A')
danmu_ip = anwsers[0].to_text()
logging.info("弹幕IP: %s" % danmu_ip)
except Exception as e:
Expand Down Expand Up @@ -381,7 +375,7 @@ def __init__(self, cacheFolder, progressBar, progressText):
vlcProgressCounter = 1
for i in range(16):
if len(self.config['danmu'][i]) < 8:
self.config['danmu'][i].append(0)
self.config['danmu'][i].append(3)
volume = self.config['volume'][i]
progressText.setText('设置第%s个主层播放器...' % str(i + 1))
self.videoWidgetList.append(VideoWidget(i, volume, cacheFolder, textSetting=self.config['danmu'][i],
Expand Down
38 changes: 26 additions & 12 deletions VideoWidget_vlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ def __init__(self, id, volume, cacheFolder, top=False, title='', resize=[],
# 关闭窗口
self.stop = PushButton(self.style().standardIcon(
QStyle.SP_DialogCancelButton))
self.stop.clicked.connect(self.mediaStop)
self.stop.clicked.connect(self._mediaStop)
frameLayout.addWidget(self.stop)

# ---- IO 交互设置 ----
Expand All @@ -431,6 +431,10 @@ def __init__(self, id, volume, cacheFolder, top=False, title='', resize=[],
self.moveTimer.timeout.connect(self.initTextPos)
self.moveTimer.start(50)

# self.reloadDanmuTimer = QTimer()
# self.reloadDanmuTimer.timeout.connect(self.reloadDanmu)
# self.reloadDanmuTimer.start(10000)

# 检查播放卡住的定时器
self.checkPlaying = QTimer()
self.checkPlaying.timeout.connect(self.checkPlayStatus)
Expand Down Expand Up @@ -892,11 +896,7 @@ def closeDanmu(self):
# self.setTranslator.emit([self.id, False])

def stopDanmuMessage(self):
try:
self.danmu.message.disconnect(self.playDanmu)
except:
logging.exception('停止弹幕出错')
self.danmu.terminate()
self.stopDanmu()

def showDanmu(self):
if self.textBrowser.isHidden():
Expand Down Expand Up @@ -986,6 +986,9 @@ def mediaReload(self):
else:
self.mediaStop()

def _mediaStop(self):
self.mediaStop()

def mediaStop(self, deleteMedia=True):
# self.userPause = True
self.oldTitle, self.oldUname = '', ''
Expand All @@ -997,17 +1000,27 @@ def mediaStop(self, deleteMedia=True):
self.play.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
if deleteMedia:
self.deleteMedia.emit(self.id)
try:
self.danmu.message.disconnect(self.playDanmu)
except:
logging.exception('停止弹幕出错')
self.getMediaURL.recordToken = False
self.getMediaURL.checkTimer.stop()
self.checkPlaying.stop()
self.stopDanmu()

def stopDanmu(self):
# try:
try:
self.danmu.message.disconnect(self.playDanmu)
except:
pass
self.danmu.terminate()
self.danmu.quit()
self.danmu.wait()

def reloadDanmu(self):
self.stopDanmu()
self.danmu.setRoomID(self.roomID)
self.danmu.message.connect(self.playDanmu)
self.danmu.start()

def setMedia(self, cacheName):
self.retryTimes = 0
self.cacheName = cacheName
Expand All @@ -1016,7 +1029,8 @@ def setMedia(self, cacheName):
try:
self.danmu.message.disconnect(self.playDanmu)
except:
logging.exception('停止弹幕出错')
pass
# logging.exception('停止弹幕出错')
if self.startWithDanmu:
self.danmu.message.connect(self.playDanmu)
self.danmu.terminate()
Expand Down Expand Up @@ -1126,7 +1140,7 @@ def playDanmu(self, message):
self.textBrowser.msgsBrowser.append(message)
return
for symbol in self.filters:
if symbol in message[message.find(': ')+2:]:
if symbol in message:
self.textBrowser.transBrowser.append(message)
token = True
break
Expand Down
84 changes: 47 additions & 37 deletions remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,22 @@
import zlib
import json
import requests
from aiowebsocket.converses import AioWebSocket
# from aiowebsocket.converses import AioWebSocket
from PyQt5.QtCore import QThread, pyqtSignal
import logging
from bilibili_api import live


class Live(live.LiveDanmaku):

def __init__(self, room_display_id):
super().__init__(room_display_id)

def register(self, event: str, func: callable):
self.__getattribute__("_LiveDanmaku__event_handlers")[event].append(func)


class remoteThread(QThread):
"""
TODO: 换用 bilibili_api.live.LiveDanmaku(room_display_id)
"""
message = pyqtSignal(str)

def __init__(self, roomID):
Expand All @@ -31,32 +38,38 @@ def __init__(self, roomID):
if '"roomid":' in line:
self.roomID = line.split('"roomid":')[1].split(',')[0]

async def startup(self, url):
logging.info('尝试打开 %s 的弹幕Socket' % self.roomID)
data_raw = '000000{headerLen}0010000100000007000000017b22726f6f6d6964223a{roomid}7d'
data_raw = data_raw.format(headerLen=hex(27 + len(self.roomID))[2:],
roomid=''.join(map(lambda x: hex(ord(x))[2:], list(self.roomID))))
async with AioWebSocket(url) as aws:
try:
converse = aws.manipulator
await converse.send(bytes.fromhex(data_raw))
tasks = [self.receDM(converse), self.sendHeartBeat(converse)]
await asyncio.wait(tasks)
except:
logging.exception('弹幕Socket打开失败')
async def startup(self):
self.roomID = int(self.roomID)
self.room = Live(self.roomID)
self.room.add_event_listener('DANMU_MSG', self.danmu) # 用户发送弹幕
# self.room.add_event_listener('SEND_GIFT', self.gift) # 礼物
# self.room.add_event_listener('COMBO_SEND', self.combo_gift) # 礼物连击
# self.room.add_event_listener('GUARD_BUY', self.guard) # 续费大航海
self.room.add_event_listener('SUPER_CHAT_MESSAGE', self.sc) # 醒目留言(SC)
# self.room.add_event_listener('INTERACT_WORD', self.enter) # 用户进入直播间
await self.room.connect()
# await asyncio.wait([self.room.connect()])

async def sendHeartBeat(self, websocket):
logging.debug("向%s发送心跳包" % self.roomID)
hb = '00000010001000010000000200000001'
while True:
await asyncio.sleep(30)
await websocket.send(bytes.fromhex(hb))
async def danmu(self, jd):
self.message.emit(jd['data']['info'][1])

async def receDM(self, websocket):
while True:
recv_text = await websocket.receive()
logging.debug("从%s接收到DM" % self.roomID)
self.printDM(recv_text)
async def gift(self, event):
print(event)

async def combo_gift(self, event):
print(event)

async def guard(self, event):
print(event)

async def sc(self, jd):
jd = jd['data']
self.message.emit(
f"【SC(¥{jd['data']['price']}) {jd['data']['user_info']['uname']}: {jd['data']['message']}】"
)

async def enter(self, event):
print(event)

def printDM(self, data):
packetLen = int(data[:4].hex(), 16)
Expand Down Expand Up @@ -118,11 +131,12 @@ def getMetal(jd):
jd = json.loads(data[16:].decode('utf-8', errors='ignore'))
if jd['cmd'] == 'DANMU_MSG':
self.message.emit(
f"{userType[jd['info'][2][7]]}{adminType[jd['info'][2][2]]}{getMetal(jd)} {jd['info'][2][1]}: {jd['info'][1]}"
# f"{userType[jd['info'][2][7]]}{adminType[jd['info'][2][2]]}{getMetal(jd)} {jd['info'][2][1]}: {jd['info'][1]}"
f"{jd['info'][1]}"
)
elif jd['cmd'] == 'SUPER_CHAT_MESSAGE':
self.message.emit(
f"SC(¥{jd['data']['price']}) {getMetal(jd)} {jd['data']['user_info']['uname']}: {jd['data']['message']}"
f"SC(¥{jd['data']['price']}) {getMetal(jd)} {jd['data']['user_info']['uname']}: {jd['data']['message']}"
)
elif jd['cmd'] == 'SEND_GIFT':
if jd['data']['coin_type'] == "gold":
Expand Down Expand Up @@ -153,12 +167,8 @@ def getMetal(jd):
logging.exception('弹幕输出失败')

def setRoomID(self, roomID):
self.roomID = roomID
self.roomID = int(roomID)

def run(self):
remote = r'wss://broadcastlv.chat.bilibili.com:2245/sub'
try:
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(self.startup(remote))
except:
logging.exception('弹幕主循环出错')
asyncio.set_event_loop(asyncio.new_event_loop())
asyncio.get_event_loop().run_until_complete(self.startup())

0 comments on commit 0e7fe77

Please sign in to comment.