You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
游戏房
场景搭建(iOS)
复制全文
下载 pdf
场景搭建(iOS)

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:
最近更新时间:2025.10.16 10:06:06
这个页面对您有帮助吗?
有用
有用
无用
无用