@bilibili-notify/live 0.1.0-alpha.4 → 0.1.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +120 -18
- package/lib/index.d.cts +27 -2
- package/lib/index.d.mts +27 -2
- package/lib/index.mjs +120 -18
- package/package.json +3 -3
package/lib/index.cjs
CHANGED
|
@@ -185,6 +185,8 @@ var RoomContextBase = class {
|
|
|
185
185
|
listenerRecord = {};
|
|
186
186
|
livePushTimerManager = /* @__PURE__ */ new Map();
|
|
187
187
|
disposed = false;
|
|
188
|
+
/** stopMonitoring 主动关闭 listener 时置位;RoomSession.onClose 消费后不做自愈重连。 */
|
|
189
|
+
intentionalCloseRooms = /* @__PURE__ */ new Set();
|
|
188
190
|
/** Cached protobuf type for INTERACT_WORD_V2 decoding (lazy-loaded). */
|
|
189
191
|
interactWord;
|
|
190
192
|
/**
|
|
@@ -222,6 +224,11 @@ var RoomContextBase = class {
|
|
|
222
224
|
emitViewers(uid, viewers) {
|
|
223
225
|
this._emitViewers?.(uid, viewers);
|
|
224
226
|
}
|
|
227
|
+
consumeIntentionalClose(roomId) {
|
|
228
|
+
const hit = this.intentionalCloseRooms.has(roomId);
|
|
229
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
230
|
+
return hit;
|
|
231
|
+
}
|
|
225
232
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
226
233
|
get imageRenderer() {
|
|
227
234
|
return this.config.imageEnabled === false ? null : this._getImageRenderer();
|
|
@@ -253,10 +260,12 @@ var RoomContextBase = class {
|
|
|
253
260
|
closeListener(roomId) {
|
|
254
261
|
const listener = this.listenerRecord[roomId];
|
|
255
262
|
if (!listener) {
|
|
263
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
256
264
|
this.logger.debug(`[conn] 直播间 [${roomId}] 连接不存在,跳过关闭`);
|
|
257
265
|
return;
|
|
258
266
|
}
|
|
259
267
|
if (listener.closed) {
|
|
268
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
260
269
|
this.logger.debug(`[conn] 直播间 [${roomId}] 连接已被远端断开`);
|
|
261
270
|
delete this.listenerRecord[roomId];
|
|
262
271
|
return;
|
|
@@ -280,6 +289,7 @@ var RoomContextBase = class {
|
|
|
280
289
|
stopMonitoring(reason, roomId) {
|
|
281
290
|
if (roomId) {
|
|
282
291
|
this.logger.error(`[conn] [${roomId}] ${reason},已停止该房间的监测`);
|
|
292
|
+
this.intentionalCloseRooms.add(roomId);
|
|
283
293
|
this.closeListener(roomId);
|
|
284
294
|
const timer = this.livePushTimerManager.get(roomId);
|
|
285
295
|
if (timer) {
|
|
@@ -337,6 +347,7 @@ var RoomContext = class extends RoomContextBase {
|
|
|
337
347
|
this.logger.warn(`[conn] 直播间 [${roomId}] 连接已存在,跳过创建`);
|
|
338
348
|
return true;
|
|
339
349
|
}
|
|
350
|
+
this.consumeIntentionalClose(roomId);
|
|
340
351
|
const cookiesStr = this.api.getCookiesHeader();
|
|
341
352
|
let mySelfInfo;
|
|
342
353
|
try {
|
|
@@ -711,14 +722,17 @@ var RoomSessionBase = class {
|
|
|
711
722
|
async bootstrap() {
|
|
712
723
|
if (!await this.ctx.startLiveRoomListener(this.sub.roomId, this.buildHandler())) {
|
|
713
724
|
await this.ctx.push.sendPrivateMsg(`直播间 [${this.sub.roomId}] 弹幕连接建立失败,已停止该房间监测`);
|
|
725
|
+
this.onMonitoringStopped();
|
|
714
726
|
this.ctx.closeListener(this.sub.roomId);
|
|
715
727
|
return;
|
|
716
728
|
}
|
|
717
729
|
if (!await this.useLiveRoomInfo(4) || !await this.useMasterInfo(4) || !this.liveRoomInfo || !this.masterInfo) {
|
|
718
730
|
await this.ctx.push.sendPrivateMsg("获取直播间信息失败,启动直播间弹幕检测失败");
|
|
731
|
+
this.onMonitoringStopped();
|
|
719
732
|
this.ctx.closeListener(this.sub.roomId);
|
|
720
733
|
return;
|
|
721
734
|
}
|
|
735
|
+
this.onListenerStarted();
|
|
722
736
|
this.ctx.logger.debug(`[stat] 当前粉丝数:${this.masterInfo.liveOpenFollowerNum}`);
|
|
723
737
|
if (this.liveRoomInfo.live_status === 1) {
|
|
724
738
|
this.liveTime = this.liveRoomInfo.live_time;
|
|
@@ -747,6 +761,10 @@ var RoomSessionBase = class {
|
|
|
747
761
|
this.armPeriodicTimer();
|
|
748
762
|
}
|
|
749
763
|
}
|
|
764
|
+
/** Hook for subclass-owned connection-health bookkeeping after listener bootstrap succeeds. */
|
|
765
|
+
onListenerStarted() {}
|
|
766
|
+
/** Hook for subclass-owned cleanup before this session intentionally stops monitoring. */
|
|
767
|
+
onMonitoringStopped() {}
|
|
750
768
|
async useLiveRoomInfo(liveType) {
|
|
751
769
|
const data = await this.ctx.getLiveRoomInfo(this.sub.roomId);
|
|
752
770
|
if (!data?.uid) return false;
|
|
@@ -796,6 +814,7 @@ var RoomSessionBase = class {
|
|
|
796
814
|
/** Periodic "正在直播" tick (callback for `setInterval`). */
|
|
797
815
|
async tickPushAtTime() {
|
|
798
816
|
if (!await this.useLiveRoomInfo(2) || !this.liveRoomInfo) {
|
|
817
|
+
this.onMonitoringStopped();
|
|
799
818
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播卡片失败", this.sub.roomId);
|
|
800
819
|
return;
|
|
801
820
|
}
|
|
@@ -846,6 +865,7 @@ var RoomSessionBase = class {
|
|
|
846
865
|
this.setLiveStatus(false);
|
|
847
866
|
this.ctx.danmakuCollector.clear(this.sub.roomId);
|
|
848
867
|
if (this.ctx.isDisposed()) return;
|
|
868
|
+
this.onMonitoringStopped();
|
|
849
869
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播下播卡片失败", this.sub.roomId);
|
|
850
870
|
return;
|
|
851
871
|
}
|
|
@@ -930,6 +950,8 @@ const RECONNECT_BACKOFF_MS = [
|
|
|
930
950
|
8e3,
|
|
931
951
|
16e3
|
|
932
952
|
];
|
|
953
|
+
/** B 站 live WS 静默自愈:每分钟检查一次,3 分钟无 heartbeat/消息即主动重连。 */
|
|
954
|
+
const LIVE_WS_WATCHDOG_INTERVAL_MS = 6e4;
|
|
933
955
|
var RoomSession = class extends RoomSessionBase {
|
|
934
956
|
lastViewersEmitMs = 0;
|
|
935
957
|
/**
|
|
@@ -948,10 +970,15 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
948
970
|
/** L3:退避 sleep 的 Disposable + 唤醒句柄,cancel/teardown 时清掉,不留回调到 expiry。 */
|
|
949
971
|
reconnectTimer;
|
|
950
972
|
reconnectWake;
|
|
951
|
-
|
|
973
|
+
lastLiveWsActivityAt = 0;
|
|
974
|
+
lastLiveWsActivityReason = "connected";
|
|
975
|
+
watchdogTimer;
|
|
976
|
+
watchdogReconnectCount = 0;
|
|
977
|
+
/** 外层主动停止 listener 时调用,阻止 onError/onClose/watchdog 触发重连。 */
|
|
952
978
|
cancel() {
|
|
953
979
|
this.cancelled = true;
|
|
954
980
|
this.reconnecting = false;
|
|
981
|
+
this.stopLiveWsWatchdog();
|
|
955
982
|
this.clearReconnectSleep();
|
|
956
983
|
}
|
|
957
984
|
/** L3:dispose 退避定时器并唤醒重连循环,使其立刻重校 cancelled/disposed 后退出。 */
|
|
@@ -961,12 +988,62 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
961
988
|
this.reconnectWake?.();
|
|
962
989
|
this.reconnectWake = void 0;
|
|
963
990
|
}
|
|
991
|
+
onListenerStarted() {
|
|
992
|
+
this.markLiveWsActivity("connected");
|
|
993
|
+
this.startLiveWsWatchdog();
|
|
994
|
+
}
|
|
995
|
+
onMonitoringStopped() {
|
|
996
|
+
this.cancel();
|
|
997
|
+
}
|
|
998
|
+
getWsHealthSnapshot() {
|
|
999
|
+
return {
|
|
1000
|
+
lastActivityAt: this.lastLiveWsActivityAt,
|
|
1001
|
+
lastActivityReason: this.lastLiveWsActivityReason,
|
|
1002
|
+
watchdogReconnectCount: this.watchdogReconnectCount
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
markLiveWsActivity(reason) {
|
|
1006
|
+
this.lastLiveWsActivityAt = Date.now();
|
|
1007
|
+
this.lastLiveWsActivityReason = reason;
|
|
1008
|
+
}
|
|
1009
|
+
startLiveWsWatchdog() {
|
|
1010
|
+
if (this.watchdogTimer || this.cancelled || this.ctx.isDisposed()) return;
|
|
1011
|
+
this.watchdogTimer = this.ctx.serviceCtx.setInterval(() => this.checkLiveWsWatchdog(), LIVE_WS_WATCHDOG_INTERVAL_MS);
|
|
1012
|
+
}
|
|
1013
|
+
stopLiveWsWatchdog() {
|
|
1014
|
+
this.watchdogTimer?.dispose();
|
|
1015
|
+
this.watchdogTimer = void 0;
|
|
1016
|
+
}
|
|
1017
|
+
checkLiveWsWatchdog() {
|
|
1018
|
+
if (this.cancelled || this.ctx.isDisposed() || this.reconnecting) return;
|
|
1019
|
+
if (this.lastLiveWsActivityAt <= 0) return;
|
|
1020
|
+
const staleMs = Date.now() - this.lastLiveWsActivityAt;
|
|
1021
|
+
if (staleMs < 18e4) return;
|
|
1022
|
+
this.watchdogReconnectCount++;
|
|
1023
|
+
this.reconnect("watchdog", `${Math.floor(staleMs / 1e3)}s 无 heartbeat/消息(last=${this.lastLiveWsActivityReason},watchdog=${this.watchdogReconnectCount})`);
|
|
1024
|
+
}
|
|
964
1025
|
buildHandler() {
|
|
965
1026
|
const base = {
|
|
1027
|
+
onOpen: () => this.markLiveWsActivity("open"),
|
|
1028
|
+
onStartListen: () => this.markLiveWsActivity("start-listen"),
|
|
1029
|
+
onClose: () => {
|
|
1030
|
+
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
1031
|
+
if (this.ctx.consumeIntentionalClose(this.sub.roomId)) return;
|
|
1032
|
+
this.markLiveWsActivity("close");
|
|
1033
|
+
this.reconnect("close");
|
|
1034
|
+
},
|
|
966
1035
|
onError: () => this.onError(),
|
|
967
|
-
|
|
968
|
-
|
|
1036
|
+
onAttentionChange: () => this.markLiveWsActivity("heartbeat"),
|
|
1037
|
+
onIncomeDanmu: ({ body }) => {
|
|
1038
|
+
this.markLiveWsActivity("danmu");
|
|
1039
|
+
this.onIncomeDanmu(body);
|
|
1040
|
+
},
|
|
1041
|
+
onIncomeSuperChat: ({ body }) => {
|
|
1042
|
+
this.markLiveWsActivity("superchat");
|
|
1043
|
+
return this.onIncomeSuperChat(body);
|
|
1044
|
+
},
|
|
969
1045
|
onWatchedChange: ({ body }) => {
|
|
1046
|
+
this.markLiveWsActivity("watched");
|
|
970
1047
|
this.liveData.watchedNum = body.text_small;
|
|
971
1048
|
const now = Date.now();
|
|
972
1049
|
if (now - this.lastViewersEmitMs >= VIEWERS_EMIT_THROTTLE_MS) {
|
|
@@ -975,42 +1052,61 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
975
1052
|
}
|
|
976
1053
|
},
|
|
977
1054
|
onLikedChange: ({ body }) => {
|
|
1055
|
+
this.markLiveWsActivity("liked");
|
|
978
1056
|
this.liveData.likedNum = body.count;
|
|
979
1057
|
},
|
|
980
|
-
onGuardBuy: ({ body }) =>
|
|
981
|
-
|
|
982
|
-
|
|
1058
|
+
onGuardBuy: ({ body }) => {
|
|
1059
|
+
this.markLiveWsActivity("guard");
|
|
1060
|
+
return this.onGuardBuy(body);
|
|
1061
|
+
},
|
|
1062
|
+
onLiveStart: () => {
|
|
1063
|
+
this.markLiveWsActivity("live-start");
|
|
1064
|
+
return this.onLiveStart();
|
|
1065
|
+
},
|
|
1066
|
+
onLiveEnd: () => {
|
|
1067
|
+
this.markLiveWsActivity("live-end");
|
|
1068
|
+
return this.onLiveEnd();
|
|
1069
|
+
}
|
|
983
1070
|
};
|
|
984
1071
|
if (!this.sub.customSpecialUsersEnterTheRoom.enable) return base;
|
|
985
1072
|
return {
|
|
986
1073
|
...base,
|
|
987
|
-
raw: { INTERACT_WORD_V2: (msg) =>
|
|
1074
|
+
raw: { INTERACT_WORD_V2: (msg) => {
|
|
1075
|
+
this.markLiveWsActivity("interact");
|
|
1076
|
+
return this.onInteractWordV2(msg);
|
|
1077
|
+
} }
|
|
988
1078
|
};
|
|
989
1079
|
}
|
|
990
|
-
|
|
1080
|
+
onError() {
|
|
1081
|
+
return this.reconnect("error");
|
|
1082
|
+
}
|
|
1083
|
+
async reconnect(reason, detail) {
|
|
991
1084
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
992
1085
|
if (this.reconnecting) return;
|
|
993
1086
|
this.reconnecting = true;
|
|
994
1087
|
try {
|
|
995
|
-
await this.reconnectLoop();
|
|
1088
|
+
await this.reconnectLoop(reason, detail);
|
|
996
1089
|
} finally {
|
|
997
1090
|
this.reconnecting = false;
|
|
998
1091
|
}
|
|
999
1092
|
}
|
|
1000
1093
|
/**
|
|
1001
|
-
* 退避重连循环(单飞,由
|
|
1094
|
+
* 退避重连循环(单飞,由 reconnect 持有)。`while` 取代旧的 `setTimeout(0)`
|
|
1002
1095
|
* 递归续链 —— 杜绝深栈递归 + 每步都丢弃的定时器 Disposable;每次 sleep 后
|
|
1003
1096
|
* 重校 cancelled/disposed,sleep 自身可被 cancel/teardown dispose。
|
|
1004
1097
|
*/
|
|
1005
|
-
async reconnectLoop() {
|
|
1098
|
+
async reconnectLoop(reason, detail) {
|
|
1006
1099
|
while (this.reconnectAttempts < RECONNECT_BACKOFF_MS.length) {
|
|
1007
1100
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
1008
|
-
|
|
1009
|
-
|
|
1101
|
+
if (reason === "error") {
|
|
1102
|
+
this.setLiveStatus(false);
|
|
1103
|
+
this.cancelPeriodicTimer();
|
|
1104
|
+
}
|
|
1010
1105
|
this.ctx.closeListener(this.sub.roomId);
|
|
1011
1106
|
const delay = RECONNECT_BACKOFF_MS[this.reconnectAttempts];
|
|
1012
1107
|
this.reconnectAttempts++;
|
|
1013
|
-
|
|
1108
|
+
const reasonText = this.describeReconnectReason(reason, detail);
|
|
1109
|
+
this.ctx.logger.warn(`[conn] 直播间 [${this.sub.roomId}] ${reasonText},${delay / 1e3}s 后重连(第 ${this.reconnectAttempts}/${RECONNECT_BACKOFF_MS.length} 次)`);
|
|
1014
1110
|
await this.sleepReconnect(delay);
|
|
1015
1111
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
1016
1112
|
let ok = false;
|
|
@@ -1024,6 +1120,7 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1024
1120
|
return;
|
|
1025
1121
|
}
|
|
1026
1122
|
if (ok) {
|
|
1123
|
+
this.onListenerStarted();
|
|
1027
1124
|
this.ctx.logger.info(`[conn] 直播间 [${this.sub.roomId}] 重连成功`);
|
|
1028
1125
|
this.reconnectAttempts = 0;
|
|
1029
1126
|
return;
|
|
@@ -1031,9 +1128,15 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1031
1128
|
this.ctx.logger.warn(`[conn] 直播间 [${this.sub.roomId}] 重连未成功,继续退避`);
|
|
1032
1129
|
}
|
|
1033
1130
|
this.reconnectAttempts = 0;
|
|
1034
|
-
const msg = `直播间 [${this.sub.roomId}]
|
|
1131
|
+
const msg = `直播间 [${this.sub.roomId}] ${this.describeReconnectReason(reason, detail)}后连接持续失败,重试 ${RECONNECT_BACKOFF_MS.length} 次后放弃监听`;
|
|
1035
1132
|
this.ctx.logger.error(`[conn] ${msg}`);
|
|
1036
1133
|
this.ctx.emitEngineError(msg);
|
|
1134
|
+
this.cancel();
|
|
1135
|
+
}
|
|
1136
|
+
describeReconnectReason(reason, detail) {
|
|
1137
|
+
if (reason === "error") return "连接错误";
|
|
1138
|
+
if (reason === "close") return "连接关闭";
|
|
1139
|
+
return detail ? `连接静默(${detail})` : "连接静默";
|
|
1037
1140
|
}
|
|
1038
1141
|
/**
|
|
1039
1142
|
* L3:可被 {@link clearReconnectSleep} 取消的退避 sleep。dispose 时立即
|
|
@@ -1156,6 +1259,7 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1156
1259
|
if (!await this.useLiveRoomInfo(1) || !await this.useMasterInfo(1) || !this.liveRoomInfo || !this.masterInfo) {
|
|
1157
1260
|
this.setLiveStatus(false);
|
|
1158
1261
|
if (this.ctx.isDisposed()) return;
|
|
1262
|
+
this.onMonitoringStopped();
|
|
1159
1263
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播开播卡片失败", this.sub.roomId);
|
|
1160
1264
|
return;
|
|
1161
1265
|
}
|
|
@@ -1697,9 +1801,7 @@ var LiveEngine = class {
|
|
|
1697
1801
|
/** Tear down all listeners + per-room state, leaving the engine instance reusable. */
|
|
1698
1802
|
teardown() {
|
|
1699
1803
|
this.logger.info("[live] 关闭所有直播间监听");
|
|
1700
|
-
this.listener.
|
|
1701
|
-
this.listener.clearListeners();
|
|
1702
|
-
this.danmakuCollector.clearAll();
|
|
1804
|
+
this.listener.disposeAll();
|
|
1703
1805
|
}
|
|
1704
1806
|
/** Full rebootstrap. Used after auth-restored. */
|
|
1705
1807
|
rebuildFromSubs(subs) {
|
package/lib/index.d.cts
CHANGED
|
@@ -572,6 +572,8 @@ declare class RoomContextBase {
|
|
|
572
572
|
readonly listenerRecord: Record<string, MessageListener>;
|
|
573
573
|
readonly livePushTimerManager: Map<string, () => void>;
|
|
574
574
|
private disposed;
|
|
575
|
+
/** stopMonitoring 主动关闭 listener 时置位;RoomSession.onClose 消费后不做自愈重连。 */
|
|
576
|
+
private readonly intentionalCloseRooms;
|
|
575
577
|
/** Cached protobuf type for INTERACT_WORD_V2 decoding (lazy-loaded). */
|
|
576
578
|
protected interactWord?: protobuf.Type;
|
|
577
579
|
/**
|
|
@@ -590,6 +592,7 @@ declare class RoomContextBase {
|
|
|
590
592
|
* 同型 no-op 安全调用方。room-session 已做 per-UID 节流,这里只是分发。
|
|
591
593
|
*/
|
|
592
594
|
emitViewers(uid: string, viewers: string): void;
|
|
595
|
+
consumeIntentionalClose(roomId: string): boolean;
|
|
593
596
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
594
597
|
get imageRenderer(): ImageRenderer | null;
|
|
595
598
|
updateConfig(config: ListenerManagerConfig): void;
|
|
@@ -737,6 +740,10 @@ declare abstract class RoomSessionBase {
|
|
|
737
740
|
bootstrap(): Promise<void>;
|
|
738
741
|
/** Build the platform-specific {@link MsgHandler}; provided by the subclass. */
|
|
739
742
|
protected abstract buildHandler(): MsgHandler;
|
|
743
|
+
/** Hook for subclass-owned connection-health bookkeeping after listener bootstrap succeeds. */
|
|
744
|
+
protected onListenerStarted(): void;
|
|
745
|
+
/** Hook for subclass-owned cleanup before this session intentionally stops monitoring. */
|
|
746
|
+
protected onMonitoringStopped(): void;
|
|
740
747
|
protected useLiveRoomInfo(liveType: LiveType): Promise<boolean>;
|
|
741
748
|
protected useMasterInfo(liveType: LiveType): Promise<boolean>;
|
|
742
749
|
/**
|
|
@@ -766,6 +773,7 @@ declare abstract class RoomSessionBase {
|
|
|
766
773
|
}
|
|
767
774
|
//#endregion
|
|
768
775
|
//#region src/room-session.d.ts
|
|
776
|
+
type LiveWsActivityReason = "connected" | "open" | "start-listen" | "heartbeat" | "danmu" | "superchat" | "watched" | "liked" | "guard" | "live-start" | "live-end" | "interact" | "close";
|
|
769
777
|
declare class RoomSession extends RoomSessionBase {
|
|
770
778
|
private lastViewersEmitMs;
|
|
771
779
|
/**
|
|
@@ -784,18 +792,35 @@ declare class RoomSession extends RoomSessionBase {
|
|
|
784
792
|
/** L3:退避 sleep 的 Disposable + 唤醒句柄,cancel/teardown 时清掉,不留回调到 expiry。 */
|
|
785
793
|
private reconnectTimer?;
|
|
786
794
|
private reconnectWake?;
|
|
787
|
-
|
|
795
|
+
private lastLiveWsActivityAt;
|
|
796
|
+
private lastLiveWsActivityReason;
|
|
797
|
+
private watchdogTimer?;
|
|
798
|
+
private watchdogReconnectCount;
|
|
799
|
+
/** 外层主动停止 listener 时调用,阻止 onError/onClose/watchdog 触发重连。 */
|
|
788
800
|
cancel(): void;
|
|
789
801
|
/** L3:dispose 退避定时器并唤醒重连循环,使其立刻重校 cancelled/disposed 后退出。 */
|
|
790
802
|
private clearReconnectSleep;
|
|
803
|
+
protected onListenerStarted(): void;
|
|
804
|
+
protected onMonitoringStopped(): void;
|
|
805
|
+
getWsHealthSnapshot(): {
|
|
806
|
+
lastActivityAt: number;
|
|
807
|
+
lastActivityReason: LiveWsActivityReason;
|
|
808
|
+
watchdogReconnectCount: number;
|
|
809
|
+
};
|
|
810
|
+
private markLiveWsActivity;
|
|
811
|
+
private startLiveWsWatchdog;
|
|
812
|
+
private stopLiveWsWatchdog;
|
|
813
|
+
private checkLiveWsWatchdog;
|
|
791
814
|
protected buildHandler(): MsgHandler;
|
|
792
815
|
private onError;
|
|
816
|
+
private reconnect;
|
|
793
817
|
/**
|
|
794
|
-
* 退避重连循环(单飞,由
|
|
818
|
+
* 退避重连循环(单飞,由 reconnect 持有)。`while` 取代旧的 `setTimeout(0)`
|
|
795
819
|
* 递归续链 —— 杜绝深栈递归 + 每步都丢弃的定时器 Disposable;每次 sleep 后
|
|
796
820
|
* 重校 cancelled/disposed,sleep 自身可被 cancel/teardown dispose。
|
|
797
821
|
*/
|
|
798
822
|
private reconnectLoop;
|
|
823
|
+
private describeReconnectReason;
|
|
799
824
|
/**
|
|
800
825
|
* L3:可被 {@link clearReconnectSleep} 取消的退避 sleep。dispose 时立即
|
|
801
826
|
* resolve,让 reconnectLoop 醒来重校 cancelled/disposed 后退出 —— 不再留
|
package/lib/index.d.mts
CHANGED
|
@@ -572,6 +572,8 @@ declare class RoomContextBase {
|
|
|
572
572
|
readonly listenerRecord: Record<string, MessageListener>;
|
|
573
573
|
readonly livePushTimerManager: Map<string, () => void>;
|
|
574
574
|
private disposed;
|
|
575
|
+
/** stopMonitoring 主动关闭 listener 时置位;RoomSession.onClose 消费后不做自愈重连。 */
|
|
576
|
+
private readonly intentionalCloseRooms;
|
|
575
577
|
/** Cached protobuf type for INTERACT_WORD_V2 decoding (lazy-loaded). */
|
|
576
578
|
protected interactWord?: protobuf.Type;
|
|
577
579
|
/**
|
|
@@ -590,6 +592,7 @@ declare class RoomContextBase {
|
|
|
590
592
|
* 同型 no-op 安全调用方。room-session 已做 per-UID 节流,这里只是分发。
|
|
591
593
|
*/
|
|
592
594
|
emitViewers(uid: string, viewers: string): void;
|
|
595
|
+
consumeIntentionalClose(roomId: string): boolean;
|
|
593
596
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
594
597
|
get imageRenderer(): ImageRenderer | null;
|
|
595
598
|
updateConfig(config: ListenerManagerConfig): void;
|
|
@@ -737,6 +740,10 @@ declare abstract class RoomSessionBase {
|
|
|
737
740
|
bootstrap(): Promise<void>;
|
|
738
741
|
/** Build the platform-specific {@link MsgHandler}; provided by the subclass. */
|
|
739
742
|
protected abstract buildHandler(): MsgHandler;
|
|
743
|
+
/** Hook for subclass-owned connection-health bookkeeping after listener bootstrap succeeds. */
|
|
744
|
+
protected onListenerStarted(): void;
|
|
745
|
+
/** Hook for subclass-owned cleanup before this session intentionally stops monitoring. */
|
|
746
|
+
protected onMonitoringStopped(): void;
|
|
740
747
|
protected useLiveRoomInfo(liveType: LiveType): Promise<boolean>;
|
|
741
748
|
protected useMasterInfo(liveType: LiveType): Promise<boolean>;
|
|
742
749
|
/**
|
|
@@ -766,6 +773,7 @@ declare abstract class RoomSessionBase {
|
|
|
766
773
|
}
|
|
767
774
|
//#endregion
|
|
768
775
|
//#region src/room-session.d.ts
|
|
776
|
+
type LiveWsActivityReason = "connected" | "open" | "start-listen" | "heartbeat" | "danmu" | "superchat" | "watched" | "liked" | "guard" | "live-start" | "live-end" | "interact" | "close";
|
|
769
777
|
declare class RoomSession extends RoomSessionBase {
|
|
770
778
|
private lastViewersEmitMs;
|
|
771
779
|
/**
|
|
@@ -784,18 +792,35 @@ declare class RoomSession extends RoomSessionBase {
|
|
|
784
792
|
/** L3:退避 sleep 的 Disposable + 唤醒句柄,cancel/teardown 时清掉,不留回调到 expiry。 */
|
|
785
793
|
private reconnectTimer?;
|
|
786
794
|
private reconnectWake?;
|
|
787
|
-
|
|
795
|
+
private lastLiveWsActivityAt;
|
|
796
|
+
private lastLiveWsActivityReason;
|
|
797
|
+
private watchdogTimer?;
|
|
798
|
+
private watchdogReconnectCount;
|
|
799
|
+
/** 外层主动停止 listener 时调用,阻止 onError/onClose/watchdog 触发重连。 */
|
|
788
800
|
cancel(): void;
|
|
789
801
|
/** L3:dispose 退避定时器并唤醒重连循环,使其立刻重校 cancelled/disposed 后退出。 */
|
|
790
802
|
private clearReconnectSleep;
|
|
803
|
+
protected onListenerStarted(): void;
|
|
804
|
+
protected onMonitoringStopped(): void;
|
|
805
|
+
getWsHealthSnapshot(): {
|
|
806
|
+
lastActivityAt: number;
|
|
807
|
+
lastActivityReason: LiveWsActivityReason;
|
|
808
|
+
watchdogReconnectCount: number;
|
|
809
|
+
};
|
|
810
|
+
private markLiveWsActivity;
|
|
811
|
+
private startLiveWsWatchdog;
|
|
812
|
+
private stopLiveWsWatchdog;
|
|
813
|
+
private checkLiveWsWatchdog;
|
|
791
814
|
protected buildHandler(): MsgHandler;
|
|
792
815
|
private onError;
|
|
816
|
+
private reconnect;
|
|
793
817
|
/**
|
|
794
|
-
* 退避重连循环(单飞,由
|
|
818
|
+
* 退避重连循环(单飞,由 reconnect 持有)。`while` 取代旧的 `setTimeout(0)`
|
|
795
819
|
* 递归续链 —— 杜绝深栈递归 + 每步都丢弃的定时器 Disposable;每次 sleep 后
|
|
796
820
|
* 重校 cancelled/disposed,sleep 自身可被 cancel/teardown dispose。
|
|
797
821
|
*/
|
|
798
822
|
private reconnectLoop;
|
|
823
|
+
private describeReconnectReason;
|
|
799
824
|
/**
|
|
800
825
|
* L3:可被 {@link clearReconnectSleep} 取消的退避 sleep。dispose 时立即
|
|
801
826
|
* resolve,让 reconnectLoop 醒来重校 cancelled/disposed 后退出 —— 不再留
|
package/lib/index.mjs
CHANGED
|
@@ -161,6 +161,8 @@ var RoomContextBase = class {
|
|
|
161
161
|
listenerRecord = {};
|
|
162
162
|
livePushTimerManager = /* @__PURE__ */ new Map();
|
|
163
163
|
disposed = false;
|
|
164
|
+
/** stopMonitoring 主动关闭 listener 时置位;RoomSession.onClose 消费后不做自愈重连。 */
|
|
165
|
+
intentionalCloseRooms = /* @__PURE__ */ new Set();
|
|
164
166
|
/** Cached protobuf type for INTERACT_WORD_V2 decoding (lazy-loaded). */
|
|
165
167
|
interactWord;
|
|
166
168
|
/**
|
|
@@ -198,6 +200,11 @@ var RoomContextBase = class {
|
|
|
198
200
|
emitViewers(uid, viewers) {
|
|
199
201
|
this._emitViewers?.(uid, viewers);
|
|
200
202
|
}
|
|
203
|
+
consumeIntentionalClose(roomId) {
|
|
204
|
+
const hit = this.intentionalCloseRooms.has(roomId);
|
|
205
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
206
|
+
return hit;
|
|
207
|
+
}
|
|
201
208
|
/** 受 `config.imageEnabled` 门控的渲染器视图;关闭时返回 null。 */
|
|
202
209
|
get imageRenderer() {
|
|
203
210
|
return this.config.imageEnabled === false ? null : this._getImageRenderer();
|
|
@@ -229,10 +236,12 @@ var RoomContextBase = class {
|
|
|
229
236
|
closeListener(roomId) {
|
|
230
237
|
const listener = this.listenerRecord[roomId];
|
|
231
238
|
if (!listener) {
|
|
239
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
232
240
|
this.logger.debug(`[conn] 直播间 [${roomId}] 连接不存在,跳过关闭`);
|
|
233
241
|
return;
|
|
234
242
|
}
|
|
235
243
|
if (listener.closed) {
|
|
244
|
+
this.intentionalCloseRooms.delete(roomId);
|
|
236
245
|
this.logger.debug(`[conn] 直播间 [${roomId}] 连接已被远端断开`);
|
|
237
246
|
delete this.listenerRecord[roomId];
|
|
238
247
|
return;
|
|
@@ -256,6 +265,7 @@ var RoomContextBase = class {
|
|
|
256
265
|
stopMonitoring(reason, roomId) {
|
|
257
266
|
if (roomId) {
|
|
258
267
|
this.logger.error(`[conn] [${roomId}] ${reason},已停止该房间的监测`);
|
|
268
|
+
this.intentionalCloseRooms.add(roomId);
|
|
259
269
|
this.closeListener(roomId);
|
|
260
270
|
const timer = this.livePushTimerManager.get(roomId);
|
|
261
271
|
if (timer) {
|
|
@@ -313,6 +323,7 @@ var RoomContext = class extends RoomContextBase {
|
|
|
313
323
|
this.logger.warn(`[conn] 直播间 [${roomId}] 连接已存在,跳过创建`);
|
|
314
324
|
return true;
|
|
315
325
|
}
|
|
326
|
+
this.consumeIntentionalClose(roomId);
|
|
316
327
|
const cookiesStr = this.api.getCookiesHeader();
|
|
317
328
|
let mySelfInfo;
|
|
318
329
|
try {
|
|
@@ -687,14 +698,17 @@ var RoomSessionBase = class {
|
|
|
687
698
|
async bootstrap() {
|
|
688
699
|
if (!await this.ctx.startLiveRoomListener(this.sub.roomId, this.buildHandler())) {
|
|
689
700
|
await this.ctx.push.sendPrivateMsg(`直播间 [${this.sub.roomId}] 弹幕连接建立失败,已停止该房间监测`);
|
|
701
|
+
this.onMonitoringStopped();
|
|
690
702
|
this.ctx.closeListener(this.sub.roomId);
|
|
691
703
|
return;
|
|
692
704
|
}
|
|
693
705
|
if (!await this.useLiveRoomInfo(4) || !await this.useMasterInfo(4) || !this.liveRoomInfo || !this.masterInfo) {
|
|
694
706
|
await this.ctx.push.sendPrivateMsg("获取直播间信息失败,启动直播间弹幕检测失败");
|
|
707
|
+
this.onMonitoringStopped();
|
|
695
708
|
this.ctx.closeListener(this.sub.roomId);
|
|
696
709
|
return;
|
|
697
710
|
}
|
|
711
|
+
this.onListenerStarted();
|
|
698
712
|
this.ctx.logger.debug(`[stat] 当前粉丝数:${this.masterInfo.liveOpenFollowerNum}`);
|
|
699
713
|
if (this.liveRoomInfo.live_status === 1) {
|
|
700
714
|
this.liveTime = this.liveRoomInfo.live_time;
|
|
@@ -723,6 +737,10 @@ var RoomSessionBase = class {
|
|
|
723
737
|
this.armPeriodicTimer();
|
|
724
738
|
}
|
|
725
739
|
}
|
|
740
|
+
/** Hook for subclass-owned connection-health bookkeeping after listener bootstrap succeeds. */
|
|
741
|
+
onListenerStarted() {}
|
|
742
|
+
/** Hook for subclass-owned cleanup before this session intentionally stops monitoring. */
|
|
743
|
+
onMonitoringStopped() {}
|
|
726
744
|
async useLiveRoomInfo(liveType) {
|
|
727
745
|
const data = await this.ctx.getLiveRoomInfo(this.sub.roomId);
|
|
728
746
|
if (!data?.uid) return false;
|
|
@@ -772,6 +790,7 @@ var RoomSessionBase = class {
|
|
|
772
790
|
/** Periodic "正在直播" tick (callback for `setInterval`). */
|
|
773
791
|
async tickPushAtTime() {
|
|
774
792
|
if (!await this.useLiveRoomInfo(2) || !this.liveRoomInfo) {
|
|
793
|
+
this.onMonitoringStopped();
|
|
775
794
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播卡片失败", this.sub.roomId);
|
|
776
795
|
return;
|
|
777
796
|
}
|
|
@@ -822,6 +841,7 @@ var RoomSessionBase = class {
|
|
|
822
841
|
this.setLiveStatus(false);
|
|
823
842
|
this.ctx.danmakuCollector.clear(this.sub.roomId);
|
|
824
843
|
if (this.ctx.isDisposed()) return;
|
|
844
|
+
this.onMonitoringStopped();
|
|
825
845
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播下播卡片失败", this.sub.roomId);
|
|
826
846
|
return;
|
|
827
847
|
}
|
|
@@ -906,6 +926,8 @@ const RECONNECT_BACKOFF_MS = [
|
|
|
906
926
|
8e3,
|
|
907
927
|
16e3
|
|
908
928
|
];
|
|
929
|
+
/** B 站 live WS 静默自愈:每分钟检查一次,3 分钟无 heartbeat/消息即主动重连。 */
|
|
930
|
+
const LIVE_WS_WATCHDOG_INTERVAL_MS = 6e4;
|
|
909
931
|
var RoomSession = class extends RoomSessionBase {
|
|
910
932
|
lastViewersEmitMs = 0;
|
|
911
933
|
/**
|
|
@@ -924,10 +946,15 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
924
946
|
/** L3:退避 sleep 的 Disposable + 唤醒句柄,cancel/teardown 时清掉,不留回调到 expiry。 */
|
|
925
947
|
reconnectTimer;
|
|
926
948
|
reconnectWake;
|
|
927
|
-
|
|
949
|
+
lastLiveWsActivityAt = 0;
|
|
950
|
+
lastLiveWsActivityReason = "connected";
|
|
951
|
+
watchdogTimer;
|
|
952
|
+
watchdogReconnectCount = 0;
|
|
953
|
+
/** 外层主动停止 listener 时调用,阻止 onError/onClose/watchdog 触发重连。 */
|
|
928
954
|
cancel() {
|
|
929
955
|
this.cancelled = true;
|
|
930
956
|
this.reconnecting = false;
|
|
957
|
+
this.stopLiveWsWatchdog();
|
|
931
958
|
this.clearReconnectSleep();
|
|
932
959
|
}
|
|
933
960
|
/** L3:dispose 退避定时器并唤醒重连循环,使其立刻重校 cancelled/disposed 后退出。 */
|
|
@@ -937,12 +964,62 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
937
964
|
this.reconnectWake?.();
|
|
938
965
|
this.reconnectWake = void 0;
|
|
939
966
|
}
|
|
967
|
+
onListenerStarted() {
|
|
968
|
+
this.markLiveWsActivity("connected");
|
|
969
|
+
this.startLiveWsWatchdog();
|
|
970
|
+
}
|
|
971
|
+
onMonitoringStopped() {
|
|
972
|
+
this.cancel();
|
|
973
|
+
}
|
|
974
|
+
getWsHealthSnapshot() {
|
|
975
|
+
return {
|
|
976
|
+
lastActivityAt: this.lastLiveWsActivityAt,
|
|
977
|
+
lastActivityReason: this.lastLiveWsActivityReason,
|
|
978
|
+
watchdogReconnectCount: this.watchdogReconnectCount
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
markLiveWsActivity(reason) {
|
|
982
|
+
this.lastLiveWsActivityAt = Date.now();
|
|
983
|
+
this.lastLiveWsActivityReason = reason;
|
|
984
|
+
}
|
|
985
|
+
startLiveWsWatchdog() {
|
|
986
|
+
if (this.watchdogTimer || this.cancelled || this.ctx.isDisposed()) return;
|
|
987
|
+
this.watchdogTimer = this.ctx.serviceCtx.setInterval(() => this.checkLiveWsWatchdog(), LIVE_WS_WATCHDOG_INTERVAL_MS);
|
|
988
|
+
}
|
|
989
|
+
stopLiveWsWatchdog() {
|
|
990
|
+
this.watchdogTimer?.dispose();
|
|
991
|
+
this.watchdogTimer = void 0;
|
|
992
|
+
}
|
|
993
|
+
checkLiveWsWatchdog() {
|
|
994
|
+
if (this.cancelled || this.ctx.isDisposed() || this.reconnecting) return;
|
|
995
|
+
if (this.lastLiveWsActivityAt <= 0) return;
|
|
996
|
+
const staleMs = Date.now() - this.lastLiveWsActivityAt;
|
|
997
|
+
if (staleMs < 18e4) return;
|
|
998
|
+
this.watchdogReconnectCount++;
|
|
999
|
+
this.reconnect("watchdog", `${Math.floor(staleMs / 1e3)}s 无 heartbeat/消息(last=${this.lastLiveWsActivityReason},watchdog=${this.watchdogReconnectCount})`);
|
|
1000
|
+
}
|
|
940
1001
|
buildHandler() {
|
|
941
1002
|
const base = {
|
|
1003
|
+
onOpen: () => this.markLiveWsActivity("open"),
|
|
1004
|
+
onStartListen: () => this.markLiveWsActivity("start-listen"),
|
|
1005
|
+
onClose: () => {
|
|
1006
|
+
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
1007
|
+
if (this.ctx.consumeIntentionalClose(this.sub.roomId)) return;
|
|
1008
|
+
this.markLiveWsActivity("close");
|
|
1009
|
+
this.reconnect("close");
|
|
1010
|
+
},
|
|
942
1011
|
onError: () => this.onError(),
|
|
943
|
-
|
|
944
|
-
|
|
1012
|
+
onAttentionChange: () => this.markLiveWsActivity("heartbeat"),
|
|
1013
|
+
onIncomeDanmu: ({ body }) => {
|
|
1014
|
+
this.markLiveWsActivity("danmu");
|
|
1015
|
+
this.onIncomeDanmu(body);
|
|
1016
|
+
},
|
|
1017
|
+
onIncomeSuperChat: ({ body }) => {
|
|
1018
|
+
this.markLiveWsActivity("superchat");
|
|
1019
|
+
return this.onIncomeSuperChat(body);
|
|
1020
|
+
},
|
|
945
1021
|
onWatchedChange: ({ body }) => {
|
|
1022
|
+
this.markLiveWsActivity("watched");
|
|
946
1023
|
this.liveData.watchedNum = body.text_small;
|
|
947
1024
|
const now = Date.now();
|
|
948
1025
|
if (now - this.lastViewersEmitMs >= VIEWERS_EMIT_THROTTLE_MS) {
|
|
@@ -951,42 +1028,61 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
951
1028
|
}
|
|
952
1029
|
},
|
|
953
1030
|
onLikedChange: ({ body }) => {
|
|
1031
|
+
this.markLiveWsActivity("liked");
|
|
954
1032
|
this.liveData.likedNum = body.count;
|
|
955
1033
|
},
|
|
956
|
-
onGuardBuy: ({ body }) =>
|
|
957
|
-
|
|
958
|
-
|
|
1034
|
+
onGuardBuy: ({ body }) => {
|
|
1035
|
+
this.markLiveWsActivity("guard");
|
|
1036
|
+
return this.onGuardBuy(body);
|
|
1037
|
+
},
|
|
1038
|
+
onLiveStart: () => {
|
|
1039
|
+
this.markLiveWsActivity("live-start");
|
|
1040
|
+
return this.onLiveStart();
|
|
1041
|
+
},
|
|
1042
|
+
onLiveEnd: () => {
|
|
1043
|
+
this.markLiveWsActivity("live-end");
|
|
1044
|
+
return this.onLiveEnd();
|
|
1045
|
+
}
|
|
959
1046
|
};
|
|
960
1047
|
if (!this.sub.customSpecialUsersEnterTheRoom.enable) return base;
|
|
961
1048
|
return {
|
|
962
1049
|
...base,
|
|
963
|
-
raw: { INTERACT_WORD_V2: (msg) =>
|
|
1050
|
+
raw: { INTERACT_WORD_V2: (msg) => {
|
|
1051
|
+
this.markLiveWsActivity("interact");
|
|
1052
|
+
return this.onInteractWordV2(msg);
|
|
1053
|
+
} }
|
|
964
1054
|
};
|
|
965
1055
|
}
|
|
966
|
-
|
|
1056
|
+
onError() {
|
|
1057
|
+
return this.reconnect("error");
|
|
1058
|
+
}
|
|
1059
|
+
async reconnect(reason, detail) {
|
|
967
1060
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
968
1061
|
if (this.reconnecting) return;
|
|
969
1062
|
this.reconnecting = true;
|
|
970
1063
|
try {
|
|
971
|
-
await this.reconnectLoop();
|
|
1064
|
+
await this.reconnectLoop(reason, detail);
|
|
972
1065
|
} finally {
|
|
973
1066
|
this.reconnecting = false;
|
|
974
1067
|
}
|
|
975
1068
|
}
|
|
976
1069
|
/**
|
|
977
|
-
* 退避重连循环(单飞,由
|
|
1070
|
+
* 退避重连循环(单飞,由 reconnect 持有)。`while` 取代旧的 `setTimeout(0)`
|
|
978
1071
|
* 递归续链 —— 杜绝深栈递归 + 每步都丢弃的定时器 Disposable;每次 sleep 后
|
|
979
1072
|
* 重校 cancelled/disposed,sleep 自身可被 cancel/teardown dispose。
|
|
980
1073
|
*/
|
|
981
|
-
async reconnectLoop() {
|
|
1074
|
+
async reconnectLoop(reason, detail) {
|
|
982
1075
|
while (this.reconnectAttempts < RECONNECT_BACKOFF_MS.length) {
|
|
983
1076
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
984
|
-
|
|
985
|
-
|
|
1077
|
+
if (reason === "error") {
|
|
1078
|
+
this.setLiveStatus(false);
|
|
1079
|
+
this.cancelPeriodicTimer();
|
|
1080
|
+
}
|
|
986
1081
|
this.ctx.closeListener(this.sub.roomId);
|
|
987
1082
|
const delay = RECONNECT_BACKOFF_MS[this.reconnectAttempts];
|
|
988
1083
|
this.reconnectAttempts++;
|
|
989
|
-
|
|
1084
|
+
const reasonText = this.describeReconnectReason(reason, detail);
|
|
1085
|
+
this.ctx.logger.warn(`[conn] 直播间 [${this.sub.roomId}] ${reasonText},${delay / 1e3}s 后重连(第 ${this.reconnectAttempts}/${RECONNECT_BACKOFF_MS.length} 次)`);
|
|
990
1086
|
await this.sleepReconnect(delay);
|
|
991
1087
|
if (this.cancelled || this.ctx.isDisposed()) return;
|
|
992
1088
|
let ok = false;
|
|
@@ -1000,6 +1096,7 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1000
1096
|
return;
|
|
1001
1097
|
}
|
|
1002
1098
|
if (ok) {
|
|
1099
|
+
this.onListenerStarted();
|
|
1003
1100
|
this.ctx.logger.info(`[conn] 直播间 [${this.sub.roomId}] 重连成功`);
|
|
1004
1101
|
this.reconnectAttempts = 0;
|
|
1005
1102
|
return;
|
|
@@ -1007,9 +1104,15 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1007
1104
|
this.ctx.logger.warn(`[conn] 直播间 [${this.sub.roomId}] 重连未成功,继续退避`);
|
|
1008
1105
|
}
|
|
1009
1106
|
this.reconnectAttempts = 0;
|
|
1010
|
-
const msg = `直播间 [${this.sub.roomId}]
|
|
1107
|
+
const msg = `直播间 [${this.sub.roomId}] ${this.describeReconnectReason(reason, detail)}后连接持续失败,重试 ${RECONNECT_BACKOFF_MS.length} 次后放弃监听`;
|
|
1011
1108
|
this.ctx.logger.error(`[conn] ${msg}`);
|
|
1012
1109
|
this.ctx.emitEngineError(msg);
|
|
1110
|
+
this.cancel();
|
|
1111
|
+
}
|
|
1112
|
+
describeReconnectReason(reason, detail) {
|
|
1113
|
+
if (reason === "error") return "连接错误";
|
|
1114
|
+
if (reason === "close") return "连接关闭";
|
|
1115
|
+
return detail ? `连接静默(${detail})` : "连接静默";
|
|
1013
1116
|
}
|
|
1014
1117
|
/**
|
|
1015
1118
|
* L3:可被 {@link clearReconnectSleep} 取消的退避 sleep。dispose 时立即
|
|
@@ -1132,6 +1235,7 @@ var RoomSession = class extends RoomSessionBase {
|
|
|
1132
1235
|
if (!await this.useLiveRoomInfo(1) || !await this.useMasterInfo(1) || !this.liveRoomInfo || !this.masterInfo) {
|
|
1133
1236
|
this.setLiveStatus(false);
|
|
1134
1237
|
if (this.ctx.isDisposed()) return;
|
|
1238
|
+
this.onMonitoringStopped();
|
|
1135
1239
|
this.ctx.stopMonitoring("获取直播间信息失败,推送直播开播卡片失败", this.sub.roomId);
|
|
1136
1240
|
return;
|
|
1137
1241
|
}
|
|
@@ -1673,9 +1777,7 @@ var LiveEngine = class {
|
|
|
1673
1777
|
/** Tear down all listeners + per-room state, leaving the engine instance reusable. */
|
|
1674
1778
|
teardown() {
|
|
1675
1779
|
this.logger.info("[live] 关闭所有直播间监听");
|
|
1676
|
-
this.listener.
|
|
1677
|
-
this.listener.clearListeners();
|
|
1678
|
-
this.danmakuCollector.clearAll();
|
|
1780
|
+
this.listener.disposeAll();
|
|
1679
1781
|
}
|
|
1680
1782
|
/** Full rebootstrap. Used after auth-restored. */
|
|
1681
1783
|
rebuildFromSubs(subs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bilibili-notify/live",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/Akokk0/bilibili-notify"
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"protobufjs": "^7.4.0",
|
|
33
33
|
"@bilibili-notify/ai": "^0.0.1-alpha.1",
|
|
34
34
|
"@bilibili-notify/image": "^0.0.1-alpha.2",
|
|
35
|
-
"@bilibili-notify/
|
|
36
|
-
"@bilibili-notify/
|
|
35
|
+
"@bilibili-notify/internal": "^0.1.0-alpha.4",
|
|
36
|
+
"@bilibili-notify/api": "^0.2.0-alpha.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/luxon": "^3.4.2",
|