You need to enable JavaScript to run this app.
导航

场景搭建(iOS)

最近更新时间2023.12.13 16:13:03

首次发布时间2022.09.06 15:39:08

SDK集成

本场景需要集成火山引擎的RTC SDK 以及SUD MGP 互动小游戏接入平台,您需要在 RTC 的控制台开通服务,并下载对应的SUD MGP 的SDK,相应开通指南如下:

RTC SDK 接入

详细细节请参见 RTC服务开通指南

alt

小游戏 SDK 接入

整体实现流程

alt


核心功能实现

房主创建游戏房与观众加入游戏房

时序图

alt

示例代码

/**
 * Join the RTC room and initialize the parameters
 * @param token: RTC Token
 * @param roomID: RTC room id
 * @param uid: RTC user id
 **/
- (void)joinChannelWithToken:(NSString *)token roomID:(NSString *)roomID uid:(NSString *)uid {
    //设置订阅的音视频流回退选项
    [self.rtcEngineKit setSubscribeFallbackOption:ByteRTCSubscribeFallbackOptionAudioOnly];
 
    //关闭 本地音频/视频采集
    [self.rtcEngineKit stopAudioCapture];
    [self.rtcEngineKit stopVideoCapture];
 
    //设置音频路由模式,YES 扬声器/NO 听筒
    [self.rtcEngineKit setDefaultAudioRoute:ByteRTCAudioRouteSpeakerphone];
 
    //开启/关闭发言者音量键控
    ByteRTCAudioPropertiesConfig *audioPropertiesConfig = [[ByteRTCAudioPropertiesConfig alloc] init];
    audioPropertiesConfig.interval = 200;
    [self.rtcEngineKit enableAudioPropertiesReport:audioPropertiesConfig];
    
 
    //加入房间,开始连麦,需要申请AppId和Token
    ByteRTCUserInfo *userInfo = [[ByteRTCUserInfo alloc] init];
    userInfo.userId = uid;
    
    ByteRTCRoomConfig *config = [[ByteRTCRoomConfig alloc] init];
    config.profile = ByteRTCRoomProfileCommunication;
    config.isAutoPublish = YES;
    config.isAutoSubscribeAudio = YES;
    
    self.rtcRoom = [self.rtcEngineKit createRTCRoom:roomID];
    self.rtcRoom.delegate = self;
    [self.rtcRoom joinRoom:token userInfo:userInfo roomConfig:config];
    
    //设置用户为隐身状态
    [self.rtcRoom setUserVisibility:NO];
}

/**
 * 初始化游戏SDK
 * @param appId: 游戏SDK所需的appId
 * @param appKey: 游戏SDK所需的appKey
 * @param callback: 初始化游戏SDK回调
 **/
- (void)initSudMGPSDKWithAppId:(NSString *)appId appKey:(NSString *)appKey callback:(void(^)(BOOL success))resultCallback {
    if (self.isInitSDK) {
        if (resultCallback) {
            resultCallback(YES);
        }
        return;
    }
    
    __weak __typeof(self) wself = self;
    [SudMGP initSDK:appId appKey:appKey isTestEnv:YES listener:^(int retCode, const NSString * _Nonnull retMsg) {
        wself.isInitSDK = retCode == 0;
        if (resultCallback) {
            resultCallback(wself.isInitSDK);
        }
    }];
}

/**
 * 小游戏游戏界面加载
 **/
- (void)initSudMGP {
    __weak __typeof(self) wself = self;
    [[GameSudMGPManager shareManager] requestSudMGPCode:NO resultCallback:^(NSString * _Nullable code) {
        if (!code) {
            [[ToastComponent shareToastComponent] showWithMessage:@"登录小游戏失败" view:self.view];
            return;
        }
        
        [wself.iSudAPP destroyMG];
        wself.iSudAPP = nil;
        wself.iSudAPP = [SudMGP loadMG:[LocalUserComponent userModel].uid roomId:self.roomModel.room_id code:[GameSudMGPManager shareManager].sudMGPCode mgId:self.gameId language:@"zh-CN" fsmMG:self rootView:self.gameRootView];
    }];
}

/**
 * 游戏游戏界面UI适配
 * @param handle 回调句柄,APP接入方需要调用handle.success或handle.fail
 * @param dataJson {}
 */
-(void)onGetGameViewInfo:(id<ISudFSMStateHandle>)handle dataJson:(NSString*)dataJson {
    CGRect rect = self.gameRootView.frame;
    CGFloat scale = [[UIScreen mainScreen] nativeScale];

    CGFloat height = rect.size.height;
    CGFloat width = 1.0 / 1.4 * height;         //游戏区域宽高比建议1:1.4
    width = MIN(width, rect.size.width);

    CGFloat left = 0.5 * (rect.size.width - width) * scale;
    NSDictionary *rectDict = [NSDictionary dictionaryWithObjectsAndKeys:@(0), @"top", @(left), @"left", @(0), @"bottom", @(left), @"right", nil];
    NSDictionary *viewDict = [NSDictionary dictionaryWithObjectsAndKeys:@(rect.size.width * scale), @"width", @(rect.size.height * scale), @"height", nil];
    NSDictionary *dataDict = [NSDictionary dictionaryWithObjectsAndKeys:@(0), @"ret_code", @"return form APP onGetGameViewInfo", @"ret_msg", viewDict, @"view_size", rectDict, @"view_game_rect", nil];
    /// 回调
    [handle success:[GameUtils dictionaryToJson:dataDict]];
}

观众请求上麦与房主拉观众上麦

观众请求上麦时序图

alt

房主拉观众上麦时序图

alt

示例代码

/**
 * tab按钮状态变化回调
 * @param itemButton: 点击的button
 * @param status: button状态
 **/
- (void)gameRoomBottomView:(GameRoomBottomView *_Nonnull)gameRoomBottomView
                itemButton:(GameRoomItemButton *_Nullable)itemButton
           didSelectStatus:(GameRoomBottomStatus)status {
    if (status == GameRoomBottomStatusList ||
        status == GameRoomBottomStatusListRed) {
        __weak __typeof(self) wself = self;
        [self.userListComponents show:^{
            [wself restoreBottomViewMenuStatus];
        }];
    } else if (status == GameRoomBottomStatusRaiseHand) {     //点击举手
        __weak __typeof(self) wself = self;
        [GameRTSManager raiseHandsMicWithBlock:^(RTMACKModel * _Nonnull model) {
            if (model.result) {
                [[ToastComponent shareToastComponent] showWithMessage:@"上麦请求已发出,请耐心等待"];
 
                [wself.bottomView updateButtonStatus:GameRoomBottomStatusRaiseHand close:YES];
                [wself checkMicrophoneSystemAuthority];
            }else {
                [[ToastComponent shareToastComponent] showWithMessage:@"操作失败,请重试"];
            }
        }];
    } else if (status == GameRoomBottomStatusMic) {
        [SystemAuthority authorizationStatusWithType:AuthorizationTypeAudio block:^(BOOL isAuthorize) {
            if (itemButton.status == ButtonStatusNone) {
                //mute
                [GameRTSManager muteMic];
                [[GameRTCManager shareRtc] muteLocalAudioStream:YES];
            } else {
                //unmute
                [GameRTSManager unmuteMic];
                [[GameRTCManager shareRtc] muteLocalAudioStream:NO];
            }
            itemButton.status = itemButton.status == ButtonStatusNone ? ButtonStatusActive : ButtonStatusNone;
        }];
    } else if (status == GameRoomBottomStatusVolume) {
        [self.volumeComponents show];
    } else if (status == GameRoomBottomStatusData) {
        [self.paramComponents show];
    } else if (status == GameRoomBottomStatusDownHand) {    //点击下麦
        AlertActionModel *alertModel = [[AlertActionModel alloc] init];
        alertModel.title = @"确定";
        AlertActionModel *cancelModel = [[AlertActionModel alloc] init];
        cancelModel.title = @"取消";
        __weak __typeof(self) wself = self;
        [[AlertActionManager shareAlertActionManager] showWithMessage:@"是否确认下麦?" actions:@[cancelModel, alertModel]];
        alertModel.alertModelClickBlock = ^(UIAlertAction * _Nonnull action) {
            if ([action.title isEqualToString:@"确定"]) {
                [GameRTSManager offSelfMicWithBlock:^(RTMACKModel * _Nonnull model) {
                    if (model.result) {
                        [wself sendDownloadHand];
                    }else {
                        [[ToastComponent shareToastComponent] showWithMessage:@"操作失败,请重试"];
                    }
                }];
            }
        };
    } else {
        
    }
}

/**
 * 房主对观众和嘉宾操作
 * @param userModel: 选中user的model
 * @param dataLists: 对应的用户列表
 **/
- (void)updateTableViewWithModel:(GameControlUserModel *)userModel dataLists:(NSArray<GameControlUserModel *> *)dataLists {
    if (userModel.user_status == 0) {
        // 邀请上麦
        [GameRTSManager inviteMic:userModel.user_id block:^(RTMACKModel * _Nonnull model) {
            if (!model.result) {
                [[ToastComponent shareToastComponent] showWithMessage:@"操作失败,请重试"];
            }
        }];
    } else if (userModel.user_status == 1) {
        // 举手 - 同意
        __weak __typeof(self)wself = self;
        [GameRTSManager agreeMic:userModel.user_id block:^(RTMACKModel * _Nonnull model) {
            if (model.result) {
                userModel.user_status = 2;
                [wself updateDataLists:dataLists model:userModel];
            } else {
                [[ToastComponent shareToastComponent] showWithMessage:@"操作失败,请重试"];
            }
        }];
    } else if (userModel.user_status == 2) {
        // 上麦 - 下麦
        AlertActionModel *alertModel = [[AlertActionModel alloc] init];
        alertModel.title = @"确定";
        AlertActionModel *cancelModel = [[AlertActionModel alloc] init];
        cancelModel.title = @"取消";
        [[AlertActionManager shareAlertActionManager] showWithMessage:@"是否将该用户下麦?" actions:@[cancelModel, alertModel]];
        __weak __typeof(self) wself = self;
        alertModel.alertModelClickBlock = ^(UIAlertAction * _Nonnull action) {
            if ([action.title isEqualToString:@"确定"]) {
                [GameRTSManager offMic:userModel.user_id block:^(RTMACKModel * _Nonnull model) {
                    if (model.result) {
                        [wself removeDataLists:dataLists model:userModel];
                    } else {
                        [[ToastComponent shareToastComponent] showWithMessage:@"操作失败,请重试"];
                    }
                }];
            }
        };
    }
}

/**
 * 房间内观众上麦通知
 * @param userModel: 对应user的model
 **/
- (void)receivedRaiseHandSucceedWithUser:(GameControlUserModel *)userModel {
    [self.roomView audienceRaisedHandsSuccess:userModel];
    [self.userListComponents update];
    if ([userModel.user_id isEqualToString:[LocalUserComponent userModel].uid]) {
        GameControlUserModel *localUser = [self currentLoginuserModel];
        localUser.user_status = 2;
        localUser.is_mic_on = YES;
        [self.bottomView updateBottomLists:[self getBottomListsWithModel:localUser]];
        [[ToastComponent shareToastComponent] showWithMessage:@"您已成功上麦"];
        [[GameRTCManager shareRtc] makeCoHost:YES];
        [[GameRTCManager shareRtc] muteLocalAudioStream:NO];
        [self checkMicrophoneSystemAuthority];
    }
}

核心功能 API 与回调参考

API

功能点API
创建 RTCVideo 对象createRTCVideo:delegate:parameters:
创建 RTCRoom 对象createRTCRoom:
设置用户可见性setUserVisibility:
开启本地音频采集startAudioCapture
加入RTC房间joinRoom:userInfo:roomConfig:
离开房间leaveRoom
关闭内部音频采集stopAudioCapture
销毁 RTCRoom 对象destroy

回调

功能点回调
本地用户加入 RTC 房间回调rtcRoom:onRoomStateChanged:uid:state:extraInfo
远端可见用户加入房间rtcRoom:onUserJoined:elapsed: