写点什么

IOS 技术分享| IOS 快对讲调度场景实现

作者:anyRTC开发者
  • 2023-01-04
    上海
  • 本文字数:9937 字

    阅读完需:约 33 分钟

IOS技术分享| IOS快对讲调度场景实现

前言

“快对讲” 是基于 anyRTC 音视频技术 对讲业务的产品,为客户提供专业对讲、多媒体对讲和可视化调度功能。 主要功能包含:


  • 频道与会话

  • 多频道对讲、监听、锁定、强拆

  • 音视频单人、多人呼叫、呼叫调度台

  • 图片、视频上报

  • 视频回传、监看

  • 位置回传

  • 即时消息:文字消息、语音消息、图片消息、视频消息、文件消息、位置消息

  • 文字广播、媒体广播

  • 监控、录像服务、调度台。

功能体验


场景功能实现

一、对讲

效果预览

部分代码实现
/** anyRTC 云平台对讲 频道方法。 */__attribute__((visibility("default"))) @interface ARTalkChannel: NSObject

/// ARTalkChannelDelegate 接口类向 App 发送回调通知,上报运行时的频道相关事件。@property (nonatomic, weak, nullable) id<ARTalkChannelDelegate> channelDelegate;
/// 加入频道。/// - Parameter completionBlock 同一用户只能同时加入最多 20 个频道。加入频道超限时用户会收到错误码 ARTalkJoinChannelErrorExceedLimit- (void)joinWithCompletion:(ARTalkJoinChannelBlock _Nullable)completionBlock;
/// 离开频道。/// - Parameter completionBlock ARTalkLeaveChannelBlock 回调返回本方法的调用结果。- (void)leaveWithCompletion:(ARTalkLeaveChannelBlock _Nullable)completionBlock;
/// 设置自己的对讲等级/// - Parameter level 说话等级,0为最大,level 越大等级越低- (int)setLevel:(int)level;
/// 获取自己的对讲等级/// - returns 对讲等级- (int)getLevel;
/// 设置推送音频质量/// - Parameter nQuality: 1-5 低,中,高,超高,HD,默认为1- (int)setPushAudioQuality:(int)nQuality;
/// 设置拉取音频质量,暂不可使用/// - Parameter nQuality: 1-5 低,中,高,超高,HD,默认为1- (int)setPullAudioQuality:(int)nQuality;
/// 开始对讲/// - Parameter nTalkOnTime: 对讲时长,0为无限制/// - returns 0方法调用成功,小于0方法调用失败- (int)pushToTalk:(int)nTalkOnTime;
/// 结束对讲/// - returns 0方法调用成功,小于0方法调用失败- (int)stopPushToTalk;
/// 是否接收频道其它声音/// - Parameter mute: mute true 静音,false 解除静音/// - returns 0方法调用成功,小于0方法调用失败- (int)muteAllRemoteAudio:(BOOL)mute;
/// 打断对讲/// - returns 0方法调用成功,小于0方法调用失败- (int)breakTalk;
/// 是否接收广播流/// - Parameter enable: YES 接收,NO 不接收- (int)enableAudioStream:(BOOL)enable;
/// 获取频道 ID- (NSString *)getChannelId;
@end
复制代码

二、频道与会话

效果预览

部分代码实现
class ARMainViewCellData: ARUICommonCellData {    /// 群id    var groupId: String = ""    /// 是否监听    var isMonitor: Bool = false    /// 监听状态    var monitorStatus: Bool = false    var groupName: String = ""    /// 群组类型(0:群组(频道),1:临时群组(会话))    var groupType: Int = 1    /// 在线人数    var onlineCount: NSInteger = 0    /// 群人数    var memberCount: NSInteger = 0    /// 用户在群组内权限(默认0,0:高,1:中,2:低,3:仅听)    var permission: NSInteger = 0    var isOwner: Bool = false    var item: ARCahnnelItem!    /// 对讲状态(优先级高)    var talkStatus: String?    /// 广播状态(优先级低)    var broadCastStatus: String?    /// 群组最大发言时长(单位:秒),默认:60s,0表示无限制    var groupMaxSpeak: NSInteger = 60    /// 最大排队人数(默认0:无排队,1:5人,2:10人)    var groupMaxQueue: NSInteger = 0    var groupDesc: String = ""    /// 群组级别(默认:0,0:低,1:中,2:高)    var groupGrade: NSInteger = 0    var groupImId: NSInteger = 0    /// 对讲状态    var userDataNofi: ARTalkUserDataNofi?    /// 未读数    var unReadNum: NSInteger = 0        weak var delegate: ARMainViewCellDataDelegate?    weak var dataSource: ARMainViewCellDataSource?        class func getCellData(item: ARCahnnelItem) -> ARMainViewCellData {        let cellData = ARMainViewCellData()        cellData.groupId = item.groupId        cellData.groupImId = item.groupImId        cellData.isMonitor = (item.isMonitor == 1)        cellData.groupName = item.groupName        cellData.groupType = item.groupType        cellData.onlineCount = item.onlineCount        cellData.memberCount = item.memberCount        cellData.permission = item.permission        cellData.isOwner = (item.ownerId == localUserData.uId)        cellData.groupMaxSpeak = item.groupMaxSpeak        cellData.groupMaxQueue = item.groupMaxQueue        cellData.groupDesc = item.groupDesc        cellData.groupGrade = item.groupGrade        cellData.item = item        cellData.unReadNum = ARIMMessage.queryGroupUnRead(recvId: UInt64(cellData.groupImId))        return cellData    }        func updateMonitorStatus(monitor: Bool, result: @escaping (_ code: NSInteger) -> Void) {        /// 修改监听状态        /// 频道: api 接口 + sdk    会话: sdk        debugPrint("ARUITalking - updateMonitorStatus monitor = \(monitor) monitorStatus = \(monitorStatus)")                guard monitor != monitorStatus else { return }                if groupType == 0 {            /// api 接口            ARNetWorkManager.shared.updateGroupMonitorStatus(groupId: groupId, isMonitor: monitor ? 1 : 0) {                print("ARUITalking - updateGroupMonitorStatus api sucess")            } failed: { _ in                print("ARUITalking - updateGroupMonitorStatus api failed")            }        }                if !monitor && groupId == ARTalkManager.shared.lockCellData?.groupId {            /// 解除频道锁定            ARTalkManager.shared.lockChannel(data: nil)        }                if ARTalkManager.shared.lockCellData != nil && ARTalkManager.shared.lockCellData?.groupId != groupId && monitor {            ARTalkManager.shared.getTalkChannel(channelId: groupId).muteAllRemoteAudio(true)        }                /// sdk 接口        ARTalkManager.shared.monitorChannel(channelId: groupId, grpImId: groupImId, isMonitor: monitor) { [weak self] code in            debugPrint("ARUITalking - updateMonitorStatus sdk result = \(code)")                        guard let self = self else { return }            if code == 0 {                self.item.isMonitor = monitor ? 1 : 0                self.isMonitor = monitor                self.monitorStatus = monitor                if self.delegate != nil {                    self.delegate?.onMonitorStatusChange()                }            }            result(code)        }    }}
复制代码

三、音视频通话

效果预览

部分代码实现
@interface ARUICalling : NSObject
/// 基础配置@property(nonatomic, strong) ARUIConfiguration *config;
+ (instancetype)shareInstance;
/// 通话接口/// @param users 用户信息/// @param type 呼叫类型:视频/语音- (void)call:(NSArray<ARCallUser *> *)users type:(ARUICallingType)type NS_SWIFT_NAME(call(users:type:));
/// 通话接口(用于自定义chanId或添加token)/// @param users 用户信息/// @param type 呼叫类型:视频/语音/// @param chanId 频道id/// @param token rtc token 动态密钥 【1】安全要求不高: 将值设为 nil 【2】安全要求高: 将值设置为 Token。如果你已经启用了 App Certificate,请务必使用 Token。【3】请务必确保用于生成 Token 的 App ID 和 ARUILogin initWithSdkAppID 时用的是同一个 App ID。- (void)call:(NSArray<ARCallUser *> *)users type:(ARUICallingType)type chanId:(NSString *_Nullable)chanId token:(NSString *_Nullable)token NS_SWIFT_NAME(call(users:type:chanId:token:));
/// 通话回调/// @param listener 回调- (void)setCallingListener:(id<ARUICallingListerner>)listener NS_SWIFT_NAME(setCallingListener(listener:));
/// 设置铃声,建议在30s以内,只支持本地音频文件/// @param filePath 音频文件路径- (void)setCallingBell:(NSString *)filePath NS_SWIFT_NAME(setCallingBell(filePath:));
/// 开启静音模式(默认关)- (void)enableMuteMode:(BOOL)enable NS_SWIFT_NAME(enableMuteMode(enable:));
/// 打开悬浮窗(默认关)- (void)enableFloatWindow:(BOOL)enable NS_SWIFT_NAME(enableFloatWindow(enable:));
/// 开启自定义路由(默认关)/// @param enable 打开后,在onStart回调中,会收到对应的ViewController对象,可以自行决定视图展示方式- (void)enableCustomViewRoute:(BOOL)enable NS_SWIFT_NAME(enableCustomViewRoute(enable:));
/// rtc token 鉴权 (收到onCallInvitedByToken回调时调用)/// @param token 安全机制- (void)updateCallingToken:(NSString *_Nonnull)token NS_SWIFT_NAME(updateCallingToken(token:));
@end
复制代码

四、视频回传、监看

效果预览

部分代码实现
    func initializeEngine() {        // init ARtcEngineKit        rtcEngine = ARtcEngineKit.sharedEngine(withAppId: ARUILogin.getSdkAppID(), delegate: self)        let rtcConfig = ARCoreConfig.default().rtcConfig        if (rtcConfig?.addr.count != 0 && rtcConfig?.port != 0) {            /// 配置私有云            let dic: NSDictionary = ["Cmd": "ConfPriCloudAddr", "ServerAdd": rtcConfig?.addr as Any, "Port": rtcConfig?.port as Any] as NSDictionary            rtcEngine.setParameters(toJSONString(dict: dic))        }                rtcEngine.setChannelProfile(.liveBroadcasting)        rtcEngine.enableAudioVolumeIndication(2000, smooth: 3, report_vad: true)        rtcEngine.setClientRole(.broadcaster)                let configuration = ARCameraCapturerConfiguration()        configuration.cameraDirection = .rear        rtcEngine.setCameraCapturerConfiguration(configuration)                rtcEngine.enableVideo()        let fpsValue = ARUIVideoFrameRateFps(rawValue: localConfig.videoFps)?.fps()        rtcEngine.setVideoEncoderConfiguration(ARVideoEncoderConfiguration(size: (ARUIVideoDimension(rawValue: localConfig.videoSize)?.dimension())!, frameRate: ARVideoFrameRate(rawValue: fpsValue ?? 15)!, bitrate: 500, orientationMode: .adaptative))        setLocalVideoRender(render: view)    }        func setLocalVideoRender(render: UIView) {        let videoCanvas = ARtcVideoCanvas()        videoCanvas.view = render        videoCanvas.renderMode = .hidden        rtcEngine.setupLocalVideo(videoCanvas)        rtcEngine.startPreview()    }        func joinChannel(token: String?, expire: Bool) {        rtcEngine.joinChannel(byToken: token, channelId: channelId, uid: userInfo.uId) { [weak self] channel, uid, elapsed in            guard let self = self else { return }            if !expire {                self.startTimer()                self.startReport()            }            debugPrint("ARUIPassBack - joinChannel sucess")        }    }
复制代码

五、图片、视频上报

部分代码实现
@objc protocol ARReportUploadManagerDelegate: NSObjectProtocol {    /// 上报进度    @objc optional func onReportDataProgress(progress: CGFloat, identification: String)    /// 上报成功    @objc optional func onReportDataSucess(identification: String)    /// 上报失败    @objc optional func onReportDataFailure(identification: String)}
func fetchImage(for asset: PHAsset) { let option = PHImageRequestOptions() option.resizeMode = .fast option.isNetworkAccessAllowed = true PHImageManager.default().requestImage(for: asset, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFill, options: option) { [weak self] image, info in var downloadFinished = false if let info = info { downloadFinished = !(info[PHImageCancelledKey] as? Bool ?? false) && (info[PHImageErrorKey] == nil) } let isDegraded = (info?[PHImageResultIsDegradedKey] as? Bool ?? false) if downloadFinished, !isDegraded { guard let self = self else { return } self.addNewData(images: [image!], assets: [asset]) } } } func calculateFileSize() { var fileSize = 0 var sandBoxSize = 0 for detail in reportInfo.detail { if detail.type == 2 { fileSize = detail.size sandBoxSize = detail.compressSize break } else { fileSize += detail.size sandBoxSize += detail.compressSize } } delegate?.onReportDataSourceChange?(fileSize: "\(formatLength(length: fileSize))", compressSize: "\(formatLength(length: sandBoxSize))", enable: assets.count != 0) }
复制代码

六、位置回传

部分代码实现
extension ARUIShareLocationController: MAMapViewDelegate {    func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {        /// 大头针、气泡        if annotation is MAPointAnnotation {            let customReuseIndetifier: String = "annotationReuseIndetifier"            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: customReuseIndetifier) as? ARUICustomAnnotationView            if annotationView == nil {                annotationView = ARUICustomAnnotationView(annotation: annotation, reuseIdentifier: customReuseIndetifier)                annotationView?.image = UIImage(named: "icon_direction")                // 设置为false,用以调用自定义的calloutView                annotationView?.canShowCallout = false                // 设置中心点偏移,使得标注底部中间点成为经纬度对应点                annotationView?.centerOffset = CGPoint(x: 0, y: -18)                annotationView?.isDraggable = false            }                        if annotation.isKind(of: ARUIMapAnnotation.self) {                let mapAnnotation = annotation as! ARUIMapAnnotation                annotationView?.setHeadUrl(url: mapAnnotation.faceUrl)                annotationView?.setNickName(text: mapAnnotation.nickName)            }                        if annotation.isKind(of: MAUserLocation.self) {                localAnnotationView = annotationView                localAnnotationView?.setHeadUrl(url: localUserData.faceUrl)                localAnnotationView?.setNickName(text: localUserData.nickName)                localLocation = annotation as? MAUserLocation            }                        return annotationView        }                   return nil    }        func mapView(_ mapView: MAMapView!, didUpdate userLocation: MAUserLocation!, updatingLocation: Bool) {        if (!updatingLocation && localAnnotationView != nil) {            UIView.animate(withDuration: 0.1) {                let degree = userLocation.heading.trueHeading - self.mapView.rotationDegree                self.localAnnotationView?.imageView.transform = CGAffineTransform(rotationAngle: degree * Double.pi / 180.0)            }        }    }}
复制代码

七、即时消息

部分代码实现
/// 消息内容类型enum ARIMElemType: NSInteger, Codable, HandyJSONEnum {    /// 未知消息    case none = 100    /// 文本消息    case text = 101    /// 图片消息    case picture = 102    /// 语音消息    case voice = 103    /// 视频消息    case video = 104    /// ptt 语音    case pushtotalk = 105    /// 文件消息    case file = 106    case atText = 107    /// 合并消息    case merger = 108    case card = 109    /// 地理位置消息    case location = 110    /// 自定义消息    case custom = 111    case revoke = 112    case hasReadReceipt = 113    case typing = 114    case quote = 115    case common = 200    case groupMsg = 201}

/// 消息状态enum ARIMMessageStatus: NSInteger, Codable, HandyJSONEnum { case sending = 1 case sendSuccess = 2 case sendFailed = 3 case hasDeleted = 4 case revoked = 5 case filtered = 6}
/// IM消息///"sendId":3578970541,"recvId":2723541307,"clientMsgId":"1653488317610","sessionType":1,"contentType":101,"content":"IM消息","curSeq":1011,"sendTime":1653488317,"status":2struct ARIMMessage: HandyJSON, PersistableRecord { /// 序列号 var curSeq: UInt64! /// 消息 Id var clientMsgId: String! /// 发送者 Id var sendId: UInt64! /// 接受者 id var recvId: UInt64! /// 消息类型(点对点消息:1,群组消息:2) var sessionType: ARIMSessionType = .c2c /// 消息内容类型,附加信息中的<消息内容类型> var contentType: ARIMElemType = .none /// 消息内容 var content: String = "" /// 消息状态 var status: ARIMMessageStatus? /// 发送时间(单位:毫秒) var sendTime: Int? /// 发送者平台类型 var senderPlatformId: ARIMPlatform? /// 附加消息 var ex: String? var syncMsgId: String = "" /// 消息保存时间(单位:秒) var createTime: Int = 0 /// 消息归属Id var msgImId: Int = 0 /// 已读状态,默认未读 var isRead: Bool = false /// 是否播放(针对语音、ptt 已读未读),默认未播放 var isPlay: Bool = false ///////////////////////////////////////////////////////////////////////////////// // 自定义消息体 /////////////////////////////////////////////////////////////////////////////////
/// 图片消息 var imageElem: ARUIImageElem? /// 视频消息 var videoElem: ARUIVideoElem? /// 语音消息 var soundElem: ARUISoundElem? /// 文件消息 var fileElem: ARUIFileElem? /// 位置消息 var locationElem: ARUILocationElem?}
extension ARIMMessage { func getImageElem() -> ARUIImageElem { /// 获取图片消息单元 var elem = ARUIImageElem() var jsonStr = content.base64Decoded() /// 兼容之前未base64的消息 if content.base64Decoded() == "【不支持的消息类型】" { jsonStr = content } elem.imageList = JSONDeserializer<ARUIImageItem>.deserializeModelArrayFrom(json: jsonStr) as? [ARUIImageItem] return elem } func getVideoElem() -> ARUIVideoElem { /// 获取视频消息单元 let elem = JSONDeserializer<ARUIVideoElem>.deserializeFrom(json: content.base64Decoded()) return elem! } func getSoundElem() -> ARUISoundElem { /// 获取音频消息单元 let elem = JSONDeserializer<ARUISoundElem>.deserializeFrom(json: content.base64Decoded()) return elem! } func getFileElem() -> ARUIFileElem { /// 获取文件消息单元 let elem = JSONDeserializer<ARUIFileElem>.deserializeFrom(json: content.base64Decoded()) return elem! } func getLocationElem() -> ARUILocationElem { /// 获取位置消息单元 let elem = JSONDeserializer<ARUILocationElem>.deserializeFrom(json: content.base64Decoded()) return elem! }}
复制代码

八、文字广播、媒体广播

部分代码实现
@protocol ARTalkChannelDelegate <NSObject>@optional
/// 广播开启/// @param channel 所在频道。详见 ARTalkChannel 。/// @param uid 用户id/// @param userData 自定义信息- (void)channel:(ARTalkChannel * _Nonnull)channel userStreamOn:(NSString *)uid userData: (NSString * _Nullable)userData;
/// 广播关闭/// @param channel 所在频道。详见 ARTalkChannel 。/// @param uid 用户id/// @param userData 自定义信息- (void)channel:(ARTalkChannel * _Nonnull)channel userStreamOff:(NSString *)uid userData: (NSString * _Nullable)userData;
/// 开始对讲回调/// @param channel 所在频道。详见 ARTalkChannel 。/// @param code 错误码- (void)channel:(ARTalkChannel * _Nonnull)channel pushToTalkResult:(ARTalkPushToTalkErrorCode)code;
/// 结束对讲回调/// @param channel 所在频道。详见 ARTalkChannel 。/// @param code 错误码- (void)channel:(ARTalkChannel * _Nonnull)channel pushToTalkEnded:(ARTalkPushToTalkEndErrorCode)code;
/// 其他用户开始对讲回调/// @param channel 所在频道。详见 ARTalkChannel 。/// @param uid 用户id/// @param userData 自定义信息/// @param level 用户等级- (void)channel:(ARTalkChannel * _Nonnull)channel userIsTalkOn:(NSString *)uid userData: (NSString * _Nullable)userData userLevel:(NSInteger)level;
/// 其他用户结束对讲回调/// @param channel 所在频道。详见 ARTalkChannel 。/// @param uid 用户id/// @param userData 自定义信息- (void)channel:(ARTalkChannel * _Nonnull)channel userIsTalkOff:(NSString *)uid userData: (NSString * _Nullable)userData;
@end
复制代码

快对讲 iOS 端基础库

platform :ios, '11.0'use_frameworks!
target 'ARUITalking' do # anyRTC 音视频库 pod 'ARtcKit_iOS', '~> 4.3.0.3' # anyRTC 实时消息库 pod 'ARtmKit_iOS', '~> 1.1.0.1' # anyRTC 对讲库 pod 'ARTalkKit_iOS'end
复制代码


以上就是快对讲 IOS 端的基本功能实现。快对讲调度系统将语音、视频、图像、文本消息等信息高度融合一体,搭建综合指挥调度业务,不仅实现企业通讯数字信息化,进行高效协作提升企业整体形象,也能满足紧急救援、紧急决策等要求,达到统一指挥、联合行动的目的。开发者可以基于 ARUICalling 音视频通话开源组件,来开发自己的专属快对讲调度系统。



发布于: 刚刚阅读数: 4
用户头像

实时交互,万物互联! 2020-08-10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
IOS技术分享| IOS快对讲调度场景实现_音视频_anyRTC开发者_InfoQ写作社区