麦位bug
This commit is contained in:
parent
78c3a95160
commit
1b4d0c40c6
@ -502,7 +502,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -511,7 +511,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.2;
|
||||
MARKETING_VERSION = 1.2.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -693,7 +693,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -702,7 +702,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.2;
|
||||
MARKETING_VERSION = 1.2.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -722,7 +722,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -731,7 +731,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.2.2;
|
||||
MARKETING_VERSION = 1.2.5;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.org.yumiparty;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const double roomGameDesignWidth = 750;
|
||||
@ -64,79 +62,115 @@ String resolveRoomGameScreenMode(
|
||||
return fullScreen ? 'full' : 'half';
|
||||
}
|
||||
|
||||
String buildRoomGameNativeFitScript({
|
||||
required double visibleHeight,
|
||||
required double expectedHeight,
|
||||
}) {
|
||||
final options = jsonEncode(<String, dynamic>{
|
||||
'visibleHeight': _roundMetric(visibleHeight),
|
||||
'expectedHeight': _roundMetric(expectedHeight),
|
||||
});
|
||||
return '''
|
||||
(function() {
|
||||
const options = $options;
|
||||
const fitKey = '__roomGameNativeViewportFit';
|
||||
|
||||
function applyFit() {
|
||||
try {
|
||||
const visibleHeight = Number(options.visibleHeight) || window.innerHeight || 0;
|
||||
const expectedHeight = Number(options.expectedHeight) || 0;
|
||||
if (!document.body || !visibleHeight || !expectedHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rawScale = visibleHeight / expectedHeight;
|
||||
const scale = Math.min(1, rawScale);
|
||||
window[fitKey] = {
|
||||
scale: scale,
|
||||
visibleHeight: visibleHeight,
|
||||
expectedHeight: expectedHeight,
|
||||
compact: scale < 0.999
|
||||
};
|
||||
|
||||
if (scale >= 0.999) {
|
||||
document.documentElement.style.overflow = '';
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.transform = '';
|
||||
document.body.style.transformOrigin = '';
|
||||
document.body.style.width = '';
|
||||
document.body.style.minHeight = '';
|
||||
document.documentElement.removeAttribute('data-room-game-native-fit');
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-room-game-native-fit', 'true');
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.transform = 'scale(' + scale + ')';
|
||||
document.body.style.transformOrigin = 'top left';
|
||||
document.body.style.width = (100 / scale) + '%';
|
||||
document.body.style.minHeight = expectedHeight + 'px';
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', applyFit, { once: true });
|
||||
} else {
|
||||
applyFit();
|
||||
}
|
||||
[50, 250, 800, 1600, 3000].forEach(function(delay) {
|
||||
window.setTimeout(applyFit, delay);
|
||||
});
|
||||
if (!window.__roomGameNativeFitResizeReady) {
|
||||
window.__roomGameNativeFitResizeReady = true;
|
||||
window.addEventListener('resize', applyFit, true);
|
||||
window.addEventListener('orientationchange', function() {
|
||||
window.setTimeout(applyFit, 120);
|
||||
}, true);
|
||||
}
|
||||
})();
|
||||
''';
|
||||
}
|
||||
|
||||
double _roundMetric(double value) {
|
||||
if (!value.isFinite) {
|
||||
double resolveRoomGameCompatibilityBottomHeight(BuildContext context) {
|
||||
final mediaQuery = MediaQuery.maybeOf(context);
|
||||
if (mediaQuery == null) {
|
||||
return 0;
|
||||
}
|
||||
return double.parse(value.toStringAsFixed(2));
|
||||
final isClassicScreen = mediaQuery.padding.bottom <= 0.5;
|
||||
if (!isClassicScreen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (mediaQuery.size.width * 0.19).clamp(56.0, 88.0).toDouble();
|
||||
}
|
||||
|
||||
class RoomGameCompatibilityBottomFrame extends StatelessWidget {
|
||||
const RoomGameCompatibilityBottomFrame({super.key, required this.height});
|
||||
|
||||
final double height;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (height <= 0) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: height,
|
||||
width: double.infinity,
|
||||
child: CustomPaint(painter: _RoomGameBottomFramePainter()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RoomGameBottomFramePainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final radius = Radius.circular(size.height * 0.18);
|
||||
final frameRect = RRect.fromRectAndCorners(
|
||||
Offset.zero & size,
|
||||
topLeft: radius,
|
||||
topRight: radius,
|
||||
);
|
||||
|
||||
final backgroundPaint =
|
||||
Paint()
|
||||
..shader = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[Color(0xFF0E392E), Color(0xFF082A23)],
|
||||
).createShader(Offset.zero & size);
|
||||
canvas.drawRRect(frameRect, backgroundPaint);
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRRect(frameRect);
|
||||
final patternPaint =
|
||||
Paint()
|
||||
..color = const Color(0xFF2C5B4A).withValues(alpha: 0.20)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
final motifWidth = size.width / 8;
|
||||
final motifHeight = size.height / 2.4;
|
||||
for (double x = -motifWidth; x < size.width + motifWidth; x += motifWidth) {
|
||||
for (
|
||||
double y = -motifHeight * 0.2;
|
||||
y < size.height + motifHeight;
|
||||
y += motifHeight * 0.72
|
||||
) {
|
||||
final rect = Rect.fromCenter(
|
||||
center: Offset(x + motifWidth * 0.52, y + motifHeight * 0.55),
|
||||
width: motifWidth * 0.9,
|
||||
height: motifHeight,
|
||||
);
|
||||
canvas.drawArc(rect, -0.35, 4.5, false, patternPaint);
|
||||
canvas.drawArc(
|
||||
rect.translate(motifWidth * 0.34, 0),
|
||||
2.75,
|
||||
3.6,
|
||||
false,
|
||||
patternPaint,
|
||||
);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
final borderPaint =
|
||||
Paint()
|
||||
..color = const Color(0xFFE9D58A)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 3;
|
||||
final innerBorderPaint =
|
||||
Paint()
|
||||
..color = const Color(0xFF8F7136).withValues(alpha: 0.75)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1;
|
||||
|
||||
canvas.drawRRect(frameRect.deflate(1.5), borderPaint);
|
||||
canvas.drawRRect(frameRect.deflate(5), innerBorderPaint);
|
||||
|
||||
final highlightPaint =
|
||||
Paint()
|
||||
..shader = const LinearGradient(
|
||||
colors: <Color>[
|
||||
Color(0x00F6E8A7),
|
||||
Color(0x99F6E8A7),
|
||||
Color(0x00F6E8A7),
|
||||
],
|
||||
).createShader(Rect.fromLTWH(0, 0, size.width, 3));
|
||||
canvas.drawRect(Rect.fromLTWH(0, 0, size.width, 3), highlightPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
@ -106,7 +106,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
_didFinishPageLoad = true;
|
||||
_log('page_finished url=${_clip(url, 240)}');
|
||||
await _injectBridge(reason: 'page_finished');
|
||||
await _applyNativeViewportFit(reason: 'page_finished');
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {
|
||||
_log(
|
||||
@ -274,7 +273,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
jsCallbackPath: jsCallbackPath,
|
||||
),
|
||||
);
|
||||
await _applyNativeViewportFit(reason: 'send_config');
|
||||
} catch (error) {
|
||||
_log('send_config_error error=${_clip(error.toString(), 300)}');
|
||||
}
|
||||
@ -332,7 +330,6 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
_isLoading = false;
|
||||
_errorMessage = null;
|
||||
});
|
||||
await _applyNativeViewportFit(reason: 'game_loaded');
|
||||
break;
|
||||
case BaishunBridgeActions.gameRecharge:
|
||||
_log('game_recharge open_wallet');
|
||||
@ -636,35 +633,12 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
return screenSize.width / ratio;
|
||||
}
|
||||
|
||||
Future<void> _applyNativeViewportFit({String reason = 'manual'}) async {
|
||||
final topCrop = 28.w;
|
||||
final visibleHeight = _calculateWebViewHeight(context) + topCrop;
|
||||
final expectedHeight = _calculatePreferredWebViewHeight(context);
|
||||
if (expectedHeight <= visibleHeight + 1) {
|
||||
return;
|
||||
}
|
||||
_log(
|
||||
'native_fit reason=$reason visible=${visibleHeight.toStringAsFixed(1)} '
|
||||
'expected=${expectedHeight.toStringAsFixed(1)}',
|
||||
);
|
||||
try {
|
||||
await _controller.runJavaScript(
|
||||
buildRoomGameNativeFitScript(
|
||||
visibleHeight: visibleHeight,
|
||||
expectedHeight: expectedHeight,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
_log(
|
||||
'native_fit_error reason=$reason error=${_clip(error.toString(), 300)}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final topCrop = 28.w;
|
||||
final webViewHeight = _calculateWebViewHeight(context);
|
||||
final bottomFrameHeight = resolveRoomGameCompatibilityBottomHeight(context);
|
||||
final dialogHeight = webViewHeight + bottomFrameHeight;
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
||||
@ -684,7 +658,7 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
),
|
||||
child: Container(
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: webViewHeight,
|
||||
height: dialogHeight,
|
||||
color: const Color(0xFF081915),
|
||||
child: Stack(
|
||||
children: [
|
||||
@ -692,12 +666,22 @@ class _BaishunGamePageState extends State<BaishunGamePage> {
|
||||
top: -topCrop,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
bottom: bottomFrameHeight,
|
||||
child: WebViewWidget(
|
||||
controller: _controller,
|
||||
gestureRecognizers: _webGestureRecognizers,
|
||||
),
|
||||
),
|
||||
if (bottomFrameHeight > 0)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: bottomFrameHeight,
|
||||
child: RoomGameCompatibilityBottomFrame(
|
||||
height: bottomFrameHeight,
|
||||
),
|
||||
),
|
||||
if (_errorMessage != null) _buildErrorState(),
|
||||
if (_isLoading && _errorMessage == null)
|
||||
const IgnorePointer(
|
||||
|
||||
@ -219,7 +219,6 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
await _controller.runJavaScript(
|
||||
LeaderJsBridge.bootstrapScript(launchPayload: _buildLaunchPayload()),
|
||||
);
|
||||
await _applyNativeViewportFit(reason: reason);
|
||||
} catch (error) {
|
||||
_log(
|
||||
'inject_bridge_error reason=$reason error=${_clip(error.toString(), 300)}',
|
||||
@ -387,35 +386,6 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
Future<void> _applyNativeViewportFit({String reason = 'manual'}) async {
|
||||
final screenMode = _resolveScreenMode();
|
||||
if (screenMode == 'full') {
|
||||
return;
|
||||
}
|
||||
final headerHeight = 44.w;
|
||||
final visibleHeight = _calculateBodyHeight(context, headerHeight);
|
||||
final expectedHeight = _calculatePreferredBodyHeight(context);
|
||||
if (expectedHeight <= visibleHeight + 1) {
|
||||
return;
|
||||
}
|
||||
_log(
|
||||
'native_fit reason=$reason visible=${visibleHeight.toStringAsFixed(1)} '
|
||||
'expected=${expectedHeight.toStringAsFixed(1)}',
|
||||
);
|
||||
try {
|
||||
await _controller.runJavaScript(
|
||||
buildRoomGameNativeFitScript(
|
||||
visibleHeight: visibleHeight,
|
||||
expectedHeight: expectedHeight,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
_log(
|
||||
'native_fit_error reason=$reason error=${_clip(error.toString(), 300)}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _log(String message) {
|
||||
debugPrint('$_logPrefix $message');
|
||||
}
|
||||
@ -576,6 +546,7 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
Widget build(BuildContext context) {
|
||||
final headerHeight = 44.w;
|
||||
final bodyHeight = _calculateBodyHeight(context, headerHeight);
|
||||
final bottomFrameHeight = resolveRoomGameCompatibilityBottomHeight(context);
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) async {
|
||||
@ -595,7 +566,7 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
),
|
||||
child: Container(
|
||||
width: ScreenUtil().screenWidth,
|
||||
height: bodyHeight + headerHeight,
|
||||
height: bodyHeight + headerHeight + bottomFrameHeight,
|
||||
color: const Color(0xFF081915),
|
||||
child: Stack(
|
||||
children: [
|
||||
@ -610,12 +581,22 @@ class _LeaderGamePageState extends State<LeaderGamePage> {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: headerHeight,
|
||||
bottom: 0,
|
||||
bottom: bottomFrameHeight,
|
||||
child: WebViewWidget(
|
||||
controller: _controller,
|
||||
gestureRecognizers: _webGestureRecognizers,
|
||||
),
|
||||
),
|
||||
if (bottomFrameHeight > 0)
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: bottomFrameHeight,
|
||||
child: RoomGameCompatibilityBottomFrame(
|
||||
height: bottomFrameHeight,
|
||||
),
|
||||
),
|
||||
if (_errorMessage != null) _buildErrorState(),
|
||||
if (_isLoading && _errorMessage == null)
|
||||
const IgnorePointer(
|
||||
|
||||
@ -57,16 +57,25 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
static const Duration _giftTriggeredMicRefreshMinInterval = Duration(
|
||||
milliseconds: 900,
|
||||
);
|
||||
static const Duration _agoraJoinTimeout = Duration(seconds: 8);
|
||||
static const Duration _agoraDisconnectedGracePeriod = Duration(seconds: 12);
|
||||
|
||||
bool needUpDataUserInfo = false;
|
||||
bool _roomVisualEffectsEnabled = false;
|
||||
bool _isExitingCurrentVoiceRoomSession = false;
|
||||
bool _isHandlingAgoraRoomFailure = false;
|
||||
bool _agoraJoined = false;
|
||||
bool _agoraRoleIsBroadcaster = false;
|
||||
RoomStartupStatus _roomStartupStatus = RoomStartupStatus.idle;
|
||||
RoomStartupFailureType _roomStartupFailureType = RoomStartupFailureType.none;
|
||||
int _roomStartupFailureToken = 0;
|
||||
bool _isHandlingRoomStartupFailure = false;
|
||||
Timer? _micListPollingTimer;
|
||||
Timer? _onlineUsersPollingTimer;
|
||||
Timer? _joinAgoraTimeoutTimer;
|
||||
Timer? _agoraDisconnectedCleanupTimer;
|
||||
Completer<void>? _joinAgoraCompleter;
|
||||
String? _joiningChannelId;
|
||||
bool _isRefreshingMicList = false;
|
||||
bool _isRefreshingOnlineUsers = false;
|
||||
bool _disableMicListRefreshForCurrentSession = false;
|
||||
@ -580,6 +589,96 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _isCurrentAgoraChannel(String? channelId) {
|
||||
final normalizedChannelId = (channelId ?? "").trim();
|
||||
final currentRoomId =
|
||||
(currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (normalizedChannelId.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return normalizedChannelId == currentRoomId ||
|
||||
normalizedChannelId == (_joiningChannelId ?? "").trim();
|
||||
}
|
||||
|
||||
void _cancelAgoraJoinWaiter([Object? error]) {
|
||||
_joinAgoraTimeoutTimer?.cancel();
|
||||
_joinAgoraTimeoutTimer = null;
|
||||
final completer = _joinAgoraCompleter;
|
||||
_joinAgoraCompleter = null;
|
||||
if (error != null && completer != null && !completer.isCompleted) {
|
||||
completer.completeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelAgoraDisconnectedCleanup() {
|
||||
_agoraDisconnectedCleanupTimer?.cancel();
|
||||
_agoraDisconnectedCleanupTimer = null;
|
||||
}
|
||||
|
||||
void _resetAgoraTracking({bool clearJoiningChannel = true}) {
|
||||
_cancelAgoraJoinWaiter();
|
||||
_cancelAgoraDisconnectedCleanup();
|
||||
_agoraJoined = false;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
if (clearJoiningChannel) {
|
||||
_joiningChannelId = null;
|
||||
}
|
||||
_resetLocalAudioRuntimeTracking();
|
||||
}
|
||||
|
||||
bool get _canReportMicActive {
|
||||
return _agoraJoined && _agoraRoleIsBroadcaster && isOnMai();
|
||||
}
|
||||
|
||||
bool get _joinAgoraCompleterIsPending {
|
||||
final completer = _joinAgoraCompleter;
|
||||
return completer != null && !completer.isCompleted;
|
||||
}
|
||||
|
||||
void _resyncHeartbeatFromAgoraState() {
|
||||
final roomId = currenRoom?.roomProfile?.roomProfile?.id ?? "";
|
||||
final canReportMicActive = _canReportMicActive;
|
||||
_syncRoomHeartbeatState(
|
||||
roomId: roomId,
|
||||
isOnMic: canReportMicActive,
|
||||
shouldRunAnchorHeartbeat: canReportMicActive,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isFatalAgoraError(ErrorCodeType err) {
|
||||
return err == ErrorCodeType.errInvalidArgument ||
|
||||
err == ErrorCodeType.errNotReady ||
|
||||
err == ErrorCodeType.errNotSupported ||
|
||||
err == ErrorCodeType.errRefused ||
|
||||
err == ErrorCodeType.errInvalidState ||
|
||||
err == ErrorCodeType.errNoPermission ||
|
||||
err == ErrorCodeType.errJoinChannelRejected ||
|
||||
err == ErrorCodeType.errInvalidAppId ||
|
||||
err == ErrorCodeType.errInvalidChannelName ||
|
||||
err == ErrorCodeType.errTokenExpired ||
|
||||
err == ErrorCodeType.errInvalidToken ||
|
||||
err == ErrorCodeType.errConnectionLost;
|
||||
}
|
||||
|
||||
bool _isFatalConnectionReason(ConnectionChangedReasonType reason) {
|
||||
return reason ==
|
||||
ConnectionChangedReasonType.connectionChangedBannedByServer ||
|
||||
reason == ConnectionChangedReasonType.connectionChangedJoinFailed ||
|
||||
reason == ConnectionChangedReasonType.connectionChangedInvalidAppId ||
|
||||
reason ==
|
||||
ConnectionChangedReasonType.connectionChangedInvalidChannelName ||
|
||||
reason == ConnectionChangedReasonType.connectionChangedInvalidToken ||
|
||||
reason == ConnectionChangedReasonType.connectionChangedTokenExpired ||
|
||||
reason ==
|
||||
ConnectionChangedReasonType.connectionChangedRejectedByServer ||
|
||||
reason == ConnectionChangedReasonType.connectionChangedSameUidLogin ||
|
||||
reason ==
|
||||
ConnectionChangedReasonType.connectionChangedTooManyBroadcasters ||
|
||||
reason ==
|
||||
ConnectionChangedReasonType
|
||||
.connectionChangedLicenseValidationFailure;
|
||||
}
|
||||
|
||||
void _syncSelfMicRuntimeState() {
|
||||
final currentUserId = AccountStorage().getCurrentUser()?.userProfile?.id;
|
||||
if ((currentUserId ?? "").isEmpty) {
|
||||
@ -597,6 +696,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
|
||||
if (currentUserMic == null) {
|
||||
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_syncRoomHeartbeatState(
|
||||
roomId: roomId,
|
||||
isOnMic: false,
|
||||
@ -610,23 +710,16 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
|
||||
_preferredSelfMicIndex = currentUserMic.micIndex;
|
||||
final canReportMicActive = _canReportMicActive;
|
||||
_syncRoomHeartbeatState(
|
||||
roomId: roomId,
|
||||
isOnMic: true,
|
||||
shouldRunAnchorHeartbeat: true,
|
||||
isOnMic: canReportMicActive,
|
||||
shouldRunAnchorHeartbeat: canReportMicActive,
|
||||
);
|
||||
|
||||
if ((currentUserMic.micMute ?? false) || isMic) {
|
||||
_applyLocalAudioRuntimeState(
|
||||
clientRole: ClientRoleType.clientRoleAudience,
|
||||
muted: true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_applyLocalAudioRuntimeState(
|
||||
clientRole: ClientRoleType.clientRoleBroadcaster,
|
||||
muted: false,
|
||||
muted: (currentUserMic.micMute ?? false) || isMic,
|
||||
);
|
||||
}
|
||||
|
||||
@ -641,6 +734,19 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_agoraJoined) {
|
||||
if (_lastScheduledVoiceLiveRoomId != null ||
|
||||
_lastScheduledAnchorRoomId != null) {
|
||||
SCHeartbeatUtils.scheduleHeartbeat(
|
||||
SCHeartbeatStatus.ONLINE.name,
|
||||
false,
|
||||
);
|
||||
SCHeartbeatUtils.cancelAnchorTimer();
|
||||
}
|
||||
_resetHeartbeatTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
final voiceLiveChanged =
|
||||
_lastScheduledVoiceLiveRoomId != normalizedRoomId ||
|
||||
_lastScheduledVoiceLiveOnMic != isOnMic;
|
||||
@ -692,6 +798,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
required ClientRoleType clientRole,
|
||||
required bool muted,
|
||||
}) {
|
||||
if (clientRole != ClientRoleType.clientRoleBroadcaster) {
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
}
|
||||
if (engine == null) {
|
||||
_lastAppliedClientRole = null;
|
||||
_lastAppliedLocalAudioMuted = null;
|
||||
@ -818,7 +927,83 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _cleanupAgoraRoomState({bool clearMicSeats = false}) async {
|
||||
_resetAgoraTracking();
|
||||
try {
|
||||
await engine?.leaveChannel();
|
||||
} catch (error) {
|
||||
debugPrint('[Agora] leave channel during cleanup failed: $error');
|
||||
}
|
||||
if (clearMicSeats) {
|
||||
roomWheatMap.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
}
|
||||
|
||||
Future<void> _handleAgoraJoinFailed(
|
||||
Object error, {
|
||||
required bool exitRoom,
|
||||
}) async {
|
||||
debugPrint('[Agora] join failed: $error');
|
||||
await _cleanupAgoraRoomState(clearMicSeats: true);
|
||||
if (!exitRoom || _isExitingCurrentVoiceRoomSession) {
|
||||
return;
|
||||
}
|
||||
SCTts.show("Join room fail");
|
||||
unawaited(exitCurrentVoiceRoomSession(false));
|
||||
}
|
||||
|
||||
Future<void> _handleAgoraDisconnected(Object reason) async {
|
||||
if (_isHandlingAgoraRoomFailure || _isExitingCurrentVoiceRoomSession) {
|
||||
return;
|
||||
}
|
||||
_isHandlingAgoraRoomFailure = true;
|
||||
debugPrint('[Agora] disconnected, leave room: $reason');
|
||||
SCTts.show("Voice connection failed");
|
||||
await _cleanupAgoraRoomState(clearMicSeats: true);
|
||||
await exitCurrentVoiceRoomSession(false);
|
||||
}
|
||||
|
||||
void _scheduleAgoraDisconnectedCleanup(Object reason) {
|
||||
if (_isExitingCurrentVoiceRoomSession) {
|
||||
return;
|
||||
}
|
||||
_agoraDisconnectedCleanupTimer?.cancel();
|
||||
_agoraDisconnectedCleanupTimer = Timer(_agoraDisconnectedGracePeriod, () {
|
||||
_agoraDisconnectedCleanupTimer = null;
|
||||
if (!_agoraJoined && !_isExitingCurrentVoiceRoomSession) {
|
||||
unawaited(_handleAgoraDisconnected(reason));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> joinAgoraVoiceChannel({bool throwOnError = false}) async {
|
||||
final channelId = (currenRoom?.roomProfile?.roomProfile?.id ?? "").trim();
|
||||
if (channelId.isEmpty) {
|
||||
final error = StateError('Agora channel id is empty');
|
||||
await _handleAgoraJoinFailed(error, exitRoom: !throwOnError);
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_cancelAgoraJoinWaiter();
|
||||
_cancelAgoraDisconnectedCleanup();
|
||||
_joiningChannelId = channelId;
|
||||
_agoraJoined = false;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
final joinCompleter = Completer<void>();
|
||||
_joinAgoraCompleter = joinCompleter;
|
||||
_joinAgoraTimeoutTimer = Timer(_agoraJoinTimeout, () {
|
||||
if (!joinCompleter.isCompleted) {
|
||||
joinCompleter.completeError(
|
||||
TimeoutException('Agora join timeout', _agoraJoinTimeout),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
final rtcEngine = await _initAgoraRtcEngine();
|
||||
rtcEngine.setAudioProfile(
|
||||
@ -832,15 +1017,15 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
);
|
||||
await rtcEngine.disableVideo();
|
||||
var rtcToken = await SCAccountRepository().getRtcToken(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
channelId,
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||
isPublisher: false,
|
||||
);
|
||||
await rtcEngine.joinChannel(
|
||||
token: rtcToken.rtcToken ?? "",
|
||||
channelId: currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
channelId: channelId,
|
||||
uid: _resolveAgoraUidForCurrentUser(),
|
||||
options: ChannelMediaOptions(
|
||||
options: const ChannelMediaOptions(
|
||||
// 自动订阅所有视频流
|
||||
autoSubscribeVideo: false,
|
||||
// 自动订阅所有音频流
|
||||
@ -854,15 +1039,122 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
|
||||
),
|
||||
);
|
||||
await joinCompleter.future;
|
||||
_syncSelfMicRuntimeState();
|
||||
rtcEngine.muteAllRemoteAudioStreams(roomIsMute);
|
||||
} catch (e) {
|
||||
await _handleAgoraJoinFailed(e, exitRoom: !throwOnError);
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
SCTts.show("Join room fail");
|
||||
exitCurrentVoiceRoomSession(false);
|
||||
print('加入失败:${e.runtimeType},${e.toString()}');
|
||||
} finally {
|
||||
if (identical(_joinAgoraCompleter, joinCompleter)) {
|
||||
_cancelAgoraJoinWaiter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _switchAgoraToBroadcaster({
|
||||
required String publisherToken,
|
||||
required bool muted,
|
||||
}) async {
|
||||
if (!_agoraJoined || engine == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (publisherToken.trim().isNotEmpty) {
|
||||
await engine?.renewToken(publisherToken);
|
||||
}
|
||||
await engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||||
if (!muted) {
|
||||
adjustRecordingSignalVolume(100);
|
||||
}
|
||||
await engine?.muteLocalAudioStream(muted);
|
||||
_lastAppliedClientRole = ClientRoleType.clientRoleBroadcaster;
|
||||
_lastAppliedLocalAudioMuted = muted;
|
||||
_agoraRoleIsBroadcaster = true;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
return true;
|
||||
} catch (error) {
|
||||
debugPrint('[Agora] switch to broadcaster failed: $error');
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _switchAgoraToAudience() async {
|
||||
try {
|
||||
await engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
await engine?.muteLocalAudioStream(true);
|
||||
_lastAppliedClientRole = ClientRoleType.clientRoleAudience;
|
||||
_lastAppliedLocalAudioMuted = true;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
return true;
|
||||
} catch (error) {
|
||||
debugPrint('[Agora] switch to audience failed: $error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setSelfMicMutedFromBottom(bool muted) async {
|
||||
isMic = muted;
|
||||
final currentUserId =
|
||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||
final seatIndex = userOnMaiInIndex(currentUserId);
|
||||
if (seatIndex < 0) {
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
final seat = roomWheatMap[seatIndex];
|
||||
final seatMuted = seat?.micMute ?? false;
|
||||
if (seatMuted || muted) {
|
||||
if (_agoraJoined && isOnMai()) {
|
||||
await _switchAgoraToBroadcaster(
|
||||
publisherToken: seat?.roomToken ?? "",
|
||||
muted: true,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await _switchAgoraToBroadcaster(
|
||||
publisherToken: seat?.roomToken ?? "",
|
||||
muted: false,
|
||||
);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> handleSelfMicRemovedByRemote({
|
||||
bool refreshMicList = true,
|
||||
}) async {
|
||||
final currentUserId =
|
||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "").trim();
|
||||
if (currentUserId.isEmpty) {
|
||||
await _switchAgoraToAudience();
|
||||
return;
|
||||
}
|
||||
|
||||
final currentSeatIndex = userOnMaiInIndex(currentUserId);
|
||||
isMic = true;
|
||||
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
|
||||
if (currentSeatIndex > -1) {
|
||||
_startSelfMicReleaseGuard(sourceIndex: currentSeatIndex);
|
||||
} else {
|
||||
_clearSelfMicReleaseGuard();
|
||||
}
|
||||
_clearUserFromSeats(currentUserId);
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_syncSelfMicRuntimeState();
|
||||
notifyListeners();
|
||||
|
||||
await _switchAgoraToAudience();
|
||||
if (refreshMicList) {
|
||||
requestMicrophoneListRefresh(
|
||||
notifyIfUnchanged: false,
|
||||
minInterval: const Duration(milliseconds: 350),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -878,6 +1170,119 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
_rtcEngineEventHandler = RtcEngineEventHandler(
|
||||
onError: (ErrorCodeType err, String msg) {
|
||||
print('rtc错误$err');
|
||||
final error = 'Agora error: $err $msg';
|
||||
if (!_agoraJoined) {
|
||||
final completer = _joinAgoraCompleter;
|
||||
if (completer != null && !completer.isCompleted) {
|
||||
completer.completeError(StateError(error));
|
||||
} else {
|
||||
unawaited(_handleAgoraJoinFailed(error, exitRoom: true));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_isFatalAgoraError(err)) {
|
||||
unawaited(_handleAgoraDisconnected(error));
|
||||
}
|
||||
},
|
||||
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||||
print('rtc 自己加入 ${connection.channelId} ${connection.localUid}');
|
||||
final joinedChannelId = connection.channelId ?? "";
|
||||
if (!_isCurrentAgoraChannel(joinedChannelId)) {
|
||||
return;
|
||||
}
|
||||
_agoraJoined = true;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_cancelAgoraDisconnectedCleanup();
|
||||
_joinAgoraTimeoutTimer?.cancel();
|
||||
_joinAgoraTimeoutTimer = null;
|
||||
final completer = _joinAgoraCompleter;
|
||||
if (completer != null && !completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
},
|
||||
onLeaveChannel: (RtcConnection connection, RtcStats stats) {
|
||||
if (_isCurrentAgoraChannel(connection.channelId)) {
|
||||
final completer = _joinAgoraCompleter;
|
||||
if (completer != null && !completer.isCompleted) {
|
||||
completer.completeError(
|
||||
StateError('Agora left channel before join success'),
|
||||
);
|
||||
}
|
||||
_resetAgoraTracking();
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
}
|
||||
},
|
||||
onClientRoleChanged: (
|
||||
RtcConnection connection,
|
||||
ClientRoleType oldRole,
|
||||
ClientRoleType newRole,
|
||||
ClientRoleOptions newRoleOptions,
|
||||
) {
|
||||
if (!_isCurrentAgoraChannel(connection.channelId)) {
|
||||
return;
|
||||
}
|
||||
_agoraRoleIsBroadcaster =
|
||||
newRole == ClientRoleType.clientRoleBroadcaster;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
},
|
||||
onClientRoleChangeFailed: (
|
||||
RtcConnection connection,
|
||||
ClientRoleChangeFailedReason reason,
|
||||
ClientRoleType currentRole,
|
||||
) {
|
||||
if (!_isCurrentAgoraChannel(connection.channelId)) {
|
||||
return;
|
||||
}
|
||||
_agoraRoleIsBroadcaster =
|
||||
currentRole == ClientRoleType.clientRoleBroadcaster;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
},
|
||||
onConnectionStateChanged: (
|
||||
RtcConnection connection,
|
||||
ConnectionStateType state,
|
||||
ConnectionChangedReasonType reason,
|
||||
) {
|
||||
if (!_isCurrentAgoraChannel(connection.channelId)) {
|
||||
return;
|
||||
}
|
||||
if (state == ConnectionStateType.connectionStateConnected &&
|
||||
reason ==
|
||||
ConnectionChangedReasonType.connectionChangedRejoinSuccess) {
|
||||
_agoraJoined = true;
|
||||
_cancelAgoraDisconnectedCleanup();
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
return;
|
||||
}
|
||||
if (reason ==
|
||||
ConnectionChangedReasonType.connectionChangedLeaveChannel) {
|
||||
_resetAgoraTracking();
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
return;
|
||||
}
|
||||
if (state == ConnectionStateType.connectionStateFailed ||
|
||||
_isFatalConnectionReason(reason)) {
|
||||
_agoraJoined = false;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
if (_joinAgoraCompleterIsPending) {
|
||||
_joinAgoraCompleter?.completeError(
|
||||
StateError('Agora connection failed before join: $reason'),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
_handleAgoraDisconnected('Agora connection failed: $reason'),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state == ConnectionStateType.connectionStateDisconnected ||
|
||||
state == ConnectionStateType.connectionStateReconnecting) {
|
||||
_agoraJoined = false;
|
||||
_agoraRoleIsBroadcaster = false;
|
||||
_resyncHeartbeatFromAgoraState();
|
||||
_scheduleAgoraDisconnectedCleanup(reason);
|
||||
}
|
||||
},
|
||||
onAudioMixingStateChanged: (
|
||||
AudioMixingStateType state,
|
||||
@ -895,9 +1300,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
break;
|
||||
}
|
||||
},
|
||||
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||||
print('rtc 自己加入 ${connection.channelId} ${connection.localUid}');
|
||||
},
|
||||
onUserJoined: (connection, remoteUid, elapsed) {
|
||||
print('rtc用户 $remoteUid 加入了频道');
|
||||
},
|
||||
@ -912,7 +1314,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
var rtcToken = await SCAccountRepository().getRtcToken(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id ?? "",
|
||||
isPublisher: isOnMai(),
|
||||
isPublisher: _agoraRoleIsBroadcaster,
|
||||
);
|
||||
engine?.renewToken(rtcToken.rtcToken ?? "");
|
||||
},
|
||||
@ -940,6 +1342,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
} catch (e) {
|
||||
print('rtc销毁前离开频道出错: $e');
|
||||
}
|
||||
_resetAgoraTracking();
|
||||
|
||||
try {
|
||||
await rtcEngine.release();
|
||||
@ -1207,8 +1610,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
),
|
||||
);
|
||||
|
||||
await _bootstrapRoomHeartbeatState();
|
||||
|
||||
///获取麦位
|
||||
retrieveMicrophoneList();
|
||||
fetchOnlineUsersList();
|
||||
@ -1222,6 +1623,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
|
||||
failureType = RoomStartupFailureType.rtc;
|
||||
await joinAgoraVoiceChannel(throwOnError: true);
|
||||
await _bootstrapRoomHeartbeatState();
|
||||
_setRoomStartupReady();
|
||||
|
||||
Msg joinMsg = Msg(
|
||||
@ -1421,7 +1823,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
SCHeartbeatUtils.scheduleHeartbeat(SCHeartbeatStatus.ONLINE.name, false);
|
||||
SCHeartbeatUtils.cancelAnchorTimer();
|
||||
rtmProvider?.cleanRoomData();
|
||||
await engine?.leaveChannel();
|
||||
await _cleanupAgoraRoomState();
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
await rtmProvider?.quitGroup(
|
||||
currenRoom!.roomProfile?.roomProfile?.roomAccount ?? "",
|
||||
@ -1454,7 +1856,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
if (groupId.isNotEmpty) {
|
||||
await rtmProvider?.quitGroup(groupId);
|
||||
}
|
||||
await engine?.leaveChannel();
|
||||
await _cleanupAgoraRoomState();
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
} catch (e) {
|
||||
print('rtc清理本地房间状态出错: $e');
|
||||
@ -1487,7 +1889,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
if (groupId.isNotEmpty) {
|
||||
await rtmProvider?.quitGroup(groupId);
|
||||
}
|
||||
await engine?.leaveChannel();
|
||||
await _cleanupAgoraRoomState(clearMicSeats: true);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
if (roomId.isNotEmpty) {
|
||||
await SCAccountRepository().quitRoom(roomId);
|
||||
@ -1508,13 +1910,14 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
void _clearData() {
|
||||
_stopRoomStatePolling();
|
||||
_resetHeartbeatTracking();
|
||||
_resetLocalAudioRuntimeTracking();
|
||||
_resetAgoraTracking();
|
||||
_disableMicListRefreshForCurrentSession = false;
|
||||
_disableOnlineUsersRefreshForCurrentSession = false;
|
||||
_isSelfMicActionInFlight = false;
|
||||
_roomStartupStatus = RoomStartupStatus.idle;
|
||||
_roomStartupFailureType = RoomStartupFailureType.none;
|
||||
_isHandlingRoomStartupFailure = false;
|
||||
_isHandlingAgoraRoomFailure = false;
|
||||
_pendingMicListRefresh = false;
|
||||
_pendingMicListRefreshNotifyIfUnchanged = false;
|
||||
_seatEmojiEventVersions.clear();
|
||||
@ -1719,22 +2122,32 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
inviterId: inviterId,
|
||||
);
|
||||
|
||||
/// 设置成主播角色
|
||||
engine?.renewToken(micGoUpRes.roomToken ?? "");
|
||||
if (!micGoUpRes.micMute!) {
|
||||
if (!isMic) {
|
||||
engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||||
adjustRecordingSignalVolume(100);
|
||||
await engine?.muteLocalAudioStream(false);
|
||||
}
|
||||
} else {
|
||||
engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
await engine?.muteLocalAudioStream(true);
|
||||
}
|
||||
SCHeartbeatUtils.scheduleAnchorHeartbeat(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
);
|
||||
final targetIndex = micGoUpRes.micIndex ?? index;
|
||||
final switched = await _switchAgoraToBroadcaster(
|
||||
publisherToken: micGoUpRes.roomToken ?? "",
|
||||
muted: (micGoUpRes.micMute ?? false) || isMic,
|
||||
);
|
||||
if (!switched) {
|
||||
await _switchAgoraToAudience();
|
||||
try {
|
||||
await SCChatRoomRepository().micGoDown(
|
||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
||||
targetIndex,
|
||||
);
|
||||
} catch (downError) {
|
||||
debugPrint(
|
||||
'[Agora] release backend mic after broadcaster switch failed: $downError',
|
||||
);
|
||||
}
|
||||
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
|
||||
_clearUserFromSeats(myUser?.id);
|
||||
_syncSelfMicRuntimeState();
|
||||
notifyListeners();
|
||||
_refreshMicListSilently(notifyIfUnchanged: true);
|
||||
SCTts.show('Failed to put on the microphone, Agora switch failed');
|
||||
return;
|
||||
}
|
||||
|
||||
final previousSelfSeatIndex =
|
||||
myUser != null ? userOnMaiInIndex(myUser.id ?? "") : -1;
|
||||
final isSeatSwitching =
|
||||
@ -1806,7 +2219,6 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
final currentUserSeatIndex = userOnMaiInIndex(currentUserId);
|
||||
if (roomWheatMap[index]?.user?.id == currentUserId) {
|
||||
isMic = true;
|
||||
engine?.muteLocalAudioStream(true);
|
||||
}
|
||||
_clearSelfMicSwitchGuard(clearPreferredIndex: true);
|
||||
if (currentUserSeatIndex > -1) {
|
||||
@ -1816,9 +2228,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
}
|
||||
SCHeartbeatUtils.cancelAnchorTimer();
|
||||
|
||||
/// 设置成主持人角色
|
||||
engine?.renewToken("");
|
||||
engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
await _switchAgoraToAudience();
|
||||
_clearUserFromSeats(currentUserId);
|
||||
_syncSelfMicRuntimeState();
|
||||
notifyListeners();
|
||||
@ -2054,14 +2464,10 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
true,
|
||||
);
|
||||
if (isOnMaiInIndex(index)) {
|
||||
Provider.of<RtcProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
Provider.of<RtcProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.muteLocalAudioStream(true);
|
||||
await _switchAgoraToBroadcaster(
|
||||
publisherToken: roomWheatMap[index]?.roomToken ?? "",
|
||||
muted: true,
|
||||
);
|
||||
}
|
||||
var mic = roomWheatMap[index];
|
||||
if (mic != null) {
|
||||
@ -2079,17 +2485,12 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
||||
false,
|
||||
);
|
||||
if (isOnMaiInIndex(index)) {
|
||||
if (!Provider.of<RtcProvider>(context!, listen: false).isMic) {
|
||||
if (!isMic) {
|
||||
if (roomWheatMap[index]?.user != null) {
|
||||
adjustRecordingSignalVolume(100);
|
||||
Provider.of<RtcProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
|
||||
Provider.of<RtcProvider>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.muteLocalAudioStream(false);
|
||||
await _switchAgoraToBroadcaster(
|
||||
publisherToken: roomWheatMap[index]?.roomToken ?? "",
|
||||
muted: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -304,16 +303,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
if (memberList.isNotEmpty) {
|
||||
if (memberList.first.userID ==
|
||||
AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
);
|
||||
unawaited(
|
||||
rtcProvider.handleSelfMicRemovedByRemote(refreshMicList: false),
|
||||
);
|
||||
|
||||
///退出房间
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context,
|
||||
listen: false,
|
||||
).exitCurrentVoiceRoomSession(false).whenComplete(() {
|
||||
rtcProvider.exitCurrentVoiceRoomSession(false).whenComplete(() {
|
||||
SCRoomUtils.closeAllDialogs();
|
||||
SmartDialog.show(
|
||||
tag: "showConfirmDialog",
|
||||
@ -1395,16 +1394,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
||||
}
|
||||
if (msg.type == SCRoomMsgType.killXiaMai) {
|
||||
///踢下麦
|
||||
if (msg.msg == AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).engine?.setClientRole(role: ClientRoleType.clientRoleAudience);
|
||||
}
|
||||
Provider.of<RealTimeCommunicationManager>(
|
||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||
context!,
|
||||
listen: false,
|
||||
).retrieveMicrophoneList(notifyIfUnchanged: false);
|
||||
);
|
||||
if (msg.msg == AccountStorage().getCurrentUser()?.userProfile?.id) {
|
||||
unawaited(
|
||||
rtcProvider.handleSelfMicRemovedByRemote(refreshMicList: false),
|
||||
);
|
||||
}
|
||||
rtcProvider.retrieveMicrophoneList(notifyIfUnchanged: false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@ -13,7 +14,6 @@ import 'package:yumi/ui_kit/widgets/room/room_menu_dialog.dart';
|
||||
import 'package:yumi/ui_kit/widgets/room/room_msg_input.dart';
|
||||
import 'package:yumi/ui_kit/components/text/sc_text.dart';
|
||||
import 'package:yumi/shared/tools/sc_room_utils.dart';
|
||||
import 'package:yumi/shared/data_sources/sources/local/user_manager.dart';
|
||||
import 'package:yumi/services/audio/rtc_manager.dart';
|
||||
|
||||
import '../../../app/routes/sc_fluro_navigator.dart';
|
||||
@ -340,32 +340,8 @@ class _RoomBottomWidgetState extends State<RoomBottomWidget> {
|
||||
final provider = context.read<RtcProvider>();
|
||||
setState(() {
|
||||
provider.isMic = !provider.isMic;
|
||||
|
||||
provider.roomWheatMap.forEach((k, v) {
|
||||
final seat = provider.micAtIndexForDisplay(k);
|
||||
if ((seat?.user?.id ?? "").trim() ==
|
||||
(AccountStorage().getCurrentUser()?.userProfile?.id ?? "")
|
||||
.trim() &&
|
||||
!(seat?.micMute ?? true)) {
|
||||
if (!provider.isMic) {
|
||||
provider.engine?.adjustRecordingSignalVolume(100);
|
||||
provider.engine?.setClientRole(
|
||||
role: ClientRoleType.clientRoleBroadcaster,
|
||||
);
|
||||
provider.engine?.muteLocalAudioStream(false);
|
||||
} else {
|
||||
if (provider.isMusicPlaying) {
|
||||
provider.engine?.adjustRecordingSignalVolume(0);
|
||||
} else {
|
||||
provider.engine?.setClientRole(
|
||||
role: ClientRoleType.clientRoleAudience,
|
||||
);
|
||||
provider.engine?.muteLocalAudioStream(provider.isMic);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
unawaited(provider.setSelfMicMutedFromBottom(provider.isMic));
|
||||
},
|
||||
child: RoomBottomCircleAction(
|
||||
child: Image.asset(
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.2.2+6
|
||||
version: 1.2.5+7
|
||||
|
||||
|
||||
environment:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user