当前位置: 首页 > news >正文

【flutter】flutter网易云信令 + im + 声网rtm从0实现通话视频文字聊天的踩坑

接了一个国外的项目,项目采用网易云im + 网易云信令+声网rtm遇到的一些问题

这个项目只对接口,给的工期是两周,延了工期,问题还是比较多的

  1. 需要全局监听rtm信息,收到监听内容,引起视频通话
  2. 网易云给的文档太烂,所有的类型推策只能文档一点点推
  3. 声网的rtm配置网易云的信令,坑太多,比如声网接收的字段是number,网易云给的字段是string等一系列报错问题
  4. im普通的对接,体验太差,采用倒叙分页解决此问题
  5. im的上传图片上传过程无显示,需要做上传图片的百分比显示

解决 im普通的对接,体验太差,采用倒叙分页解决此问题和图片上传百分比显示

//imNIMMessageListOption option = NIMMessageListOption(conversationId: widget.conversationId ?? '',direction: NIMQueryDirection.desc, //倒叙limit: limit,anchorMessage: _anchorMessage,// endTime: endTime,);

//图片
// 采用模拟发送数据,根据im提供的NimCore.instance.messageService.sendMessage ,得到是否成功,来显示状态Future<void> _pickImage() async {try {_logI('Picking image from gallery');final XFile? pickedFile = await _imagePicker.pickImage(source: ImageSource.gallery,imageQuality: 80,);if (pickedFile != null) {// u83b7u53d6u6587u4ef6u4fe1u606ffinal File imageFile = File(pickedFile.path);final String fileName = pickedFile.name;// u83b7u53d6u56feu7247u5c3au5bf8final decodedImage =await decodeImageFromList(imageFile.readAsBytesSync());final int width = decodedImage.width;final int height = decodedImage.height;// u521bu5efau4e34u65f6u6d88u606ffinal tempMessage = UnifiedMessage.createTempImage(pickedFile.path);setState(() {_messages.insert(0, tempMessage);});_scrollToBottom();// u5f00u59cbu4e0au4f20_sendImageMessage(tempMessage, pickedFile.path, fileName, width, height);}} catch (e) {_logI('Error picking image: $e');ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to pick image: $e')),);}}

全局监听rtm信息回调

建立 call_manager.dart,单页面引入

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:nim_core_v2/nim_core.dart';
import 'package:yunxin_alog/yunxin_alog.dart';
import '../screens/video_call_screen.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import '../utils/event_bus.dart';
import '../utils/toast_util.dart';var listener;class CallManager {static final CallManager _instance = CallManager._internal();factory CallManager() => _instance;CallManager._internal();RtcEngine? _engine; // 添加 engine 变量final AudioPlayer _audioPlayer = AudioPlayer();Timer? _ringtoneTimer;BuildContext? _lastContext;bool _isShowingCallDialog = false;void initialize(BuildContext context) {_lastContext = context;_setupSignallingListeners();}void updateContext(BuildContext context) {_lastContext = context;}// 添加设置 engine 的方法void setEngine(RtcEngine engine) {_engine = engine;}// 修改现有的代码void _handleCallHangup(event) {print('关闭信令频道房间成功${event.channelInfo!.channelId} 目前一样');NimCore.instance.signallingService.closeRoom(event.channelInfo!.channelId!, true, null).then((result) async {_isShowingCallDialog = false;EventBusUtil().eventBus.fire(VideoCallEvent(VideoCallEvent.LEAVE_CHANNEL));if (result.isSuccess) {if (_engine != null) {await _engine!.leaveChannel();await _engine!.release();}// Success handling} else {// Error handling}});}@overridevoid dispose() {print('dispose');if (listener != null) {listener.cancel();listener = null;}}// 添加一个方法来检查并清理监听器void cleanup() {print('cleanup');if (listener != null) {listener.cancel();listener = null;}_ringtoneTimer?.cancel();_ringtoneTimer = null;_audioPlayer.stop();_isShowingCallDialog = false;}// NIMSignallingEventTypeUnknown	0	未知
// NIMSignallingEventTypeClose	1	关闭信令频道房间
// NIMSignallingEventTypeJoin	2	加入信令频道房间
// NIMSignallingEventTypeInvite	3	邀请加入信令频道房间
// NIMSignallingEventTypeCancelInvite	4	取消邀请加入信令频道房间
// NIMSignallingEventTypeReject	5	拒绝入房的邀请
// NIMSignallingEventTypeAccept	6	接受入房的邀请
// NIMSignallingEventTypeLeave	7	离开信令频道房间
// NIMSignallingEventTypeControl	8	自定义控制命令void _setupSignallingListeners() {// Listen for online events (when the app is active)listener = NimCore.instance.signallingService.onOnlineEvent.listen((NIMSignallingEvent event) {print("事件监听开始${event.toJson()}");_handleSignallingEvent(event);});// Listen for offline events (when the app is in background)NimCore.instance.signallingService.onOfflineEvent.listen((event) {// Handle offline eventsprint('Offline event: $event');});// Listen for multi-client eventsNimCore.instance.signallingService.onMultiClientEvent.listen((event) {// Handle multi-client eventsprint('Multi-client event: $event');});Alog.i(tag: 'CallManager', content: 'Signalling listeners setup complete');}void _handleSignallingEvent(NIMSignallingEvent event) {if (event.channelInfo != null &&event.eventType ==NIMSignallingEventType.NIMSignallingEventTypeInvite) {// 3_showIncomingCallDialog(event.channelInfo, event.requestId ?? '');}if (event.channelInfo != null &&event.eventType == NIMSignallingEventType.NIMSignallingEventTypeClose) {//1_handleCallHangup(event);}if (event.channelInfo != null &&event.eventType == NIMSignallingEventType.NIMSignallingEventTypeJoin) {EventBusUtil().eventBus.fire(VideoCallEvent(VideoCallEvent.USER_JOINED));}if (event.channelInfo != null &&event.eventType ==NIMSignallingEventType.NIMSignallingEventTypeReject) {ToastUtil.showDanger('user reject');cleanup();}}Future<void> _playRingtone() async {try {await _audioPlayer.play(AssetSource('sounds/incoming_call.mp3'));// Loop the ringtone_ringtoneTimer =Timer.periodic(const Duration(seconds: 3), (timer) async {await _audioPlayer.play(AssetSource('sounds/incoming_call.mp3'));});} catch (e) {Alog.e(tag: 'CallManager', content: 'Error playing ringtone: $e');}}void _stopRingtone() {_ringtoneTimer?.cancel();_ringtoneTimer = null;_audioPlayer.stop();}void _showIncomingCallDialog(NIMSignallingChannelInfo? channelInfo, String requestId) {if (channelInfo == null || _lastContext == null || _isShowingCallDialog) {return;}_isShowingCallDialog = true;_playRingtone();String? channelId = channelInfo.channelId;String? callerName = channelInfo.creatorAccountId;showDialog(context: _lastContext!,barrierDismissible: false,builder: (context) {return AlertDialog(backgroundColor: Colors.black87,title: const Text('Incoming Video Call',style: TextStyle(color: Colors.white),textAlign: TextAlign.center,),content: Column(mainAxisSize: MainAxisSize.min,children: [const CircleAvatar(radius: 40,backgroundColor: Colors.purple,child: Icon(Icons.person, size: 50, color: Colors.white),),const SizedBox(height: 16),Text(callerName ?? 'Unknown Caller',style: const TextStyle(color: Colors.white,fontSize: 18,fontWeight: FontWeight.bold,),),const SizedBox(height: 8),const Text('is calling you...',style: TextStyle(color: Colors.white70),),],),actions: [Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,children: [// Decline buttonElevatedButton(onPressed: () {_stopRingtone();_isShowingCallDialog = false;Navigator.of(context).pop();// Reject the call - using hangup instead of reject since reject isn't available_rejectCall(channelInfo, requestId, context);},style: ElevatedButton.styleFrom(backgroundColor: Colors.red,shape: const CircleBorder(),padding: const EdgeInsets.all(16),),child: const Icon(Icons.call_end, color: Colors.white),),// Accept buttonElevatedButton(onPressed: () {_stopRingtone();_isShowingCallDialog = false;Navigator.of(context).pop();// Accept the call_acceptCall(channelInfo, requestId, context);},style: ElevatedButton.styleFrom(backgroundColor: Colors.green,shape: const CircleBorder(),padding: const EdgeInsets.all(16),),child: const Icon(Icons.call, color: Colors.white),),],),],);},).then((_) {_stopRingtone();_isShowingCallDialog = false;});}Future<void> _acceptCall(NIMSignallingChannelInfo channelInfo,String requestId, BuildContext context) async {String? channelId = channelInfo.channelId;String? creatorAccountId = channelInfo.creatorAccountId;if (channelId == null || creatorAccountId == null) {return;}final params = NIMSignallingCallSetupParams(channelId: channelId,callerAccountId: creatorAccountId,requestId: requestId,);try {final result = await NimCore.instance.signallingService.callSetup(params);if (result.isSuccess) {// Navigate to the video call screen with incomingCall flag// 检查 context 是否还有效print("1121212${result.toMap()}");final setUpChanelId = result.data?.roomInfo?.channelInfo?.channelId;final setUpCalleeAccountId =result.data?.roomInfo?.channelInfo?.creatorAccountId;// final setUpRemoteUid = result.data?.roomInfo?.members?.first.uid ?? 0;// final setUpRemoteUid = result.data?.roomInfo?.members?.first.uid;final setUpRemoteUid =result.data?.roomInfo?.channelInfo?.creatorAccountId;if (!context.mounted) {Alog.e(tag: 'CallManager', content: 'Context is not mounted anymore');// 如果原始 context 无效,尝试使用 _lastContextif (_lastContext != null && _lastContext!.mounted) {context = _lastContext!;} else {Alog.e(tag: 'CallManager',content: 'No valid context available for navigation');return;}}Navigator.push(context,MaterialPageRoute(builder: (context) => VideoCallScreen(calleeAccountId: channelInfo.creatorAccountId,isIncomingCall: true,setUpChanelId: setUpChanelId,setUpCalleeAccountId: setUpCalleeAccountId,setUpRemoteUid: int.tryParse(setUpRemoteUid!) ?? 0),),);} else {Alog.e(tag: 'CallManager',content: 'Failed to setup video call: ${result.code}');ToastUtil.showDanger('Failed to setup video call');}} catch (e) {ToastUtil.showDanger('Failed to setup video call');Alog.e(tag: 'CallManager', content: 'Error accepting call: $e');}}Future<void> _rejectCall(NIMSignallingChannelInfo channelInfo,String requestId, BuildContext context) async {try {String? channelId = channelInfo.channelId;String? creatorAccountId = channelInfo.creatorAccountId;if (channelId == null || creatorAccountId == null) {return;}final params = NIMSignallingRejectInviteParams(channelId: channelId,inviterAccountId: creatorAccountId,requestId: requestId,);// Close the room since direct reject isn't availablefinal result =await NimCore.instance.signallingService.rejectInvite(params);if (result.isSuccess) {Alog.i(tag: 'CallManager', content: 'Call rejected successfully');} else {Alog.e(tag: 'CallManager',content: 'Failed to reject call: ${result.code}');}} catch (e) {Alog.e(tag: 'CallManager', content: 'Error rejecting call: $e');}}
}

待续

http://www.lryc.cn/news/585124.html

相关文章:

  • CentOS 安装 JDK+ NGINX+ Tomcat + Redis + MySQL搭建项目环境
  • 『 C++ 入门到放弃 』- 多态
  • MyBatis-Plus通用中等、大量数据分批查询和处理
  • c语言中的数组IV
  • 卸载软件总留一堆“垃圾”?这款免费神器,一键扫清注册表和文件残留!
  • Python shutil模块详解
  • GPT3/chatGPT/T5/PaLM/LLaMA/GLM主流大语言模型的原理和差异
  • 从零实现一个GPT 【React + Express】--- 【3】解析markdown,处理模型记忆
  • 【LeetCode 热题 100】146. LRU 缓存——哈希表+双向链表
  • 0102基础补充_交易演示-区块链-web3
  • Django母婴商城项目实践(二)
  • 机器学习数据集划分全指南:train_test_split详解与实践
  • 基于相似性引导的多视角功能性脑网络融合|文献速递-最新论文分享
  • 【科研绘图系列】R语言绘制系统发育树和柱状图
  • 思维链革命:让大模型突破“机器思考”的边界
  • UniHttp中HttpApiProcessor生命周期钩子介绍以及公共参数填充-以百度天气接口为例
  • Grid网格布局完整功能介绍和示例演示
  • hive/spark sql中unix_timestamp 函数的坑以及时间戳相关的转换
  • php中调用对象的方法可以使用array($object, ‘methodName‘)?
  • 【JMeter】接口加密
  • 【JMeter】数据驱动测试
  • 预防DNS 解析器安全威胁
  • flutter redux状态管理
  • 【unitrix】 4.21 类型级二进制数基本结构体(types.rs)
  • JavaScript加强篇——第五章 DOM节点(加强)与BOM
  • 【驱动】移植CH340驱动,设置 udev 规则,解决和 BRLTTY 的冲突
  • 容器管理: 单机用Docker Compose,多机用Kubernetes
  • 用 React Three Fiber 实现 3D 城市模型的扩散光圈特效
  • 保安员从业资格证历年考试真题
  • Debian:从GNOME切换到Xfce