Browse Source
feat(rtc): 添加RTC管理器并初始化-声网SDK 新增RTCManager类,实现单例模式管理音视频通话
feat(rtc): 添加RTC管理器并初始化-声网SDK 新增RTCManager类,实现单例模式管理音视频通话
- 集成声网RTC SDK,支持频道加入、音视频控制等功能 - 在main.dart中初始化RTC引擎,配置App ID - 提供完整的RTC事件回调处理机制- 实现本地和远程视频视图设置功能- 支持摄像头切换、音视频静音等基础操作ios
2 changed files with 427 additions and 0 deletions
Unified View
Diff Options
@ -0,0 +1,425 @@ |
|||||
|
import 'package:agora_rtc_engine/agora_rtc_engine.dart'; |
||||
|
|
||||
|
/// RTC 管理器,负责管理声网音视频通话功能 |
||||
|
class RTCManager { |
||||
|
// 单例模式 |
||||
|
static final RTCManager _instance = RTCManager._internal(); |
||||
|
factory RTCManager() => _instance; |
||||
|
// 静态getter用于instance访问 |
||||
|
static RTCManager get instance => _instance; |
||||
|
|
||||
|
RtcEngine? _engine; |
||||
|
bool _isInitialized = false; |
||||
|
bool _isInChannel = false; |
||||
|
String? _currentChannelId; |
||||
|
int? _currentUid; |
||||
|
|
||||
|
// 事件回调 |
||||
|
Function(RtcConnection connection, int elapsed)? onJoinChannelSuccess; |
||||
|
Function(RtcConnection connection, int remoteUid, int elapsed)? onUserJoined; |
||||
|
Function( |
||||
|
RtcConnection connection, |
||||
|
int remoteUid, |
||||
|
UserOfflineReasonType reason, |
||||
|
)? |
||||
|
onUserOffline; |
||||
|
Function(RtcConnection connection, RtcStats stats)? onLeaveChannel; |
||||
|
Function( |
||||
|
RtcConnection connection, |
||||
|
ConnectionStateType state, |
||||
|
ConnectionChangedReasonType reason, |
||||
|
)? |
||||
|
onConnectionStateChanged; |
||||
|
Function(int uid, UserInfo userInfo)? onUserInfoUpdated; |
||||
|
Function(RtcConnection connection, int uid, int elapsed)? |
||||
|
onFirstRemoteVideoDecoded; |
||||
|
Function( |
||||
|
RtcConnection connection, |
||||
|
VideoSourceType sourceType, |
||||
|
int uid, |
||||
|
int width, |
||||
|
int height, |
||||
|
int rotation, |
||||
|
)? |
||||
|
onVideoSizeChanged; |
||||
|
Function(RtcConnection connection, int uid, bool muted)? onUserMuteAudio; |
||||
|
Function(RtcConnection connection, int uid, bool muted)? onUserMuteVideo; |
||||
|
Function(RtcConnection connection)? onConnectionLost; |
||||
|
Function(RtcConnection connection, int code, String msg)? onError; |
||||
|
|
||||
|
RTCManager._internal() { |
||||
|
print('RTCManager instance created'); |
||||
|
} |
||||
|
|
||||
|
/// 初始化 RTC Engine |
||||
|
/// [appId] 声网 App ID |
||||
|
/// [channelProfile] 频道场景类型,默认为通信模式 |
||||
|
Future<bool> initialize({ |
||||
|
required String appId, |
||||
|
ChannelProfileType channelProfile = |
||||
|
ChannelProfileType.channelProfileCommunication, |
||||
|
}) async { |
||||
|
try { |
||||
|
if (_isInitialized && _engine != null) { |
||||
|
print('RTC Engine already initialized'); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// 创建 RTC Engine |
||||
|
_engine = createAgoraRtcEngine(); |
||||
|
|
||||
|
// 初始化 RTC Engine |
||||
|
await _engine!.initialize( |
||||
|
RtcEngineContext(appId: appId, channelProfile: channelProfile), |
||||
|
); |
||||
|
|
||||
|
// 注册事件处理器 |
||||
|
_registerEventHandlers(); |
||||
|
|
||||
|
_isInitialized = true; |
||||
|
print('RTC Engine initialized successfully'); |
||||
|
return true; |
||||
|
} catch (e) { |
||||
|
print('Failed to initialize RTC Engine: $e'); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 注册事件处理器 |
||||
|
void _registerEventHandlers() { |
||||
|
if (_engine == null) return; |
||||
|
|
||||
|
_engine!.registerEventHandler( |
||||
|
RtcEngineEventHandler( |
||||
|
onJoinChannelSuccess: (RtcConnection connection, int elapsed) { |
||||
|
_isInChannel = true; |
||||
|
_currentChannelId = connection.channelId; |
||||
|
print('加入频道成功,频道名:${connection.channelId},耗时:${elapsed}ms'); |
||||
|
if (onJoinChannelSuccess != null) { |
||||
|
onJoinChannelSuccess!(connection, elapsed); |
||||
|
} |
||||
|
}, |
||||
|
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) { |
||||
|
print('用户加入,UID:$remoteUid'); |
||||
|
if (onUserJoined != null) { |
||||
|
onUserJoined!(connection, remoteUid, elapsed); |
||||
|
} |
||||
|
}, |
||||
|
onUserOffline: |
||||
|
( |
||||
|
RtcConnection connection, |
||||
|
int remoteUid, |
||||
|
UserOfflineReasonType reason, |
||||
|
) { |
||||
|
print('用户离开,UID:$remoteUid,原因:$reason'); |
||||
|
if (onUserOffline != null) { |
||||
|
onUserOffline!(connection, remoteUid, reason); |
||||
|
} |
||||
|
}, |
||||
|
onLeaveChannel: (RtcConnection connection, RtcStats stats) { |
||||
|
_isInChannel = false; |
||||
|
_currentChannelId = null; |
||||
|
print('离开频道,统计信息:${stats.duration}秒'); |
||||
|
if (onLeaveChannel != null) { |
||||
|
onLeaveChannel!(connection, stats); |
||||
|
} |
||||
|
}, |
||||
|
onConnectionStateChanged: |
||||
|
( |
||||
|
RtcConnection connection, |
||||
|
ConnectionStateType state, |
||||
|
ConnectionChangedReasonType reason, |
||||
|
) { |
||||
|
print('连接状态改变:$state,原因:$reason'); |
||||
|
if (onConnectionStateChanged != null) { |
||||
|
onConnectionStateChanged!(connection, state, reason); |
||||
|
} |
||||
|
}, |
||||
|
onUserInfoUpdated: (int uid, UserInfo userInfo) { |
||||
|
print('用户信息更新,UID:$uid'); |
||||
|
if (onUserInfoUpdated != null) { |
||||
|
onUserInfoUpdated!(uid, userInfo); |
||||
|
} |
||||
|
}, |
||||
|
onFirstRemoteVideoDecoded: |
||||
|
( |
||||
|
RtcConnection connection, |
||||
|
int uid, |
||||
|
int width, |
||||
|
int height, |
||||
|
int elapsed, |
||||
|
) { |
||||
|
print('首次远程视频解码,UID:$uid,分辨率:${width}x${height}'); |
||||
|
if (onFirstRemoteVideoDecoded != null) { |
||||
|
onFirstRemoteVideoDecoded!(connection, uid, elapsed); |
||||
|
} |
||||
|
}, |
||||
|
onVideoSizeChanged: |
||||
|
( |
||||
|
RtcConnection connection, |
||||
|
VideoSourceType sourceType, |
||||
|
int uid, |
||||
|
int width, |
||||
|
int height, |
||||
|
int rotation, |
||||
|
) { |
||||
|
print('视频尺寸改变,UID:$uid,分辨率:${width}x${height}'); |
||||
|
if (onVideoSizeChanged != null) { |
||||
|
onVideoSizeChanged!( |
||||
|
connection, |
||||
|
sourceType, |
||||
|
uid, |
||||
|
width, |
||||
|
height, |
||||
|
rotation, |
||||
|
); |
||||
|
} |
||||
|
}, |
||||
|
onUserMuteAudio: (RtcConnection connection, int uid, bool muted) { |
||||
|
print('用户静音状态改变,UID:$uid,静音:$muted'); |
||||
|
if (onUserMuteAudio != null) { |
||||
|
onUserMuteAudio!(connection, uid, muted); |
||||
|
} |
||||
|
}, |
||||
|
onUserMuteVideo: (RtcConnection connection, int uid, bool muted) { |
||||
|
print('用户视频状态改变,UID:$uid,关闭:$muted'); |
||||
|
if (onUserMuteVideo != null) { |
||||
|
onUserMuteVideo!(connection, uid, muted); |
||||
|
} |
||||
|
}, |
||||
|
onConnectionLost: (RtcConnection connection) { |
||||
|
print('连接丢失'); |
||||
|
if (onConnectionLost != null) { |
||||
|
onConnectionLost!(connection); |
||||
|
} |
||||
|
}, |
||||
|
onError: (ErrorCodeType err, String msg) { |
||||
|
print('RTC Engine 错误:$err,消息:$msg'); |
||||
|
if (onError != null) { |
||||
|
onError!( |
||||
|
RtcConnection( |
||||
|
channelId: _currentChannelId ?? '', |
||||
|
localUid: _currentUid ?? 0, |
||||
|
), |
||||
|
err.value(), |
||||
|
msg, |
||||
|
); |
||||
|
} |
||||
|
}, |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/// 启用视频 |
||||
|
Future<void> enableVideo() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.enableVideo(); |
||||
|
print('视频已启用'); |
||||
|
} |
||||
|
|
||||
|
/// 禁用视频 |
||||
|
Future<void> disableVideo() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.disableVideo(); |
||||
|
print('视频已禁用'); |
||||
|
} |
||||
|
|
||||
|
/// 启用音频 |
||||
|
Future<void> enableAudio() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.enableAudio(); |
||||
|
print('音频已启用'); |
||||
|
} |
||||
|
|
||||
|
/// 禁用音频 |
||||
|
Future<void> disableAudio() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.disableAudio(); |
||||
|
print('音频已禁用'); |
||||
|
} |
||||
|
|
||||
|
/// 开启本地视频预览 |
||||
|
Future<void> startPreview() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.startPreview(); |
||||
|
print('本地视频预览已开启'); |
||||
|
} |
||||
|
|
||||
|
/// 停止本地视频预览 |
||||
|
Future<void> stopPreview() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.stopPreview(); |
||||
|
print('本地视频预览已停止'); |
||||
|
} |
||||
|
|
||||
|
/// 设置本地视频视图 |
||||
|
/// [viewId] 视图ID |
||||
|
/// [mirrorMode] 镜像模式 |
||||
|
Future<void> setupLocalVideo({ |
||||
|
required int viewId, |
||||
|
VideoSourceType sourceType = VideoSourceType.videoSourceCameraPrimary, |
||||
|
VideoMirrorModeType mirrorMode = VideoMirrorModeType.videoMirrorModeAuto, |
||||
|
}) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.setupLocalVideo( |
||||
|
VideoCanvas(view: viewId, sourceType: sourceType, mirrorMode: mirrorMode), |
||||
|
); |
||||
|
print('本地视频视图已设置,viewId:$viewId'); |
||||
|
} |
||||
|
|
||||
|
/// 设置远程视频视图 |
||||
|
/// [uid] 远程用户ID |
||||
|
/// [viewId] 视图ID |
||||
|
Future<void> setupRemoteVideo({ |
||||
|
required int uid, |
||||
|
required int viewId, |
||||
|
VideoSourceType sourceType = VideoSourceType.videoSourceCameraPrimary, |
||||
|
VideoMirrorModeType mirrorMode = |
||||
|
VideoMirrorModeType.videoMirrorModeDisabled, |
||||
|
}) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.setupRemoteVideo( |
||||
|
VideoCanvas( |
||||
|
uid: uid, |
||||
|
view: viewId, |
||||
|
sourceType: sourceType, |
||||
|
mirrorMode: mirrorMode, |
||||
|
), |
||||
|
); |
||||
|
print('远程视频视图已设置,UID:$uid,viewId:$viewId'); |
||||
|
} |
||||
|
|
||||
|
/// 加入频道 |
||||
|
/// [token] 频道令牌(可选,如果频道未开启鉴权则可以为空字符串) |
||||
|
/// [channelId] 频道ID |
||||
|
/// [uid] 用户ID(0表示自动分配) |
||||
|
/// [options] 频道媒体选项 |
||||
|
Future<void> joinChannel({ |
||||
|
String? token, |
||||
|
required String channelId, |
||||
|
int uid = 0, |
||||
|
ChannelMediaOptions? options, |
||||
|
}) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
if (_isInChannel) { |
||||
|
print('已经在频道中,先离开当前频道'); |
||||
|
await leaveChannel(); |
||||
|
} |
||||
|
|
||||
|
_currentUid = uid; |
||||
|
await _engine!.joinChannel( |
||||
|
token: token ?? '', |
||||
|
channelId: channelId, |
||||
|
uid: uid, |
||||
|
options: options ?? const ChannelMediaOptions(), |
||||
|
); |
||||
|
print('正在加入频道:$channelId,UID:$uid'); |
||||
|
} |
||||
|
|
||||
|
/// 离开频道 |
||||
|
Future<void> leaveChannel() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
if (!_isInChannel) { |
||||
|
print('当前不在频道中'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await _engine!.leaveChannel(); |
||||
|
_currentUid = null; |
||||
|
print('已离开频道'); |
||||
|
} |
||||
|
|
||||
|
/// 切换摄像头 |
||||
|
Future<void> switchCamera() async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.switchCamera(); |
||||
|
print('摄像头已切换'); |
||||
|
} |
||||
|
|
||||
|
/// 静音/取消静音本地音频 |
||||
|
/// [muted] true表示静音,false表示取消静音 |
||||
|
Future<void> muteLocalAudio(bool muted) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.muteLocalAudioStream(muted); |
||||
|
print('本地音频${muted ? "已静音" : "已取消静音"}'); |
||||
|
} |
||||
|
|
||||
|
/// 开启/关闭本地视频 |
||||
|
/// [enabled] true表示开启,false表示关闭 |
||||
|
Future<void> muteLocalVideo(bool enabled) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.muteLocalVideoStream(!enabled); |
||||
|
print('本地视频${enabled ? "已开启" : "已关闭"}'); |
||||
|
} |
||||
|
|
||||
|
/// 设置客户端角色(仅用于直播场景) |
||||
|
/// [role] 客户端角色:主播或观众 |
||||
|
Future<void> setClientRole({ |
||||
|
required ClientRoleType role, |
||||
|
ClientRoleOptions? options, |
||||
|
}) async { |
||||
|
if (_engine == null) { |
||||
|
throw Exception('RTC Engine not initialized'); |
||||
|
} |
||||
|
await _engine!.setClientRole(role: role, options: options); |
||||
|
print('客户端角色已设置为:$role'); |
||||
|
} |
||||
|
|
||||
|
/// 获取当前是否在频道中 |
||||
|
bool get isInChannel => _isInChannel; |
||||
|
|
||||
|
/// 获取当前频道ID |
||||
|
String? get currentChannelId => _currentChannelId; |
||||
|
|
||||
|
/// 获取当前用户ID |
||||
|
int? get currentUid => _currentUid; |
||||
|
|
||||
|
/// 获取 RTC Engine 实例(用于高级操作) |
||||
|
RtcEngine? get engine => _engine; |
||||
|
|
||||
|
/// 释放资源 |
||||
|
Future<void> dispose() async { |
||||
|
try { |
||||
|
if (_isInChannel) { |
||||
|
await leaveChannel(); |
||||
|
} |
||||
|
if (_engine != null) { |
||||
|
await _engine!.release(); |
||||
|
_engine = null; |
||||
|
} |
||||
|
_isInitialized = false; |
||||
|
_isInChannel = false; |
||||
|
_currentChannelId = null; |
||||
|
_currentUid = null; |
||||
|
print('RTC Engine disposed'); |
||||
|
} catch (e) { |
||||
|
print('Failed to dispose RTC Engine: $e'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save