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

【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.xdnf.cn/news/15230.html

相关文章:

  • 影石(insta360)GO3拇指相机格式化后的恢复方法
  • OpenCV 与深度学习:从图像分类到目标检测技术
  • 如何安装和配置Autoptimize插件以提高WordPress网站访问速度
  • 飞算JavaAI:重塑Java开发的“人机协同“新模式
  • 免费应用分发平台的安全漏洞和防护机制是什么?
  • Jenkins 自动触发执行的配置
  • 飞算JavaAI:重构Java开发的“人机协同”新范式
  • JavaScript VMP (Virtual Machine Protection) 分析与调试
  • 创建显示心电图的组件
  • 前端学习4:小白入门注册表单的制作(包括详细思考CSS、JS实现过程)
  • uniapp语音播报天气预报微信小程序
  • 格密码--数学基础--02基变换、幺模矩阵与 Hermite 标准形
  • 从UI设计到数字孪生实战应用:构建智慧金融的风险评估与预警平台
  • 使用 SSH 连接 GitHub
  • 飞算 JavaAI 深度体验:开启 Java 开发智能化新纪元
  • 速学 RocketMQ
  • 基于定制开发开源AI智能名片与S2B2C商城小程序的旅游日志创新应用研究
  • FPGA实现SDI转LVDS视频发送,基于GTX+OSERDES2原语架构,提供2套工程源码和技术支持
  • Maui劝退:用windows直接真机调试iOS,无须和Mac配对
  • leetcode:518. 零钱兑换 II[完全背包]
  • Python 类型注解实战:`Optional` 与安全数据处理的艺术
  • 静态路由综合实验
  • GitHub敏感信息收集与防御指南
  • 人大金仓下载安装教程总结
  • 时间显示 蓝桥云课Java
  • 安卓应用启动崩溃的问题排查记录
  • P1722 矩阵 II 题解 DFS深度优先遍历与卡特兰数(Catalan number)解
  • 【实战】使用 ELK 搭建 Spring Boot Docker 容器日志监控系统
  • 【三维生成】FlashDreamer:基于扩散模型的单目图像到3D场景
  • 力扣-54.螺旋矩阵