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
Split 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