iOS

# 实现插件特性

本文展示如何实现菊风视频能力平台提供的插件功能。

# 前提条件

开始前,请确保你具备以下条件:

  • 已下载插件包
  • 已集成 Juphoon Plugin。目前只有该版本支持插件集成。
  • 插件 API说明在页面底部,请查看。

# 1. 定制化配置

/**
 * @brief 设置定制配置参数
 * @param customConfig 定制化配置参数
 */
- (void)setCustomConfig:(JCCGuestCustomConfig *)customConfig;

JCCGuestCustomConfig (opens new window) 内部根据不同界面进行分类区分

/// 定制化配置参数
/// 初始化JCCGuestCustomConfig,内部会对JCCTalkingConfig也做初始化
@interface JCCGuestCustomConfig : NSObject
/// 呼叫页面配置
@property (nonatomic, strong) JCCWaitingConfig *waitingConfig;
/// 来电页面配置
@property (nonatomic, strong) JCCIncomingConfig *incomingConfig;
/// 聊天页面配置
@property (nonatomic, strong) JCCTalkingConfig *talkingConfig;

@end

# 呼叫页面配置

@interface JCCWaitingConfig : NSObject

/// 呼叫时,需要播放的音频文件路径,不传默认不播放音乐
@property (nonatomic, copy) NSString *waitingAudioPath;

// 设置tipTitle和tipContent会出现跑马灯提示
/// 跑马灯标题
@property (nonatomic, copy) NSString *tipTitle;
/// 跑马灯内容
@property (nonatomic, copy) NSString *tipContent;

@end

# 来电页面配置

/// 回呼场景:来电定制化配置参数
@interface JCCIncomingConfig : NSObject
/// 来电时,需要播放的音频文件路径,不传默认不播放音乐
@property (nonatomic, copy) NSString *incomingAudioPath;
/// 接听超时时长,默认60秒,以秒为单位
@property (nonatomic, assign) NSUInteger answerTimeout;
/// 是否使用自定义来电页面,默认为NO
@property (nonatomic, assign) BOOL useCustomIncomming;

@end

# 通话页面配置

// 底部工具栏按钮枚举定义
typedef NS_ENUM(NSUInteger, JCCToolBarButtons) {
    JCCEndButton = 1,          // 挂断按钮
    JCCSpeakerButton,          // 扬声器按钮
    JCCMicButton,              // 麦克风按钮
    JCCCameraButton,           // 摄像头按钮
    JCCFlipButton,             // 翻转按钮
    JCCScreenShareButton,      // 屏幕共享按钮
    JCCLocalRecordButton,      // 本地录制按钮
    JCCChatButton,             // 消息按钮
    JCCCollaborationButton,    // 协作按钮
    JCCRemoteRecordButton      // 远程录制按钮
};


@interface JCCTalkingConfig : NSObject

/// 颜色格式为8位16进制ARGB,例如0xFFFFFFFF

/// 顶部栏背景色
@property (nonatomic, assign) NSUInteger topBarBackground;
/// 标题内容
@property (nonatomic, copy) NSString *title;
/// 工具栏背景色
@property (nonatomic, assign) NSUInteger toolBarBackground;
/// JCToolbarButtons配置数组
@property (nonatomic, strong) NSArray *toolBarButtons;
/// 自定义图层,需实现IBaseView协议,用来接收生命周期等事件,尺寸为全屏幕大小
@property (nonatomic, strong) UIView<JCCIBaseView> *customView;
/// 是否显示最小化按钮,默认为NO
@property (nonatomic, assign) BOOL showMinimized;
/// 是否显示统计按钮,默认为NO
@property (nonatomic, assign) BOOL showStatistic;
/// 是否使用第三方排队机,如果为NO,则使用插件中排队机,默认为NO
@property (nonatomic, assign) BOOL useThirdACD;
/// 是否显示电子签名栏取消按钮,默认为NO
@property (nonatomic, assign) BOOL showCancelSign;
/// 电子签名画笔宽度,默认为8
@property (nonatomic, assign) NSUInteger defaultDoodleStrokeWidth;
/// 签字签名是否全屏显示
@property (nonatomic, assign) BOOL signFullScreen;
/// 通话保持中时,需要播放的音频文件路径
@property (nonatomic, copy) NSString *heldAudioPath;
/// 转接时,需要播放的音频文件路径
@property (nonatomic, copy) NSString *transferAudioPath;
/// 是否显示共享的画面,默认为YES
@property (nonatomic, assign) BOOL showShareCanvas;
/// 协作共享远端路径颜色
@property (nonatomic, assign) NSUInteger collaborationRemotePathColor;
/// 协作共享自己端路径颜色
@property (nonatomic, assign) NSUInteger collaborationSelfPathColor;
/// 通话中关闭摄像头视频背景颜色,如果不使用背景颜色则设置为-1,默认设置为-1
@property (nonatomic, assign) NSInteger videoHoldBackgroundColor;
/// 通话中关闭摄像头小视频图标
@property (nonatomic, strong) UIImage *videoHoldSmallIcon;
/// 通话中关闭摄像头大视频图标
@property (nonatomic, strong) UIImage *videoHoldLargeIcon;
/// 全显示功能布局
@property (nonatomic, assign) BOOL fullDisplayFunctionLayout;

@end
// IBaseView定义
@protocol IBaseView <NSObject>

- (void)viewDidLoad;

- (void)viewWillAppear:(BOOL)animated;

- (void)viewDidAppear:(BOOL)animated;

- (void)viewWillDisappear:(BOOL)animated;

- (void)viewDidDisappear:(BOOL)animated;

@end

# 示例代码

JCCGuestCustomConfig *customConfig = [[JCCGuestCustomConfig alloc] init];
customConfig.talkingConfig.toolBarButtons = @[@(JCCEndButton), @(JCCSpeakerButton)];
...
[JCCGuestManager.shared setCustomConfig:customConfig];

# 2. 在通话页面中添加自定义视图

自定义图层需要实现IBaseView协议

@interface TestCustomView : UIView<IBaseView>

@end

@implementation TestCustomView

- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"viewDidAppear");
}

- (void)viewDidDisappear:(BOOL)animated {
    NSLog(@"viewDidDisappear");
}

- (void)viewDidLoad {
    NSLog(@"viewDidLoad");
}

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"viewWillAppear");
}

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"viewWillDisappear");
}

示例代码:

///自定义图层尺寸设置为全屏大小
TestCustomView *customView = [[TestCustomView alloc] initWithFrame:bounds];
JCCManager.defaultManager.customConfig.talkingConfig.customView = customView;

# 3. 在通话过程中弹窗

可以通过 ViewController 、Window 以及插件提供的自定义视图实现弹窗。

# ViewController弹出方式

获取最顶层 ViewController ,通过顶层 ViewController present 弹窗 Controller

- (UIViewController *)topViewController {
    UIViewController *resultVC;
    resultVC = [self _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
    while (resultVC.presentedViewController) {
        resultVC = [self _topViewController:resultVC.presentedViewController];
    }
    return resultVC;
}

- (UIViewController *)_topViewController:(UIViewController *)vc {
    if ([vc isKindOfClass:[UINavigationController class]]) {
        return [self _topViewController:[(UINavigationController *)vc topViewController]];
    } else if ([vc isKindOfClass:[UITabBarController class]]) {
        return [self _topViewController:[(UITabBarController *)vc selectedViewController]];
    } else {
        return vc;
    }
    return nil;
}
TestViewController *controller = [TestViewController alloc] init];
[topViewController presentViewController:controller animated:YES completion:nil];

# Window方式

创建一个 Window,通过 Window 弹出弹窗。需要保证 Window 的生命周期。可以设置 Window 为单例或者让某个类持有 Window ,在使用结束再销毁 Window 。

UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
window.rootViewController = [[UIViewController alloc] init];
window.windowLevel = UIWindowLevelNormal + 1;
window.hidden = NO;

TestViewController *controller = [TestViewController alloc] init];
[window.rootViewController presentViewController:controller animated:YES completion:nil];

如果实现了iOS 13的 **UIWindowSceneDelegate ,**需要将 Window 添加到 windowScene 中

if (@available(iOS 13.0, *)) {
    for (UIWindowScene *windowScene in [UIApplication sharedApplication].connectedScenes) {
        if (windowScene.activationState == UISceneActivationStateForegroundActive) {
            window.windowScene = windowScene;
            break;
        }
    }
}

# 自定义视图方式

在自定义视图中添加弹窗视图,需要显示的时候弹出,其他时候隐藏。

JCCGuestCustomConfig *customConfig = [[JCCGuestCustomConfig alloc] init];
// TestCustomView 实现了IBaseView协议
TestCustomView *customView = [[TestCustomView alloc] initWithFrame:self.view.bounds];
customConfig.talkingConfig.customView = customView;
[JCCGuestManager.shared setCustomConfig:customConfig];

# 4. 对接第三方排队机

使用第三方的排队机,需要在配置项中进行设置,屏蔽掉插件中提供的排队机。

# 定制化配置中设置使用第三方排队机

JCCGuestCustomConfig *customConfig = [[JCCGuestCustomConfig alloc] init];
customConfig.talkingConfig.useThirdACD = YES;
[JCCGuestManager.shared setCustomConfig:customConfig];

# 监听通话状态回调

呼叫状态时,调用第三方排队机界面

通话接通时,判断是否使用了第三方排队机,如果使用了第三方排队机,先隐藏第三方排队机界面,再调用showTalkingView 方法。

通话结束的时候,关闭第三方排队机界面。

JCCGuestCallState (opens new window) 判断通话状态。

- (void)onCallStateChanged:(JCCGuestCallState)state inviter:(JCCInviter *)inviter termReason:(JCCTermReason)reason
{
    self.callState = state;
    // 呼叫状态时
    if (state == JCCGuestCallStateCalling) {
        // 判断是否使用了第三方排队机
        if (self.customConfig.talkingConfig.useThirdACD) {
            self.testACDController = [[TestThirdACDViewController alloc] init];
            self.testACDController.modalPresentationStyle = UIModalPresentationOverFullScreen;
            [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:self.testACDController animated:YES completion:nil];
        }
    }
    // 通话接通时
    if (state == JCCGuestCallStateTalking) {
        // 判断是否使用了第三方排队机
        if (self.customConfig.talkingConfig.useThirdACD) {
            // 先隐藏第三方排队机界面,再调用showTalkingView,显示通话界面
            [self.testACDController dismissViewControllerAnimated:NO completion:^{
                self.testACDController = nil;
                [JCCGuestManager.shared showTalkingView:NO];
            }];
        }
    } 
    // 通话挂断时
    else if (state == JCCGuestCallStateTermed) {
        [self.testACDController dismissViewControllerAnimated:NO completion:^{
            self.testACDController = nil;
        }];
    }
}

# 5. 使用签名

主动调用签名,签名图片会保存在设备中,签名成功,会将签名结果路径返回。

# 调起签名页

调用 showSignView (opens new window) 方法,主动调用签名。

// 调起签名页
[JCCGuestManager.shared showSignView];

# 获得签名结果

通过 onSignRequestComplete (opens new window) 接收签名结果。

// 签名结果回调
- (void)onSignRequestComplete:(BOOL)result filePath:(NSString *)filePath {
    if (result) {
        NSLog(@"签名成功, 签名存放路径 %@", filePath);
    } else {
        NSLog(@"签名失败");
    }
}

# 6. 开启国密

# 参数配置

开启国密需要在初始化的时候设置 JCCLoginParam (opens new window) 的 accountEntry 和 certificate 参数。

参数需要根据实际情况填写。

JCCLoginParam *config = [[JCCLoginParam alloc] init];

// 设置账户分录,测试环境accountEntry值
config.accountEntry = @"AccountEntry:sarc -h arc@AccountEntry -p 198 -S 3;";
// 设置证书路径,测试证书
NSString *s1 = [[NSBundle mainBundle] pathForResource:@"CA" ofType:@"der"];
NSData *data1 = [[NSFileManager defaultManager] contentsAtPath:s1];
NSString *ca = [data1 base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
config.certificate = ca;

config.appKey = @"appkey";
config.server = @"server"
config.userName = @"userName"

[JCCGuestManager.shared login:config];

# 加载国密库

加载jussl.framework库。

img

# 7. 开启云加速

  1. 从管理平台申请加速云接入,并获得加速云接入地址,接口 key 和接入 key 密钥
  2. 登录参数中需要将上述获得的参数填入

接入地址说明 (opens new window)

JCCLoginParam *config = [[JCCLoginParam alloc] init];
...
config.server = @"加速云接入地址";
config.accelerateKey = @"加速云Key";
config.accelerateKeySecret = @"加速云Key密钥";
...
BOOL ret = [JCCGuestManager.shared login:config];

# 8. 屏幕共享

屏幕共享可以让您和频道中的其他成员一起分享设备里的精彩内容,您可以在频道中利用屏幕共享的功能进行文档演示、在线教育演示、视频会议以及游戏过程分享等。 通过ReplayKit 2的系统库,实现屏幕共享,该库仅支持iOS 11.0 以上的系统进行共享系统的屏幕,且支持屏幕外共享

# 添加 Broadcast Upload Extension扩展

1、项目中选择target,添加 extension ,用于屏幕录制。

img

img

如上所示,在添加 extension 时选中 Broadcast Upload Extension 扩展进行添加。

2、app 和 extension 加入同一个 app group 后,实现 extension 和宿主 app 间的数据共享,加入步骤如下图所示。 (opens new window)

img

img

3、然后打开工程目录下对应的 SampleHandler.h 和 SampleHandler.m 文件,导入库 #import <ReplayKit/ReplayKit.h>,继承系统的 RPBroadcastSampleHandler 类,重写broadcastStartedWithSetupInfo、processSampleBuffer、broadcastFinished方法,并且调用调用插件提供的方法,如下图

img

img

img

# 配置屏幕共享参数

在登录的时候传入 shareGroupId 和 shareExtension

- (BOOL)login:(JCCLoginParam *)config {
    config.shareGroupId = @"groupId";
    config.shareExtension = @"shareExtension";
    BOOL ret = [JCCGuestManager.shared login:config];
    return ret;
}

完成以上配置后,即可使用插件屏幕共享功能。

# 9. 同屏共享

# 开启同屏共享

# imgimg

点击工具栏中的同屏共享按钮,在确认框中点击允许,开启同屏共享请求。

# 同屏共享录屏

img

在远端成员同意请求后,会弹出录屏的请求框,允许录屏后就开始了同屏共享。

# 同屏共享工具

左上方的工具栏可以停止共享或者暂停共享。右边有个涂鸦按钮,点击可以开启涂鸦操作。

img

# 同屏共享接口调用

暂停恢复共享

/**
 * 暂停/继续屏幕共享
 * @note
 * 只有自己发起的屏幕共享可以使用该接口暂停,多次调用会覆盖
 * @param suspend true 暂停屏幕共享, false 继续屏幕共享
 * @param tip 暂停屏幕共享后提示文字
 * @return 接口调用结果
 * - true: 接口调用成功, 会收到 {@link JRTCKitGuestCallback#onRoomPropertyChanged onRoomPropertyChanged} 回调,可通过{@link #isSuspendScreenShare isSuspendScreenShare} 判断当前屏幕共享是否暂停
 * - false: 接口调用异常
 */
- (bool)suspendScreenShare:(bool)suspend tip:(NSString *_Nonnull)tip;

查询当前是否在共享中

/**
 * 是否共享暂停
 * @return
 * - true: 暂停屏幕共享
 * - false: 未暂停屏幕共享
 */
- (bool)isSuspendScreenShare;

# Token 校验

token 认证服务,主要用于登录时 token 验证,由第三方服务获取 token,将 token 下发给集成的终端,由 SDK 发起登录时带上 token ,进行认证。详见 token 流程介绍 (opens new window)

允许用户登录时,带入 token 。如果未使用,可以不带。

/**
 * @brief 初始化并登录,登录结果通过 {@link JCCCommonCallback#onLogin:reason: onLogin} 回调通知
 * @param param 初始化和登录的基本配置信息
 * @return 接口调用结果
 * - YES 表示调用成功
 * - NO 表示调用异常
 */
- (BOOL)login:(JCCLoginParam *)param;

示例代码:

JCCLoginParam *loginParam = [JCCLoginParam new];
param.token = @"token字符串";
[JCCGuestManager.shared login:param];