修复自己刚上麦成功后,会立即触发一次 /live/mic/list 刷新。如果这次拉到的是旧麦位快照,本地 roomWheatMap 会把“自己在麦上”的状态清掉,isOnMai() 变成 false,底部栏麦克风就被隐藏了。之前只保护了 MIC_CHANGE 和换麦位场景,没有保护“自己初次上麦”的旧快照覆盖

This commit is contained in:
zhx 2026-05-26 16:36:07 +08:00
parent 10d5e821f7
commit 15eb93c810
2 changed files with 148 additions and 45 deletions

View File

@ -428,6 +428,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
final Map<num, _RecentMicChangeSeatSnapshot> _recentMicChangeSeatSnapshots =
<num, _RecentMicChangeSeatSnapshot>{};
num? _preferredSelfMicIndex;
num? _pendingSelfMicGoUpIndex;
int? _pendingSelfMicGoUpGuardUntilMs;
num? _pendingSelfMicSourceIndex;
int? _pendingSelfMicSwitchGuardUntilMs;
num? _pendingSelfMicReleaseIndex;
@ -1218,6 +1220,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
required num sourceIndex,
required num targetIndex,
}) {
_clearSelfMicGoUpGuard();
_clearSelfMicReleaseGuard();
_preferredSelfMicIndex = targetIndex;
_pendingSelfMicSourceIndex = sourceIndex;
@ -1225,6 +1228,22 @@ class RealTimeCommunicationManager extends ChangeNotifier {
DateTime.now().add(_selfMicStateGracePeriod).millisecondsSinceEpoch;
}
void _startSelfMicGoUpGuard({required num targetIndex}) {
_clearSelfMicReleaseGuard();
_preferredSelfMicIndex = targetIndex;
_pendingSelfMicGoUpIndex = targetIndex;
_pendingSelfMicGoUpGuardUntilMs =
DateTime.now().add(_selfMicStateGracePeriod).millisecondsSinceEpoch;
}
void _clearSelfMicGoUpGuard({bool clearPreferredIndex = false}) {
_pendingSelfMicGoUpIndex = null;
_pendingSelfMicGoUpGuardUntilMs = null;
if (clearPreferredIndex) {
_preferredSelfMicIndex = null;
}
}
void _clearSelfMicSwitchGuard({bool clearPreferredIndex = false}) {
_pendingSelfMicSourceIndex = null;
_pendingSelfMicSwitchGuardUntilMs = null;
@ -1233,6 +1252,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
}
}
@visibleForTesting
void debugStartSelfMicGoUpGuard(num targetIndex) {
_startSelfMicGoUpGuard(targetIndex: targetIndex);
}
void _startSelfMicReleaseGuard({required num sourceIndex}) {
_pendingSelfMicReleaseIndex = sourceIndex;
_pendingSelfMicReleaseGuardUntilMs =
@ -1305,9 +1329,31 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (targetIndex != null &&
nextMap[targetIndex]?.user?.id == currentUserId) {
_clearSelfMicSwitchGuard();
_clearSelfMicGoUpGuard();
return nextMap;
}
final goUpIndex = _pendingSelfMicGoUpIndex;
final goUpGuardUntilMs = _pendingSelfMicGoUpGuardUntilMs ?? 0;
final hasActiveGoUpGuard = goUpIndex != null && goUpGuardUntilMs > nowMs;
if (!hasActiveGoUpGuard) {
_clearSelfMicGoUpGuard();
} else if (selfSeatIndices.isEmpty ||
selfSeatIndices.every((seatIndex) => seatIndex == goUpIndex)) {
final stabilizedMap = _stabilizeOptimisticSelfMicSeat(
nextMap: nextMap,
previousMap: previousMap,
selfSeatIndices: selfSeatIndices,
targetIndex: goUpIndex,
currentUserId: currentUserId,
);
if (stabilizedMap != null) {
return stabilizedMap;
}
} else {
_clearSelfMicGoUpGuard();
}
if (!hasActiveGuard) {
_clearSelfMicSwitchGuard();
} else {
@ -1320,50 +1366,15 @@ class RealTimeCommunicationManager extends ChangeNotifier {
(seatIndex) => seatIndex == resolvedSourceIndex,
);
if (selfOnlyOnSource) {
final optimisticTargetSeat = previousMap[resolvedTargetIndex];
if (optimisticTargetSeat != null &&
optimisticTargetSeat.user?.id == currentUserId) {
final incomingTargetSeat = nextMap[resolvedTargetIndex];
if (incomingTargetSeat?.user == null ||
incomingTargetSeat?.user?.id == currentUserId) {
final stabilizedMap = Map<num, MicRes>.from(nextMap);
for (final seatIndex in selfSeatIndices) {
if (seatIndex == resolvedTargetIndex) {
continue;
}
final seat = stabilizedMap[seatIndex];
if (seat == null) {
continue;
}
stabilizedMap[seatIndex] = seat.copyWith(clearUser: true);
}
final baseSeat = incomingTargetSeat ?? optimisticTargetSeat;
stabilizedMap[resolvedTargetIndex] = baseSeat.copyWith(
user: optimisticTargetSeat.user,
micMute:
incomingTargetSeat?.micMute ?? optimisticTargetSeat.micMute,
micLock:
incomingTargetSeat?.micLock ?? optimisticTargetSeat.micLock,
roomToken:
incomingTargetSeat?.roomToken ??
optimisticTargetSeat.roomToken,
emojiPath:
(incomingTargetSeat?.emojiPath ?? "").isNotEmpty
? incomingTargetSeat?.emojiPath
: optimisticTargetSeat.emojiPath,
type:
(incomingTargetSeat?.type ?? "").isNotEmpty
? incomingTargetSeat?.type
: optimisticTargetSeat.type,
number:
(incomingTargetSeat?.number ?? "").isNotEmpty
? incomingTargetSeat?.number
: optimisticTargetSeat.number,
);
return stabilizedMap;
}
final stabilizedMap = _stabilizeOptimisticSelfMicSeat(
nextMap: nextMap,
previousMap: previousMap,
selfSeatIndices: selfSeatIndices,
targetIndex: resolvedTargetIndex,
currentUserId: currentUserId,
);
if (stabilizedMap != null) {
return stabilizedMap;
}
}
}
@ -1397,6 +1408,60 @@ class RealTimeCommunicationManager extends ChangeNotifier {
return stabilizedMap;
}
Map<num, MicRes>? _stabilizeOptimisticSelfMicSeat({
required Map<num, MicRes> nextMap,
required Map<num, MicRes> previousMap,
required Iterable<num> selfSeatIndices,
required num targetIndex,
required String currentUserId,
}) {
final optimisticTargetSeat = previousMap[targetIndex];
if (optimisticTargetSeat == null ||
optimisticTargetSeat.user?.id != currentUserId) {
return null;
}
final incomingTargetSeat = nextMap[targetIndex];
if (incomingTargetSeat?.user != null &&
incomingTargetSeat?.user?.id != currentUserId) {
return null;
}
final stabilizedMap = Map<num, MicRes>.from(nextMap);
for (final seatIndex in selfSeatIndices) {
if (seatIndex == targetIndex) {
continue;
}
final seat = stabilizedMap[seatIndex];
if (seat == null) {
continue;
}
stabilizedMap[seatIndex] = seat.copyWith(clearUser: true);
}
final baseSeat = incomingTargetSeat ?? optimisticTargetSeat;
stabilizedMap[targetIndex] = baseSeat.copyWith(
user: optimisticTargetSeat.user,
micMute: incomingTargetSeat?.micMute ?? optimisticTargetSeat.micMute,
micLock: incomingTargetSeat?.micLock ?? optimisticTargetSeat.micLock,
roomToken:
incomingTargetSeat?.roomToken ?? optimisticTargetSeat.roomToken,
emojiPath:
(incomingTargetSeat?.emojiPath ?? "").isNotEmpty
? incomingTargetSeat?.emojiPath
: optimisticTargetSeat.emojiPath,
type:
(incomingTargetSeat?.type ?? "").isNotEmpty
? incomingTargetSeat?.type
: optimisticTargetSeat.type,
number:
(incomingTargetSeat?.number ?? "").isNotEmpty
? incomingTargetSeat?.number
: optimisticTargetSeat.number,
);
return stabilizedMap;
}
bool _shouldPreferDuplicateMicSeat({
required String userId,
required num existingIndex,
@ -1477,6 +1542,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
if (currentUserMic == null) {
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
_clearSelfMicGoUpGuard(clearPreferredIndex: true);
_roomRtcRoleIsBroadcaster = false;
_syncRoomHeartbeatState(
roomId: roomId,
@ -2443,6 +2509,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
final currentSeatIndex = userOnMaiInIndex(currentUserId);
isMic = true;
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
_clearSelfMicGoUpGuard(clearPreferredIndex: true);
if (currentSeatIndex > -1) {
_startSelfMicReleaseGuard(sourceIndex: currentSeatIndex);
} else {
@ -4966,6 +5033,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
needUpDataUserInfo = false;
SCRoomUtils.roomUsersMap.clear();
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
_clearSelfMicGoUpGuard(clearPreferredIndex: true);
_clearSelfMicReleaseGuard();
_seatResizeOccupantPreserveUntilMs = null;
roomIsMute = false;
@ -5215,6 +5283,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
);
} catch (downError) {}
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
_clearSelfMicGoUpGuard(clearPreferredIndex: true);
_clearUserFromSeats(myUser?.id);
_syncSelfMicRuntimeState();
notifyListeners();
@ -5237,8 +5306,8 @@ class RealTimeCommunicationManager extends ChangeNotifier {
targetIndex: targetIndex,
);
} else {
_preferredSelfMicIndex = targetIndex;
_clearSelfMicSwitchGuard();
_startSelfMicGoUpGuard(targetIndex: targetIndex);
}
_clearUserFromSeats(myUser.id, exceptIndex: targetIndex);
final currentSeat = roomWheatMap[targetIndex];
@ -5302,6 +5371,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
isMic = true;
}
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
_clearSelfMicGoUpGuard(clearPreferredIndex: true);
if (currentUserSeatIndex > -1) {
_startSelfMicReleaseGuard(sourceIndex: currentUserSeatIndex);
} else {

View File

@ -0,0 +1,33 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:yumi/services/audio/rtc_manager.dart';
import 'package:yumi/shared/business_logic/models/res/login_res.dart';
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
void main() {
test('self go-up keeps optimistic mic seat over stale mic list data', () {
SharedPreferences.setMockInitialValues({});
AccountStorage().setCurrentUser(
SocialChatLoginRes(
userProfile: SocialChatUserProfile(id: 'user-1', userNickname: 'Me'),
),
);
final rtcProvider =
RealTimeCommunicationManager()
..roomWheatMap = {
3: MicRes(
micIndex: 3,
user: SocialChatUserProfile(id: 'user-1', userNickname: 'Me'),
),
};
rtcProvider.debugStartSelfMicGoUpGuard(3);
rtcProvider.debugApplyMicListSnapshot([
MicRes(micIndex: 3),
], notifyIfUnchanged: false);
expect(rtcProvider.roomWheatMap[3]?.user?.id, 'user-1');
expect(rtcProvider.isOnMai(), isTrue);
});
}