修改客户端生命周期接口
This commit is contained in:
parent
fd9fdae6ef
commit
34d1a8a44a
@ -73,18 +73,13 @@ class BLiveClient:
|
|||||||
self,
|
self,
|
||||||
room_id,
|
room_id,
|
||||||
uid=0,
|
uid=0,
|
||||||
session: aiohttp.ClientSession = None,
|
session: Optional[aiohttp.ClientSession] = None,
|
||||||
heartbeat_interval=30,
|
heartbeat_interval=30,
|
||||||
ssl: Union[bool, ssl_.SSLContext] = True,
|
ssl: Union[bool, ssl_.SSLContext] = True,
|
||||||
loop: asyncio.BaseEventLoop = None,
|
loop: Optional[asyncio.BaseEventLoop] = None,
|
||||||
):
|
):
|
||||||
# 用来init_room的临时房间ID
|
# 用来init_room的临时房间ID,可以用短ID
|
||||||
self._tmp_room_id = room_id
|
self._tmp_room_id = room_id
|
||||||
# 调用init_room后初始化
|
|
||||||
self._room_id = self._room_short_id = self._room_owner_uid = None
|
|
||||||
# [{host: "tx-bj4-live-comet-04.chat.bilibili.com", port: 2243, wss_port: 443, ws_port: 2244}, ...]
|
|
||||||
self._host_server_list = None
|
|
||||||
self._host_server_token = None
|
|
||||||
self._uid = uid
|
self._uid = uid
|
||||||
|
|
||||||
if loop is not None:
|
if loop is not None:
|
||||||
@ -93,7 +88,6 @@ class BLiveClient:
|
|||||||
self._loop = session.loop # noqa
|
self._loop = session.loop # noqa
|
||||||
else:
|
else:
|
||||||
self._loop = asyncio.get_event_loop()
|
self._loop = asyncio.get_event_loop()
|
||||||
self._future = None
|
|
||||||
|
|
||||||
if session is None:
|
if session is None:
|
||||||
self._session = aiohttp.ClientSession(loop=self._loop, timeout=aiohttp.ClientTimeout(total=10))
|
self._session = aiohttp.ClientSession(loop=self._loop, timeout=aiohttp.ClientTimeout(total=10))
|
||||||
@ -102,43 +96,67 @@ class BLiveClient:
|
|||||||
self._session = session
|
self._session = session
|
||||||
self._own_session = False
|
self._own_session = False
|
||||||
if self._session.loop is not self._loop: # noqa
|
if self._session.loop is not self._loop: # noqa
|
||||||
raise RuntimeError('BLiveClient and session has to use same event loop')
|
raise RuntimeError('BLiveClient and session must use the same event loop')
|
||||||
|
|
||||||
self._heartbeat_interval = heartbeat_interval
|
self._heartbeat_interval = heartbeat_interval
|
||||||
self._ssl = ssl if ssl else ssl_._create_unverified_context() # noqa
|
self._ssl = ssl if ssl else ssl_._create_unverified_context() # noqa
|
||||||
self._websocket = None
|
|
||||||
self._heartbeat_timer_handle = None
|
|
||||||
|
|
||||||
|
# 消息处理器,可动态增删
|
||||||
self._handlers: List[handlers.HandlerInterface] = []
|
self._handlers: List[handlers.HandlerInterface] = []
|
||||||
|
|
||||||
@property
|
# 在调用init_room后初始化的字段
|
||||||
def is_running(self):
|
# 真实房间ID
|
||||||
return self._future is not None
|
self._room_id = None
|
||||||
|
# 房间短ID,没有则为0
|
||||||
|
self._room_short_id = None
|
||||||
|
# 主播用户ID
|
||||||
|
self._room_owner_uid = None
|
||||||
|
# 弹幕服务器列表
|
||||||
|
# [{host: "tx-bj4-live-comet-04.chat.bilibili.com", port: 2243, wss_port: 443, ws_port: 2244}, ...]
|
||||||
|
self._host_server_list: Optional[List[dict]] = None
|
||||||
|
# 连接弹幕服务器用的token
|
||||||
|
self._host_server_token = None
|
||||||
|
|
||||||
|
# 在运行时初始化的字段
|
||||||
|
# websocket连接
|
||||||
|
self._websocket: Optional[aiohttp.ClientWebSocketResponse] = None
|
||||||
|
# 网络协程的future
|
||||||
|
self._network_future: Optional[asyncio.Future] = None
|
||||||
|
# 发心跳包定时器的handle
|
||||||
|
self._heartbeat_timer_handle: Optional[asyncio.TimerHandle] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def room_id(self):
|
def is_running(self) -> bool:
|
||||||
|
"""
|
||||||
|
本客户端正在运行,注意调用stop后还没完全停止也算正在运行
|
||||||
|
"""
|
||||||
|
return self._network_future is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def room_id(self) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
房间ID,调用init_room后初始化
|
房间ID,调用init_room后初始化
|
||||||
"""
|
"""
|
||||||
return self._room_id
|
return self._room_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def room_short_id(self):
|
def room_short_id(self) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
房间短ID,没有则为0,调用init_room后初始化
|
房间短ID,没有则为0,调用init_room后初始化
|
||||||
"""
|
"""
|
||||||
return self._room_short_id
|
return self._room_short_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def room_owner_uid(self):
|
def room_owner_uid(self) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
主播ID,调用init_room后初始化
|
主播用户ID,调用init_room后初始化
|
||||||
"""
|
"""
|
||||||
return self._room_owner_uid
|
return self._room_owner_uid
|
||||||
|
|
||||||
def add_handler(self, handler: 'handlers.HandlerInterface'):
|
def add_handler(self, handler: 'handlers.HandlerInterface'):
|
||||||
"""
|
"""
|
||||||
添加消息处理器
|
添加消息处理器
|
||||||
|
|
||||||
:param handler: 消息处理器
|
:param handler: 消息处理器
|
||||||
"""
|
"""
|
||||||
if handler not in self._handlers:
|
if handler not in self._handlers:
|
||||||
@ -147,6 +165,7 @@ class BLiveClient:
|
|||||||
def remove_handler(self, handler: 'handlers.HandlerInterface'):
|
def remove_handler(self, handler: 'handlers.HandlerInterface'):
|
||||||
"""
|
"""
|
||||||
移除消息处理器
|
移除消息处理器
|
||||||
|
|
||||||
:param handler: 消息处理器
|
:param handler: 消息处理器
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -156,37 +175,60 @@ class BLiveClient:
|
|||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
创建相关的协程
|
启动本客户端
|
||||||
:return: 协程的future
|
|
||||||
"""
|
"""
|
||||||
if self._future is not None:
|
if self._network_future is not None:
|
||||||
raise RuntimeError('This client is already running')
|
logger.warning('room %s 已经在运行中,不能再次start', self.room_id)
|
||||||
self._future = asyncio.ensure_future(self._message_loop(), loop=self._loop)
|
return
|
||||||
self._future.add_done_callback(self.__on_message_loop_done)
|
|
||||||
return self._future
|
|
||||||
|
|
||||||
def __on_message_loop_done(self, future):
|
self._network_future = asyncio.ensure_future(self._network_coroutine(), loop=self._loop)
|
||||||
self._future = None
|
self._network_future.add_done_callback(self.__on_network_coroutine_done)
|
||||||
logger.debug('room %s 消息协程结束', self.room_id)
|
|
||||||
|
def __on_network_coroutine_done(self, future):
|
||||||
|
self._network_future = None
|
||||||
|
|
||||||
|
logger.debug('room %s 网络协程结束', self.room_id)
|
||||||
exception = future.exception()
|
exception = future.exception()
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
logger.exception('room %s 消息协程异常结束:', self.room_id,
|
exc_info = (type(exception), exception, exception.__traceback__)
|
||||||
exc_info=(type(exception), exception, exception.__traceback__))
|
logger.exception('room %s 网络协程异常结束:', self.room_id, exc_info=exc_info)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""
|
"""
|
||||||
停止相关的协程
|
停止本客户端
|
||||||
:return: 协程的future
|
|
||||||
"""
|
"""
|
||||||
if self._future is None:
|
if self._network_future is None:
|
||||||
raise RuntimeError('This client is not running')
|
logger.warning('room %s 已经停止,不能再次stop', self.room_id)
|
||||||
self._future.cancel()
|
return
|
||||||
return self._future
|
|
||||||
|
self._network_future.cancel()
|
||||||
|
|
||||||
|
async def stop_and_close(self):
|
||||||
|
"""
|
||||||
|
停止本客户端并释放本客户端的资源,调用后本客户端将不可用
|
||||||
|
"""
|
||||||
|
self.stop()
|
||||||
|
await self.join()
|
||||||
|
await self.close()
|
||||||
|
|
||||||
|
async def join(self):
|
||||||
|
"""
|
||||||
|
等待本客户端停止
|
||||||
|
"""
|
||||||
|
if self._network_future is None:
|
||||||
|
logger.warning('room %s 已经停止,不能join', self.room_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._network_future
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
"""
|
"""
|
||||||
如果session是自己创建的则关闭session
|
释放本客户端的资源,调用后本客户端将不可用
|
||||||
"""
|
"""
|
||||||
|
if self._network_future is not None:
|
||||||
|
logger.warning('room %s 在运行状态中调用了close', self.room_id)
|
||||||
|
|
||||||
|
# 如果session是自己创建的则关闭session
|
||||||
if self._own_session:
|
if self._own_session:
|
||||||
await self._session.close()
|
await self._session.close()
|
||||||
|
|
||||||
@ -275,18 +317,18 @@ class BLiveClient:
|
|||||||
|
|
||||||
async def _send_auth(self):
|
async def _send_auth(self):
|
||||||
auth_params = {
|
auth_params = {
|
||||||
'uid': self._uid,
|
'uid': self._uid,
|
||||||
'roomid': self._room_id,
|
'roomid': self._room_id,
|
||||||
'protover': 2,
|
'protover': 2,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'clientver': '1.14.3',
|
'clientver': '1.14.3',
|
||||||
'type': 2
|
'type': 2
|
||||||
}
|
}
|
||||||
if self._host_server_token is not None:
|
if self._host_server_token is not None:
|
||||||
auth_params['key'] = self._host_server_token
|
auth_params['key'] = self._host_server_token
|
||||||
await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH))
|
await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH))
|
||||||
|
|
||||||
async def _message_loop(self):
|
async def _network_coroutine(self):
|
||||||
# 如果之前未初始化则初始化
|
# 如果之前未初始化则初始化
|
||||||
if self._host_server_token is None:
|
if self._host_server_token is None:
|
||||||
if not await self.init_room():
|
if not await self.init_room():
|
||||||
|
@ -13,7 +13,7 @@ __all__ = (
|
|||||||
logger = logging.getLogger('blivedm')
|
logger = logging.getLogger('blivedm')
|
||||||
|
|
||||||
# 常见可忽略的cmd
|
# 常见可忽略的cmd
|
||||||
FREQUENT_CMDS = (
|
IGNORED_CMDS = (
|
||||||
'INTERACT_WORD',
|
'INTERACT_WORD',
|
||||||
'ROOM_BANNER',
|
'ROOM_BANNER',
|
||||||
'ROOM_REAL_TIME_MESSAGE_UPDATE',
|
'ROOM_REAL_TIME_MESSAGE_UPDATE',
|
||||||
@ -55,7 +55,7 @@ class BaseHandler(HandlerInterface):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __heartbeat_callback(self, client: client_.BLiveClient, command: dict):
|
def __heartbeat_callback(self, client: client_.BLiveClient, command: dict):
|
||||||
return self._on_popularity(client, models.HeartbeatMessage.from_command(command['data']))
|
return self._on_heartbeat(client, models.HeartbeatMessage.from_command(command['data']))
|
||||||
|
|
||||||
def __danmu_msg_callback(self, client: client_.BLiveClient, command: dict):
|
def __danmu_msg_callback(self, client: client_.BLiveClient, command: dict):
|
||||||
return self._on_danmaku(client, models.DanmakuMessage.from_command(command['info']))
|
return self._on_danmaku(client, models.DanmakuMessage.from_command(command['info']))
|
||||||
@ -95,7 +95,7 @@ class BaseHandler(HandlerInterface):
|
|||||||
'SUPER_CHAT_MESSAGE_DELETE': __super_chat_message_delete_callback,
|
'SUPER_CHAT_MESSAGE_DELETE': __super_chat_message_delete_callback,
|
||||||
}
|
}
|
||||||
# 忽略其他常见cmd
|
# 忽略其他常见cmd
|
||||||
for cmd in FREQUENT_CMDS:
|
for cmd in IGNORED_CMDS:
|
||||||
_CMD_CALLBACK_DICT[cmd] = None
|
_CMD_CALLBACK_DICT[cmd] = None
|
||||||
del cmd
|
del cmd
|
||||||
|
|
||||||
@ -116,9 +116,9 @@ class BaseHandler(HandlerInterface):
|
|||||||
if callback is not None:
|
if callback is not None:
|
||||||
await callback(self, client, command)
|
await callback(self, client, command)
|
||||||
|
|
||||||
async def _on_popularity(self, client: client_.BLiveClient, message: models.HeartbeatMessage):
|
async def _on_heartbeat(self, client: client_.BLiveClient, message: models.HeartbeatMessage):
|
||||||
"""
|
"""
|
||||||
收到人气值
|
收到心跳包(人气值)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def _on_danmaku(self, client: client_.BLiveClient, message: models.DanmakuMessage):
|
async def _on_danmaku(self, client: client_.BLiveClient, message: models.DanmakuMessage):
|
||||||
|
13
sample.py
13
sample.py
@ -7,17 +7,17 @@ import blivedm
|
|||||||
async def main():
|
async def main():
|
||||||
# 直播间ID的取值看直播间URL
|
# 直播间ID的取值看直播间URL
|
||||||
# 如果SSL验证失败就把ssl设为False,B站真的有过忘续证书的情况
|
# 如果SSL验证失败就把ssl设为False,B站真的有过忘续证书的情况
|
||||||
client = blivedm.BLiveClient(room_id=411318, ssl=True)
|
client = blivedm.BLiveClient(room_id=21449083, ssl=True)
|
||||||
handler = MyHandler()
|
handler = MyHandler()
|
||||||
client.add_handler(handler)
|
client.add_handler(handler)
|
||||||
|
|
||||||
future = client.start()
|
client.start()
|
||||||
try:
|
try:
|
||||||
# 5秒后停止,测试用
|
# 5秒后停止,测试用
|
||||||
# await asyncio.sleep(5)
|
# await asyncio.sleep(5)
|
||||||
# future = client.stop()
|
# client.stop()
|
||||||
|
|
||||||
await future
|
await client.join()
|
||||||
finally:
|
finally:
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
@ -28,10 +28,11 @@ class MyHandler(blivedm.BaseHandler):
|
|||||||
|
|
||||||
# 入场消息回调
|
# 入场消息回调
|
||||||
async def __interact_word_callback(self, client: blivedm.BLiveClient, command: dict):
|
async def __interact_word_callback(self, client: blivedm.BLiveClient, command: dict):
|
||||||
print(f"self_type={type(self).__name__}, room_id={client.room_id}, uname={command['data']['uname']}")
|
print(f"INTERACT_WORD: self_type={type(self).__name__}, room_id={client.room_id},"
|
||||||
|
f" uname={command['data']['uname']}")
|
||||||
_CMD_CALLBACK_DICT['INTERACT_WORD'] = __interact_word_callback # noqa
|
_CMD_CALLBACK_DICT['INTERACT_WORD'] = __interact_word_callback # noqa
|
||||||
|
|
||||||
async def _on_popularity(self, client: blivedm.BLiveClient, message: blivedm.HeartbeatMessage):
|
async def _on_heartbeat(self, client: blivedm.BLiveClient, message: blivedm.HeartbeatMessage):
|
||||||
print(f'当前人气值:{message.popularity}')
|
print(f'当前人气值:{message.popularity}')
|
||||||
|
|
||||||
async def _on_danmaku(self, client: blivedm.BLiveClient, message: blivedm.DanmakuMessage):
|
async def _on_danmaku(self, client: blivedm.BLiveClient, message: blivedm.DanmakuMessage):
|
||||||
|
Loading…
Reference in New Issue
Block a user