123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- import 'dart:convert';
- import 'dart:math';
- import 'package:tencent_im_sdk_plugin/enum/V2TimSDKListener.dart';
- import 'package:tencent_im_sdk_plugin/enum/V2TimSignalingListener.dart';
- import 'package:tencent_im_sdk_plugin/models/v2_tim_callback.dart';
- import 'package:tencent_trtc_cloud/tx_beauty_manager.dart';
- import '../TRTCCalling.dart';
- import '../TRTCCallingDef.dart';
- import '../TRTCCallingDelegate.dart';
- //trtc sdk
- import 'package:tencent_trtc_cloud/trtc_cloud.dart';
- import 'package:tencent_trtc_cloud/trtc_cloud_def.dart';
- import 'package:tencent_trtc_cloud/tx_audio_effect_manager.dart';
- import 'package:tencent_trtc_cloud/tx_device_manager.dart';
- //im sdk
- import 'package:tencent_im_sdk_plugin/tencent_im_sdk_plugin.dart';
- import 'package:tencent_im_sdk_plugin/models/v2_tim_value_callback.dart';
- import 'package:tencent_im_sdk_plugin/enum/log_level.dart';
- import 'package:tencent_im_sdk_plugin/manager/v2_tim_manager.dart';
- class TRTCCallingImpl extends TRTCCalling {
- String logTag = "TRTCCallingImpl";
- static TRTCCallingImpl? sInstance;
- int timeOutCount = 30; //超时时间,默认30s
- int codeErr = -1;
- // 是否首次邀请
- bool isOnCalling = false;
- bool mIsInRoom = false;
- int mEnterRoomTime = 0;
- String mCurCallID = "";
- int mCurRoomID = 0;
- String mCurGroupId = ""; //当前群组通话的群组ID
- String? mNickName;
- String? mFaceUrl;
- /*
- * 当前邀请列表
- * C2C通话时会记录自己邀请的用户
- * IM群组通话时会同步群组内邀请的用户
- * 当用户接听、拒绝、忙线、超时会从列表中移除该用户
- */
- List<String> mCurInvitedList = [];
- List<dynamic> mCurCallList = [];
- //当前语音通话中的远端用户
- Set mCurRoomRemoteUserSet = new Set();
- /*
- * C2C通话的邀请人
- * 例如A邀请B,B存储的mCurSponsorForMe为A
- */
- String mCurSponsorForMe = "";
- //当前通话的类型
- int? mCurCallType;
- late int mSdkAppId;
- late String mCurUserId;
- late String mCurUserSig;
- String? mRoomId;
- String? mOwnerUserId;
- bool mIsInitIMSDK = false;
- bool mIsLogin = false;
- String mRole = "audience"; //默认为观众,archor为主播
- late V2TIMManager timManager;
- late TRTCCloud mTRTCCloud;
- late TXAudioEffectManager txAudioManager;
- late TXDeviceManager txDeviceManager;
- Set<VoiceListenerFunc> listeners = Set();
- TRTCCallingImpl() {
- //获取腾讯即时通信IM manager
- timManager = TencentImSDKPlugin.v2TIMManager;
- initTRTC();
- }
- initTRTC() async {
- mTRTCCloud = (await TRTCCloud.sharedInstance())!;
- txDeviceManager = mTRTCCloud.getDeviceManager();
- txAudioManager = mTRTCCloud.getAudioEffectManager();
- }
- static sharedInstance() {
- if (sInstance == null) {
- sInstance = new TRTCCallingImpl();
- }
- return sInstance;
- }
- static void destroySharedInstance() {
- if (sInstance != null) {
- sInstance = null;
- }
- TRTCCloud.destroySharedInstance();
- }
- @override
- void destroy() {
- mTRTCCloud.stopLocalPreview();
- mTRTCCloud.stopLocalAudio();
- mTRTCCloud.exitRoom();
- }
- @override
- void registerListener(VoiceListenerFunc func) {
- if (listeners.isEmpty) {
- //监听im事件
- timManager
- .getSignalingManager()
- .addSignalingListener(listener: signalingListener());
- //监听trtc事件
- mTRTCCloud.registerListener(rtcListener);
- }
- listeners.add(func);
- }
- @override
- void unRegisterListener(VoiceListenerFunc func) {
- listeners.remove(func);
- if (listeners.isEmpty) {
- mTRTCCloud.unRegisterListener(rtcListener);
- timManager
- .getSignalingManager()
- .removeSignalingListener(listener: signalingListener);
- }
- }
- emitEvent(type, params) {
- for (var item in listeners) {
- item(type, params);
- }
- }
- signalingListener() {
- TRTCCallingDelegate type;
- return new V2TimSignalingListener(
- onInvitationCancelled: (inviteID, inviter, data) {
- if (!_isCallingData(data)) {
- return;
- }
- if (inviteID == mCurCallID) {
- _stopCall();
- emitEvent(TRTCCallingDelegate.onCallingCancel, {});
- }
- },
- onInvitationTimeout: (inviteID, inviteeList) {
- //邀请者
- String curGroupCallId = _getGroupCallId(mCurUserId);
- if (!_isEmpty(mCurCallID) && inviteID != mCurCallID) {
- return;
- } else if (_isEmpty(mCurCallID) && inviteID != curGroupCallId) {
- return;
- }
- if (mCurSponsorForMe.isEmpty) {
- for (var i = 0; i < inviteeList.length; i++) {
- emitEvent(TRTCCallingDelegate.onNoResp, inviteeList[i]);
- mCurInvitedList.remove(inviteeList[i]);
- }
- } else {
- //被邀请者
- if (inviteeList.contains(mCurUserId)) {
- _stopCall();
- emitEvent(TRTCCallingDelegate.onCallingTimeout, {});
- }
- mCurInvitedList.remove(inviteeList);
- }
- // 每次超时都需要判断当前是否需要结束通话
- _preExitRoom(null);
- },
- onInviteeAccepted: (inviteID, invitee, data) {
- if (!_isCallingData(data)) {
- return;
- }
- mCurInvitedList.remove(invitee);
- },
- onInviteeRejected: (inviteID, invitee, data) {
- if (!_isCallingData(data)) {
- return;
- }
- String curGroupCallId = _getGroupCallId(invitee);
- if (mCurCallID == inviteID || curGroupCallId == inviteID) {
- try {
- Map<String, dynamic>? customMap = jsonDecode(data);
- mCurInvitedList.remove(invitee);
- if (customMap != null && customMap.containsKey('line_busy')) {
- emitEvent(TRTCCallingDelegate.onLineBusy, invitee);
- } else {
- emitEvent(TRTCCallingDelegate.onReject, invitee);
- }
- _preExitRoom(null);
- } catch (e) {
- print(logTag +
- "=onInviteeRejected JsonSyntaxException:" +
- e.toString());
- }
- }
- },
- onReceiveNewInvitation:
- (inviteID, inviter, groupID, inviteeList, data) async {
- if (!_isCallingData(data)) {
- return;
- }
- try {
- Map<String, dynamic>? customMap = jsonDecode(data);
- if (customMap == null) {
- print(logTag + "onReceiveNewInvitation extraMap is null, ignore");
- return;
- }
- if (customMap.containsKey('call_type')) {
- mCurCallType = customMap['call_type'];
- }
- if (customMap.containsKey('call_end')) {
- _preExitRoom(null);
- return;
- }
- if (customMap.containsKey('room_id')) {
- mCurRoomID = customMap['room_id'];
- }
- } catch (e) {
- print(logTag +
- "=onReceiveNewInvitation JsonSyntaxException:" +
- e.toString());
- }
- if (isOnCalling && inviteeList.contains(mCurUserId)) {
- // 正在通话时,收到了一个邀请我的通话请求,需要告诉对方忙线
- Map<String, dynamic> busyMap = _getCustomMap();
- busyMap['line_busy'] = 'line_busy';
- await timManager
- .getSignalingManager()
- .reject(inviteID: inviteID, data: jsonEncode(busyMap));
- return;
- }
- // 与对方处在同一个群中,此时收到了邀请群中其他人通话的请求,界面上展示连接动画
- if (!_isEmpty(groupID) && !_isEmpty(mCurGroupId)) {
- mCurInvitedList.addAll(inviteeList);
- TRTCCallingDelegate type =
- TRTCCallingDelegate.onGroupCallInviteeListUpdate;
- emitEvent(type, mCurInvitedList);
- }
- if (!inviteeList.contains(mCurUserId)) {
- return;
- }
- mCurSponsorForMe = inviter;
- mCurCallID = inviteID;
- mCurGroupId = groupID;
- type = TRTCCallingDelegate.onInvited;
- emitEvent(type, {
- 'sponsor': inviter,
- 'userIds': inviteeList.remove(mCurUserId),
- 'isFromGroup': !_isEmpty(groupID),
- 'type': mCurCallType
- });
- },
- );
- }
- rtcListener(rtcType, param) {
- String typeStr = rtcType.toString();
- TRTCCallingDelegate type;
- typeStr = typeStr.replaceFirst("TRTCCloudListener.", "");
- if (typeStr == "onEnterRoom") {
- if (param < 0) {
- _stopCall();
- } else {
- mIsInRoom = true;
- }
- } else if (typeStr == "onError") {
- type = TRTCCallingDelegate.onError;
- emitEvent(type, param);
- } else if (typeStr == "onUserVoiceVolume") {
- type = TRTCCallingDelegate.onUserVoiceVolume;
- emitEvent(type, param);
- } else if (typeStr == "onUserVideoAvailable") {
- type = TRTCCallingDelegate.onUserVideoAvailable;
- emitEvent(type, param);
- } else if (typeStr == "onUserAudioAvailable") {
- type = TRTCCallingDelegate.onUserAudioAvailable;
- emitEvent(type, param);
- } else if (typeStr == "onRemoteUserEnterRoom") {
- mCurRoomRemoteUserSet.add(param);
- // 只有单聊这个时间才是正确的,因为单聊只会有一个用户进群,群聊这个时间会被后面的人重置
- mEnterRoomTime = DateTime.now().millisecondsSinceEpoch;
- type = TRTCCallingDelegate.onUserEnter;
- emitEvent(type, param);
- } else if (typeStr == "onRemoteUserLeaveRoom") {
- mCurRoomRemoteUserSet.remove(param['userId']);
- mCurInvitedList.remove(param['userId']);
- type = TRTCCallingDelegate.onUserLeave;
- emitEvent(type, param['userId']);
- _preExitRoom(param['userId']);
- }
- }
- // 多人通话时,根据userId找到对应的通话id
- _getGroupCallId(String userId) {
- for (int i = 0; i < mCurCallList.length; i++) {
- if (mCurCallList[i]['userId'] == userId) {
- return mCurCallList[i]['callId'];
- }
- }
- return '';
- }
- /*
- * 重要:用于判断是否需要结束本次通话
- * 在用户超时、拒绝、忙线、有人退出房间时需要进行判断
- */
- _preExitRoom(String? leaveUser) {
- if (mCurRoomRemoteUserSet.isEmpty && mCurInvitedList.isEmpty && mIsInRoom) {
- // 当没有其他用户在房间里了,则结束通话。
- if (!_isEmpty(leaveUser)) {
- Map<String, dynamic> customMap = _getCustomMap();
- //customMap['call_end'] = 'call_end';
- customMap['call_end'] = 10;
- if (_isEmpty(mCurGroupId)) {
- timManager
- .getSignalingManager()
- .invite(invitee: leaveUser!, data: jsonEncode(customMap));
- } else {
- timManager.getSignalingManager().inviteInGroup(
- groupID: mCurGroupId,
- inviteeList: mCurInvitedList,
- data: jsonEncode(customMap));
- }
- }
- _exitRoom();
- _stopCall();
- emitEvent(TRTCCallingDelegate.onCallEnd, {});
- }
- }
- @override
- Future<ActionCallback> login(
- int sdkAppId, String userId, String userSig) async {
- mSdkAppId = sdkAppId;
- mCurUserId = userId;
- mCurUserSig = userSig;
- if (!mIsInitIMSDK) {
- //初始化SDK
- V2TimValueCallback<bool> initRes = await timManager.initSDK(
- sdkAppID: sdkAppId, //填入在控制台上申请的sdkappid
- loglevel: LogLevel.V2TIM_LOG_ERROR,
- listener: new V2TimSDKListener(onKickedOffline: () {
- TRTCCallingDelegate type = TRTCCallingDelegate.onKickedOffline;
- emitEvent(type, {});
- }));
- if (initRes.code != 0) {
- //初始化sdk错误
- return ActionCallback(code: 0, desc: 'init im sdk error');
- }
- }
- mIsInitIMSDK = true;
- // 登陆到 IM
- String? loginedUserId = (await timManager.getLoginUser()).data;
- if (loginedUserId != null && loginedUserId == userId) {
- mIsLogin = true;
- return ActionCallback(code: 0, desc: 'login im success');
- }
- V2TimCallback loginRes =
- await timManager.login(userID: userId, userSig: userSig);
- if (loginRes.code == 0) {
- mIsLogin = true;
- return ActionCallback(code: 0, desc: 'login im success');
- } else {
- return ActionCallback(code: codeErr, desc: loginRes.desc);
- }
- }
- @override
- Future<ActionCallback> logout() async {
- V2TimCallback loginRes = await timManager.logout();
- _stopCall();
- _exitRoom();
- mNickName = "";
- mFaceUrl = "";
- return ActionCallback(code: loginRes.code, desc: loginRes.desc);
- }
- @override
- Future<ActionCallback> call(String userId, int type) async {
- if (!isOnCalling) {
- // 首次拨打电话,生成id,并进入trtc房间
- mCurRoomID = _generateRoomID();
- mCurCallType = type;
- _enterTRTCRoom();
- }
- mCurInvitedList.add(userId);
- V2TimValueCallback res = await timManager.getSignalingManager().invite(
- invitee: userId,
- data: jsonEncode(_getCustomMap()),
- timeout: timeOutCount,
- onlineUserOnly: false);
- mCurCallID = res.data;
- mCurCallList.add({'userId': userId, 'callId': mCurCallID});
- return ActionCallback(code: res.code, desc: res.desc);
- }
- _isCallingData(String data) {
- try {
- Map<String, dynamic> customMap = jsonDecode(data);
- if (customMap.containsKey('call_type')) {
- return true;
- }
- } catch (e) {
- print("isCallingData json parse error");
- return false;
- }
- return false;
- }
- _getCustomMap() {
- Map<String, dynamic> customMap = new Map<String, dynamic>();
- customMap['version'] = 1;
- customMap['call_type'] = mCurCallType;
- customMap['room_id'] = mCurRoomID;
- return customMap;
- }
- /*
- * trtc 进房
- */
- _enterTRTCRoom() {
- isOnCalling = true;
- if (mCurCallType == TRTCCalling.typeVideoCall) {
- // 开启基础美颜
- TXBeautyManager txBeautyManager = mTRTCCloud.getBeautyManager();
- // 自然美颜
- txBeautyManager.setBeautyStyle(1);
- txBeautyManager.setBeautyLevel(6);
- // 进房前需要设置一下关键参数
- TRTCVideoEncParam encParam = new TRTCVideoEncParam();
- encParam.videoResolution = TRTCCloudDef.TRTC_VIDEO_RESOLUTION_960_540;
- encParam.videoFps = 15;
- encParam.videoBitrate = 1000;
- encParam.videoResolutionMode =
- TRTCCloudDef.TRTC_VIDEO_RESOLUTION_MODE_PORTRAIT;
- encParam.enableAdjustRes = true;
- mTRTCCloud.setVideoEncoderParam(encParam);
- }
- mTRTCCloud.enableAudioVolumeEvaluation(500);
- txDeviceManager.setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
- mTRTCCloud.muteLocalAudio(false);
- mTRTCCloud.startLocalAudio(TRTCCloudDef.TRTC_AUDIO_QUALITY_DEFAULT);
- mTRTCCloud.enterRoom(
- TRTCParams(
- sdkAppId: mSdkAppId,
- userId: mCurUserId,
- userSig: mCurUserSig,
- roomId: mCurRoomID,
- role: TRTCCloudDef.TRTCRoleAnchor),
- mCurCallType == TRTCCalling.typeVideoCall
- ? TRTCCloudDef.TRTC_APP_SCENE_VIDEOCALL
- : TRTCCloudDef.TRTC_APP_SCENE_AUDIOCALL);
- }
- @override
- Future<ActionCallback> groupCall(
- List<String> userIdList, int type, String? groupId) async {
- if (_isListEmpty(userIdList)) {
- return ActionCallback(code: codeErr, desc: 'userIdList is empty');
- }
- if (!isOnCalling) {
- // 首次拨打电话,生成id,并进入trtc房间
- mCurRoomID = _generateRoomID();
- mCurCallType = type;
- _enterTRTCRoom();
- }
- // 过滤已经邀请的用户id
- List<String> filterInvitedList = [];
- for (var i = 0; i < userIdList.length; i++) {
- if (!mCurInvitedList.contains(userIdList[i])) {
- filterInvitedList.add(userIdList[i]);
- }
- }
- if (_isListEmpty(filterInvitedList)) {
- return ActionCallback(
- code: codeErr, desc: 'the userIdList has been invited');
- }
- mCurInvitedList = filterInvitedList;
- if (_isEmpty(groupId)) {
- for (int i = 0; i < mCurInvitedList.length; i++) {
- V2TimValueCallback res = await timManager.getSignalingManager().invite(
- invitee: mCurInvitedList[i],
- data: jsonEncode(_getCustomMap()),
- timeout: timeOutCount,
- onlineUserOnly: false);
- mCurCallList.add({'userId': mCurInvitedList[i], 'callId': res.data});
- }
- return ActionCallback(code: 0, desc: '');
- } else {
- V2TimValueCallback res = await timManager
- .getSignalingManager()
- .inviteInGroup(
- groupID: groupId!,
- inviteeList: mCurInvitedList,
- data: jsonEncode(_getCustomMap()),
- timeout: timeOutCount,
- onlineUserOnly: false);
- mCurCallID = res.data;
- return ActionCallback(code: res.code, desc: res.desc);
- }
- }
- _isListEmpty(List? list) {
- return list == null || list.length == 0;
- }
- _isEmpty(String? data) {
- return data == null || data == "";
- }
- /*
- * 停止此次通话,把所有的变量都会重置
- */
- _stopCall() {
- isOnCalling = false;
- mIsInRoom = false;
- mEnterRoomTime = 0;
- mCurCallID = "";
- mCurRoomID = 0;
- mCurInvitedList = [];
- mCurCallList = [];
- mCurRoomRemoteUserSet.clear();
- mCurSponsorForMe = "";
- mCurGroupId = "";
- mCurCallType = TRTCCalling.typeUnknow;
- }
- @override
- Future<ActionCallback> accept() async {
- _enterTRTCRoom();
- V2TimCallback res = await timManager
- .getSignalingManager()
- .accept(inviteID: mCurCallID, data: jsonEncode(_getCustomMap()));
- return ActionCallback(code: res.code, desc: res.desc);
- }
- @override
- Future<void> closeCamera() async {
- return mTRTCCloud.stopLocalPreview();
- }
- @override
- Future<void> hangup() async {
- if (!isOnCalling) {
- await reject();
- return;
- }
- _exitRoom();
- if (_isEmpty(mCurGroupId)) {
- for (int i = 0; i < mCurInvitedList.length; i++) {
- await timManager.getSignalingManager().cancel(
- inviteID: _getGroupCallId(mCurInvitedList[i]),
- data: jsonEncode(_getCustomMap()));
- }
- } else {
- if (mCurRoomRemoteUserSet.isEmpty) {
- await timManager.getSignalingManager().cancel(
- inviteID: _getGroupCallId(mCurCallID),
- data: jsonEncode(_getCustomMap()));
- }
- }
- _stopCall();
- }
- /*
- * trtc 退房
- */
- _exitRoom() {
- mTRTCCloud.stopLocalPreview();
- mTRTCCloud.stopLocalAudio();
- mTRTCCloud.exitRoom();
- }
- @override
- Future<void> openCamera(bool isFrontCamera, int viewId) {
- return mTRTCCloud.startLocalPreview(isFrontCamera, viewId);
- }
- @override
- Future<void> updateLocalView(int viewId) {
- return mTRTCCloud.updateLocalView(viewId);
- }
- @override
- Future<ActionCallback> reject() async {
- V2TimCallback res = await timManager
- .getSignalingManager()
- .reject(inviteID: mCurCallID, data: jsonEncode(_getCustomMap()));
- _stopCall();
- return ActionCallback(code: res.code, desc: res.desc);
- }
- @override
- Future<void> setHandsFree(bool isHandsFree) {
- if (isHandsFree) {
- return txDeviceManager
- .setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_SPEAKER);
- } else {
- return txDeviceManager
- .setAudioRoute(TRTCCloudDef.TRTC_AUDIO_ROUTE_EARPIECE);
- }
- }
- @override
- Future<void> setMicMute(bool isMute) {
- return mTRTCCloud.muteLocalAudio(isMute);
- }
- @override
- Future<void> startRemoteView(String userId, int streamType, int viewId) {
- return mTRTCCloud.startRemoteView(userId, streamType, viewId);
- }
- @override
- Future<void> stopRemoteView(String userId, int streamType) {
- return mTRTCCloud.stopRemoteView(userId, streamType);
- }
- @override
- Future<void> updateRemoteView(String userId, int streamType, int viewId) {
- return mTRTCCloud.updateRemoteView(viewId, streamType, userId);
- }
- @override
- Future<void> switchCamera(bool isFrontCamera) {
- return txDeviceManager.switchCamera(isFrontCamera);
- }
- _generateRoomID() {
- Random rng = new Random();
- //2147483647
- String numStr = '';
- for (var i = 0; i < 9; i++) {
- numStr += rng.nextInt(9).toString();
- }
- return int.tryParse(numStr);
- }
- }
- /// @nodoc
- typedef VoiceListenerFunc<P> = void Function(
- TRTCCallingDelegate type, P params);
|