互动服务开发指南 微吼云

服务介绍    

基本描述

互动直播,即多路音视频互动解决方案,提供多路音视频实时互动服务,开发者通过接入互动直播SDK,可构建1对1、1对多的音视频通信服务,帮助用户实现主播与观众多路画面、实时连麦互动的直播场景。

不同于直播的单向性,观众被赋予了参与直播并进行互动的权利,提高了直播的用户体验,适用于娱乐直播、互动教学、会议直播等场景。

服务架构

主要功能

类别 功能名称 详细内容
直播功能 美颜功能 iOS和Android的主播端都支持多种实时美颜滤镜效果
屏幕共享 PC端支持共享自己屏幕进行直播
纯音频直播 iOS、Android和PC的主播端都支持纯音频直播
录制 支持旁路直播混流视频的录制
鉴黄 支持视频内容鉴黄
基础通信服务 基础通信服务 1,房间中操作消息;2,聊天消息;3,其他自定义消息
房间管理 管理方式 支持通过控制台和接口的方式对互动房间进行管理
控制台管理 支持对互动房间进行查询、开启、关闭、预览旁路直播画面操作
接口管理 支持对互动房间进行创建、查询、开启、关闭,以及对旁路直播进行开启/关闭等管理操作
数据统计 查询方式 支持通过控制台和接口的方式进行数据查询
控制台查询 支持对在线时长、峰值带宽、同时在线人数进行统计,颗粒度为1个小时
接口查询 支持获取互动流量数据和互动被踢出人列表
互动直播 多人互动 支持不超过8路语音、视频连麦互动并直播出去,1对1、多对多互动
连麦UI布局 支持UI布局模板
旁路直播 多路混流的视频流在服务端推送到直播服务中
支持终端 支持iOS、Android、JS SDK

服务优势

连麦互动

支持主播和观众实时连麦互动;提供多种连麦者UI布局模板;支持美颜滤镜

完善的消息服务

支持房间中操作消息、聊天消息、自定义消息等多种消息类型

服务端录制

支持混流视频的录制,对接点播平台,录制文件可进行转码、下载、预览观看等操作

旁路直播

提供旁路直播功能,多路音视频云端自动混流,替代终端处理,降低带宽成本,减轻终端负载

快速开始    

使用文档前准备

1 前提条件

  • 互动工具

    实现互动直播功能需要具互动工具,微吼云提供互动直播SDK及体验Demo,可以通过Demo进行互动直播

    通过微吼云提供的下载页,下载安装互动直播SDK及Demo。

2 操作步骤

2.1 登录

登录微吼云控制台

2.2 创建应用

详情参考如何创建应用

2.3 获取app_id

详情参考如何获取app_id

2.4 设置包名&签名

如需集成移动端SDK,则需设置包名&签名,详情参考设置包名&签名

2.5 添加服务

创建应用后,点击添加服务,弹出添加服务对话框,选择互动直播服务,点击确定,互动直播服务添加成功。

添加互动直播服务

互动直播服务

2.6 获取互动直播房间id

用户可通过控制台查询互动直播房间ID,也可通过接口和API在线测试工具进行查询。

2.6.1 控制台查询

在互动管理房间管理页面可查询互动直播的房间ID,具体如下图

2.6.2 接口查询

可通过接口获取room_id,具体参考创建互动房间

2.6.3 API在线测试工具查询

参考通过在线测试接口工具完成获取room_id。

2.7 获取旁路直播房间id

room_id 即为 直播房间ID ,以下简称 room_id ,用户可通过控制台查询 room_id,也可通过 API接口 和 API在线测试工具 进行查询。

2.7.1 控制台查询

在直播管理房间管理页面可查询直播间的 房间ID 字段即为 room_id,具体如下图

控制台查询

2.7.2 API接口查询

通过接口创建并获取 room_id,具体参考创建房间接口

2.7.3 API在线测试工具

通过API在线测试工具获取直播房间ID

2.8 获取access_token

access_token即权限,调用接口生成access_token将传入SDK,SDK在请求时会附带access_token,具体方式参考生成access_token接口和API在线测试工具。

: access_token过期时间,默认为一天,最大为一天,过期后需要重新生成access_token。

2.8.1 接口获取

可通过接口获取access_token,具体参考生成access_token接口。

2.8.2 API在线测试工具获取

参考通过在线测试接口工具完成获取access_token。

互动直播

JS SDK Demo

主持端

1 演示地址

https://static.vhallyun.com//jssdk/vhall-jssdk-interaction/test/index.html

2 开始互动

依次填写app_id和互动直播ID,点击开始互动,则进入互动直播房间

3 互动直播

进入互动直播房间后可在房间内进行设置、上麦、下麦、关闭/开启声音、关闭/开启画面、开启/关闭旁路直播、退出房间、用户管理操作。

设置:支持对视、音频设备和视频参数进行设置

上麦:有上麦权限,直接点击上麦,即可进行上麦

下麦:点击下麦,用户下麦,停止直播

开启/关闭视频:点击开启视频可采集视频画面,点击关闭视频将无法采集视频画面

开启/关闭音频:点击开启音频可采集声音,点击关闭音频将无法采集声音

开启/关闭旁路:点击开启旁路,弹出填写旁路直播房间ID的对话框,填写完毕点击确定,即可将混流画面推送至旁路直播房间;点击关闭旁路停止向旁路直播房间进行推流

用户列表:包含用户列表和被踢用户列表,可以查看列表中的所有人员,并可对列表中的人员进行操作,操作项包含踢出房间、邀请上麦、下麦三种

退出房间:点击退出房间则退出互动房间

观众端

1 演示地址

https://static.vhallyun.com/jssdk/vhall-jssdk-interaction/test/guest.html?v=20180611

2 开始互动

依次填写app_id和互动直播ID,点击开始互动,则进入互动直播房间

3 互动直播

进入互动直播房间后可在房间内进行设置、上麦、下麦、请求上麦、关闭/开启声音、关闭/开启画面、退出房间。

设置:支持选择摄像头/麦克风,支持设置分辨率、帧率

上麦/下麦:有上麦权限的观众端,点击上麦,即可在观众端进行上麦互动;点击下麦,即下麦停止互动。

请求上麦:不具备上麦权限的观众端,点击请求上麦,向主持端发起请求,主持端收到请求并通过,观众即可上麦进行互动。

关闭/开启声音:点击开启声音可采集声音,点击关闭声音将无法采集声音

关闭/开启画面:点击开启画面可采集视频画面,点击关画面频将无法采集视频画面

退出房间:点击退出房间则退出互动房间

iOS SDK Demo

具体参考Demo使用指南

Android SDK Demo

具体参考Demo使用指南

控制台使用指南    

成为开发者

详情参考如何成为开发者

创建应用

详情参考如何创建应用

添加服务

创建应用后,点击添加服务,弹出添加服务对话框,选择互动直播服务,点击确定,互动直播服务添加成功。

添加互动直播服务

互动直播服务

互动管理

点击互动服务的互动管理,进入互动管理页面,页面中包含房间配置、推流配置、房间管理、用量统计几部分,用户可分别通过这几个部分来对互动服务进行管理。

房间配置

互动服务添加成功后,可对服务进行配置,配置包含应用信息、旁路直播两部分

应用信息

应用信息包含app_id、回调URL

app_id的含义,参考什么是app_id

回调URL:设置回调URL,填写完成,点击保存,回调URL设置成功。

旁路直播

可对旁路直播功能进行开启关闭,开启旁路直播后,可对视频直播进行混流并推送到指定的直播房间。

提醒:开启旁路直播会产生对应费用,请根据实际情况留意相关成本支出。

推流配置

通过推流配置对推流参数进行配置,推流配置包含配置模式、编码格式、码率、帧率四部分。

系统默认配置对应参数如下

配置模式:超清 编码格式:1280*720 码率:30 帧率:20

同时还支持标清、高清、自定义几种配置模式

配置模式:标清 编码模式:480*360 码率:200 帧率:20

配置模式:高清 编码模式:640*480 码率:200 帧率:20

自定义:支持自由选择系统提供的编码格式、码率、帧率进行组合配置

房间管理

房间管理支持对互动房间进行查询、启用、关闭和预览操作。

查询:支持通过房间ID进行查询。

启用:已关闭的房间,点击启用,房间启用,可恢复推流。

关闭:已启用的房间,点击关闭,房间关闭,将无法进行推流。

直播预览:开启旁路直播且有直播流的情况,点击直播预览,可以预览旁路直播画面,观看直播效果。

用量统计

支持查看本月累计在线时长、峰值带宽、峰值在线人数

支持用户在指定时间范围内查询在线时长、同时在线人数、峰值带宽,颗粒度为1个小时。

客户端集成

iOS SDK如何进行互动

Android SDK如何进行互动

JS SDK如何进行互动

服务端集成

如何通过接口完成互动准备

服务端API手册

如何通过接口完成互动准备    

通过接口测试工具完成    

本文将引导通过接口测试工具,创建互动 inav_id 和具有互动权限的 access_token

进入接口测试页面

为使 SDK 可以在无需服务端代码即可运行,微吼云提供了 API 请求工具为 SDK 完成准备工作。 点击这里 进入 API 请求工具页面。

创建房间

通过接口互动房间、获取互动 ID

  • 选择服务 : Inav ,API 名称 : create
  • 创建互动房间接口文档 填写参数 // 上线修改
  • 返回数据中 inav_id 即为 SDK 发起互动需要的 inav_id

如下图 :

生成 access_token

通过接口获取具有互动权限的 access_token

  • 选择服务 : Base , API 名称 :create-access-token
  • 生成 access_token 接口文档 填写参数
  • 返回数据中的字段 access_token 即为 SDK 发起互动需要的 access_token

如下图 :

参考链接

完成本页的步骤后,您可能需要阅读以下文档。

通过服务端代码完成    

本文将引导通过服务器端代码请求微吼云 API,创建互动 inav_id 和具有互动权限的 access_token

获取 app_id 和 secret_key

微吼云 API 使用 app_id 来标识应用,使用 secret_key 来作为双方通讯的传输秘钥,两者为使用微吼云 API 的必要参数。

点击这里 查看如何获取 app_idsecret_key

PHP代码实现

引入公共代码

我们对调用 API 过程中一些公用代码做了简单封装,作为工具库,您可以 点击这里 获取。然后在代码中引用。

/**
 * 引入公共代码
 */
include_once("vhall_paas.php");

本文后面的 PHP 示例代码将以此工具库为基础。

运行服务端代码

当微吼云代码工具库引入到项目中后,您可以开始运行下列 API 调用代码来获取互动 inav_id 和有互动权限的 access_token

获取直播 room_id

// 引入公共代码
include 'vhall_paas.php';

/**
 * 初始化应用&秘钥信息
 * 获取方式 微吼云控制台 -> 应用 -> 设置 -> 应用信息
 * http://www.vhallyun.com/console/app/index
 */
VhallPaas::$app_id = '';
VhallPaas::$secret_key = '';

// 创建房间接口请求地址
$createInavAddress = 'api.yun.vhall.com/api/v1/inav/create';

// 创建房间参数
$createInavParams = [];

// 加载公共参数&运算签名
$createInavParams = VhallPaas::createRealParam($createInavParams);

// 创建互动房间
try {
    $result = VhallPaas::request($createInavAddress, $createInavParams);
    $inavId = $result['inav_id'];
} catch (Exception $e) {
    exit("请求发生错误 错误信息为 {$e->getMessage()} . 错误码为 {$e->getCode()}");
}

var_dump($roomId);

获取具有互动权限的 access_token

注意 :运行下面代码,需要您先获取到直播 inav_id , 详情见上一步。

// 引入公共代码
include 'vhall_paas.php';

/**
 * 初始化应用&秘钥信息
 * 获取方式 微吼云控制台 -> 应用 -> 设置 -> 应用信息
 * http://www.vhallyun.com/console/app/index
 */
VhallPaas::$app_id = '';
VhallPaas::$secret_key = '';
// 生成AccessToken接口请求地址
$accessTokenAddress = 'api.yun.vhall.com/api/v1/base/create-access-token';

/**
 * 生成 AccessToken 参数
 * AccessToken 为 SDK 中鉴别用户权限使用,针对不同的服务需要不同的参数来生成 AccessToken
 * 当前例子需要互动推流&审核上麦&踢出房间权限,所以权限位使用的 publish_inav_stream,audit_publish_inav ,kick_inav
 * 您可以通过阅读 www.vhallyun.com/docs/show/1013 来获取不同权限的 AccessToken 生成方式
 */
$createAccessTokenParams = [
    // 第三放用户ID,根据实际情况编写 (这里使用当前时间)
    'third_party_user_id' => 'vhall_paas_test_' . time(),
    // 过期时间,最大时间为一天,默认一天 (这里使用10小时做例子)
    'expire_time' => date("Y-m-d H:i:s", time() + (10 * 3600)),
    // 权限设定 (互动 inav_id 通过上一步 获取互动 inav_id 得到)
    'publish_inav_stream' => ‘inav_xxxx’,
    'audit_publish_inav' => ‘inav_xxxx’,
    'kick_inav' => ‘inav_xxxx’,
];

// 加载公共参数&运算签名
$createAccessTokenParams = VhallPaas::createRealParam($createAccessTokenParams);

// 获取 AccessToken
try {
    $result = VhallPaas::request($accessTokenAddress, $createAccessTokenParams);
    $accessToken = $result['access_token'];
} catch (Exception $e) {
    exit("请求发生错误 错误信息为 {$e->getMessage()} . 错误码为 {$e->getCode()}");
}

var_dump($accessToken);

参考链接

完成本页的步骤后,您可能需要阅读以下文档。

如何通过SDK进行互动    

iOS SDK 如何进行互动    

互动准备

  1. 注册微吼开发者账号

  2. 创建应用 获取AppID

  3. 设置应用Bundle ID

  4. 获取互动房间ID roomId

  5. 获取互动房间包含各种权限的access_token

  1. 获取旁路直播房间ID liveRoomId

工程配置

在开始使用sdk之前,我们要配置好IDE和创建基础的工程代码。相关内容在【工程配置】中有详细说明。

注意:

  1. 参与互动只能在真机环境下运行
  2. 互动库最低支持iOS9.0

时序图

注:虚线框部分请根据自身产品需求决定流程是否进行。

集成步骤简述

0、创建本地摄像头预览View,会在第3步中使用(可以在上麦推流之前,不上麦也可以不创建) 1、进入房间 2、接收他人互动画面 3、收到可以上麦代理,上麦(推流) 4、下麦(停止推流) 5、离开房间、销毁房间

代码对接

1: 引入互动SDK头文件

#import "VHInteractiveRoom.h"

2: 创建 VHInteractiveRoom 对象

先创建一个 VHInteractiveRoom 对象,我们后面主要用它来完成互动连麦等操作。

// 创建互动房间并设置 互动房间代理
_room = [[VHInteractiveRoom alloc] init];
self.room.delegate = self;

3: 进入互动房间 enterRoom

  • roomId 互动房间ID
  • accessToken 互动房间包含各种权限的accessToken
NSString* roomId      = @"inav_xxxxxx";
NSString* accessToken = @"xxxxxxxxxx";
[self.room enterRoomWithRoomId:roomId accessToken:accessToken];

4: 接收连麦他人视频 VHRenderView

renderViewsById 房间中所有其他上麦人视频view 是 以streamId 为 key VHRenderView 实例为 value 的 字典

以下是两个他人上下麦的代理方法,用于处理视频展示方式

  • 有人上麦
- (void)room:(VHInteractiveRoom *)room didAddAttendView:(VHRenderView *)attendView
{
//布局连麦界面 renderViewsById 房间中所有其他上麦人视频view
    [self updateData:self.room.renderViewsById changeView:attendView];
//上麦人id
     NSLog(@"%@",attendView.userId);
}
  • 有人下麦
- (void)room:(VHInteractiveRoom *)room didRemovedAttendView:(VHRenderView *)attendView
{
//布局连麦界面 renderViewsById 房间中所有其他上麦人视频view
    [self updateData:self.room.renderViewsById changeView:attendView];
//下麦人id
     NSLog(@"%@",attendView.userId);
}

5: 创建摄像头 cameraView

/*
 * 创建本地摄像头view
 * 默认参数 使用服务器配置参数
 */
VHRenderView*  cameraView = [[VHRenderView alloc]initCameraViewWithFrame: frame];

/*
 * 创建本地摄像头view 使用自定义 视频参数
 * type       type 推流清晰度设置
 * @param options   type = VHPushTypeCUSTOM有效   @{VHVideoWidthKey:@(192),VHVideoHeightKey:@(144),VHVideoFpsKey:@(30),VHMaxVideoBitrateKey:@(200)}
 */
VHRenderView*  cameraView = [[VHRenderView alloc]initCameraViewWithFrame: frame pushType:self.ilssType options:self.ilssOptions];

6: 开始推流 即上麦 publishWithCameraView

注:此接口需收到 room: canPublish: type: 代理方法后调用

cameraView 即上步中创建的本地摄像头 view

[self.room publishWithCameraView:self.cameraView];

7: 停止推流 即下麦 unpublish

[self.room unpublish];

8: 开启/关闭 旁路直播

注:开启旁路直播需 收到 room: didPublish: 代理方法后调用

  • liveRoomId
[self.room publishAnotherLive:isOpen liveRoomId:liveRoomId completeBlock:block];

9: 离开互动房间 leaveRoom

[self.room leaveRoom];

事件处理

  • 互动房间状态监听

1: 错误回调 error

- (void)room:(VHInteractiveRoom *)room error:(NSError*)error {
    if(room.status == VHInteractiveRoomStatusError)
    {
        [self.infoDict removeAllObjects];
        [self updateData:nil changeView:nil];
    }
    
    NSString *str = [NSString stringWithFormat:@"%@(%ld)",error.domain,(long)error.code];
    [self showMsg:str afterDelay:2];
//    [self showCallConnectViews:YES updateStatusMessage:[NSString stringWithFormat:@"Room error: %@", str]];
}

2: 房间状态回调 didChangeStatus

- (void)room:(VHInteractiveRoom *)room didChangeStatus:(VHInteractiveRoomStatus)status {
    switch (status) {
        case VHInteractiveRoomStatusDisconnected:
            [self showCallConnectViews:YES updateStatusMessage:@"已离开互动房间"];
            [self.infoDict removeAllObjects];
            [self updateData:nil changeView:nil];
            break;
        default:
            break;
    }
}

3: 有人进入房间回调 didEnterRoom

- (void)room:(VHInteractiveRoom *)room didEnterRoom:(NSString *)third_user_id {
    if( [DEMO_Setting.third_party_user_id isEqualToString:third_user_id])
        [self showCallConnectViews:NO updateStatusMessage:@"已进入互动房间"];
    else
        [self showMsg:[NSString stringWithFormat:@"%@ 进入房间",third_user_id] afterDelay:2];

    [self updateData:self.room.renderViewsById  changeView:nil];
    // We get connected and ready to publish, so publish.
}

4: 离开房间成功回调 didLeaveRoom

- (void)room:(VHInteractiveRoom *)room didLeaveRoom:(NSString *)third_user_id isKickOutRoom:(BOOL)isKickOut
{
    if( [DEMO_Setting.third_party_user_id isEqualToString:third_user_id])
        if(isKickOut)
        {
            [self showMsg:@"您已被管理员踢出房间" afterDelay:2];
            [self showCallConnectViews:NO updateStatusMessage:@"您已被管理员踢出房间"];
        }
        else
            [self showCallConnectViews:NO updateStatusMessage:@"已离开互动房间"];
    else
        [self showMsg:[NSString stringWithFormat:@"%@ 离开房间",third_user_id] afterDelay:2];
}

5:有人上麦(推流)回调 didAddAttendView

- (void)room:(VHInteractiveRoom *)room didAddAttendView:(VHRenderView *)attendView
{
    //布局连麦界面 renderViewsById 房间中所有上麦人视频view
    [self updateData:self.room.renderViewsById changeView:attendView];

//    NSString *name = attendView.attributes[@"name"];
//    NSString *name = attendView.userId;
//    [self showMsg:[NSString stringWithFormat:@"%@ 已上麦",name] afterDelay:2];
}

6:有人下麦(停止推流)回调 didAddAttendView

- (void)room:(VHInteractiveRoom *)room didRemovedAttendView:(VHRenderView *)attendView
{
    [self updateData:self.room.renderViewsById changeView:attendView];
//    NSString *name = attendView.attributes[@"name"];
//    NSString *name = attendView.userId;
//    [self showMsg:[NSString stringWithFormat:@"%@ 已下麦",name]  afterDelay:2];
}

7:收到别人上麦(推流)申请回调 requestPublish

#pragma mark - 上下麦推流信息回调
/*
 * 收到别人上麦申请 调用审核申请上麦接口回复
 */
- (void)room:(VHInteractiveRoom *)room requestPublish:(NSString *)third_user_id
{
    __weak typeof(self) wf = self;
    [JXTAlertView showAlertViewWithTitle:@"申请上麦"
                                 message:[NSString stringWithFormat:@"%@ 申请上麦",third_user_id]
                       cancelButtonTitle:@"忽略" buttonIndexBlock:^(NSInteger buttonIndex) {
                           switch (buttonIndex) {
                               case 1:
                                   [wf.room acceptPublishRequest:YES thirdUserId:third_user_id];
                                   break;
                               case 2:
                                   [wf.room acceptPublishRequest:NO thirdUserId:third_user_id];
                                   break;
                               default:
                                   break;
                           }
                       } otherButtonTitles:@"同意",@"拒绝", nil];
}

8: 收到可以上麦(推流)回调 canPublish

/*
 *
 * canPublish YES 可以上麦推流 NO 不可上麦推流
 * type 1、进入房间后有无上麦推流权限 2、申请上麦 审核后有无上麦推流权限 3、收到邀请上麦消息 获得上麦推流权限
 */
- (void)room:(VHInteractiveRoom *)room canPublish:(BOOL)canPublish type:(int) type{
    switch (type) {
        case 1:
        {
            if(canPublish)
                [self publish];
            else
                [self showMsg:@"没有上麦权限,请申请上麦" afterDelay:2];
        }
            break;
        case 2:
        {
            if(canPublish)
            {
                [self showMsg:@"管理员同意您上麦" afterDelay:2];
                [self publish];
            }
            else
                [self showMsg:@"管理员拒绝了您上麦请求" afterDelay:2];
        }
            break;
        case 3:
        {
            __weak typeof(self) wf = self;
            [JXTAlertView showAlertViewWithTitle:@"管理员邀请您上麦"
                                         message:@"管理员邀请您上麦"
                               cancelButtonTitle:@"忽略" buttonIndexBlock:^(NSInteger buttonIndex) {
                                   switch (buttonIndex) {
                                       case 1:
                                       {
                                           if(canPublish)
                                               [wf publish];
                                       }
                                           break;
                                       case 2:
                                           [wf.room refusePublish];
                                           break;
                                       default:
                                           break;
                                   }
                               } otherButtonTitles:@"同意",@"拒绝", nil];
        }
            break;
            
        default:
            break;
    }
}

9: 收到上麦(推流)成功回调 didPublish

/*
 * 推流成功
 */
- (void)room:(VHInteractiveRoom *)room didPublish:(VHRenderView *)cameraView
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self didPublish:cameraView.streamId];
        [self showCallConnectViews:NO updateStatusMessage:@"已上麦"];
    });
}

10: 收到下麦(停止推流)成功回调 didUnpublish

/*
 * 停止推流成功
 * reason  "主动下麦" "被动下麦"
 */
- (void)room:(VHInteractiveRoom *)room didUnpublish:(NSString *)reason
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self didUnPublish];
        [self showCallConnectViews:NO updateStatusMessage:@"已下麦"];
    });
}

11: 收到房间人员信息变化回调 userChangeStatus

/*
 * 房间人员信息变化
 */
- (void)room:(VHInteractiveRoom *)room userChangeStatus:(int)status onlineNum:(NSInteger)onlineNum
{
    [self updateUserListData];
    self.onlineNumLabel.text = [NSString stringWithFormat:@"在线 %ld 人",(long)onlineNum];
}

其他相关文档

  1. 快速开始
  2. 互动模块参考手册

Android SDK 如何进行互动    

互动准备

  1. 注册微吼开发者账号

  2. 创建应用 获取AppID

  3. 设置应用Bundle ID

  4. 获取互动房间ID roomId

  5. 获取互动房间包含各种权限的access_token

  1. 获取旁路直播房间ID liveRoomId

工程配置

在开始使用sdk之前,我们要配置好IDE和创建基础的工程代码。相关内容在【工程配置】中有详细说明。

注意:参与互动只能在真机环境下运行

时序图

注:虚线框部分请根据自身产品需求决定流程是否进行。

# 代码对接

1: activity或Fragment的布局文件中添加VHRenderView,渲染本地流(localView)

<com.vhall.vhallrtc.client.VHRenderView
            android:id="@+id/localView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:layout_below="@+id/hsv_streams"
            android:layout_weight="2" />

2: 创建 VHInteractive 对象

构建时传入互动直播房间的流状态监听器,设置互动房间的事件监听等操作。

 interactive = new VHInteractive(mContext, new RoomListener());
 interactive.setOnMessageListener(new MyMessageListener());
 监听示例:
class RoomListener implements Room.RoomDelegate {
        @Override
        public void onDidConnect(Room room, JSONObject jsonObject) {//进入房间
           //进入房间成功回调,一般操作是订阅当前房间所有流(room.subscribe(stream)),订阅成功后在onDidSubscribeStream回调中新建VHRenderView渲染
            //订阅房间内的其他流
            for (Stream stream : room.getRemoteStreams()) {
                room.subscribe(stream);
            }
			//刷新房间内人员状态
            refreshMembers();
        }

        @Override
        public void onDidError(Room room, Room.VHRoomErrorStatus vhRoomErrorStatus, String s) {
           //进入房间失败回调,取消订阅所有流
            for (Stream stream : room.getRemoteStreams()) {
                removeStream(stream);
            }
			//刷新房间内人员状态
            refreshMembers();
        }

        @Override
        public void onDidPublishStream(Room room, Stream stream) {//上麦
           //推流、上麦成功后回调
            isOnline = true;
        }

        @Override
        public void onDidUnPublishStream(Room room, Stream stream) {//下麦
           //取消推流、下麦成功后回调
            isOnline = false;
        }

        @Override
        public void onDidSubscribeStream(Room room, Stream stream) {//订阅其他流
            //订阅成功后回调,此处一般要新建VHRenderView来渲染订阅的流
            addStream(stream);
        }

        @Override
        public void onDidUnSubscribeStream(Room room, Stream stream) {//取消订阅
//取消订阅成功后回调,取消订阅后,该路流将不来拉取,用来渲染该路流的VHRenderView,需要回收
            removeStream(stream);
        }

        @Override
        public void onDidChangeStatus(Room room, Room.VHRoomStatus vhRoomStatus) {//状态改变
           //本地流状态发生变化时回调,连接失败等
        }

        @Override
        public void onDidAddStream(Room room, Stream stream) {//有流加入
           //有新的流加入房间时回调,在此回调中一般执行订阅该路流操作
            room.subscribe(stream);
        }

        @Override
        public void onDidRemoveStream(Room room, Stream stream) {//有流退出
           //有流退出房间时回调,此时一般要移除用户渲染该路流的VHRenderView
            removeStream(stream);
        }

        @Override
        public void onDidUpdateOfStream(Stream stream, JSONObject jsonObject) {//流状态更新
           //流状态发生变化时回调(宽高等)

        }
    }

    class MyMessageListener implements VHInteractive.OnMessageListener {

        @Override
        public void onMessage(JSONObject data) {
            refreshMembers();
            try {
                String event = data.getString("inav_event");
                int status = data.optInt("status");
                String userid = data.optString("third_party_user_id");
                switch (event) {
                    case VHInteractive.apply_inav_publish://申请上麦消息
                        showDialog(VHInteractive.apply_inav_publish, userid);
                        break;
                    case VHInteractive.audit_inav_publish://申请审核结果消息
                        if (status == 1) {//批准上麦
                            interactive.publish();
                        } else {
                            Toast.makeText(mContext, "您的上麦请求未通过!", Toast.LENGTH_SHORT).show();
                        }
                        break;
                    case VHInteractive.askfor_inav_publish://邀请上麦消息
                        showDialog(VHInteractive.askfor_inav_publish, userid);
                        break;
                    case VHInteractive.kick_inav_stream:
                        Toast.makeText(mContext, "您已被请下麦!", Toast.LENGTH_SHORT).show();
                        break;
                    case VHInteractive.kick_inav:
                        Toast.makeText(mContext, "您已被踢出房间!", Toast.LENGTH_SHORT).show();
                        getActivity().finish();
                        break;
                    case VHInteractive.user_publish_callback:
                        String action = "";
                        switch (status) {
                            case 1:
                                action = "上麦啦!";
                                break;
                            case 2:
                                action = "下麦啦!";
                                break;
                            case 3:
                                action = "拒绝上麦!";
                                break;
                        }
                        Toast.makeText(mContext, userid + ":" + action, Toast.LENGTH_SHORT).show();
                        break;
                    case VHInteractive.inav_close:
                        Toast.makeText(mContext, "直播间已关闭", Toast.LENGTH_SHORT).show();
                        getActivity().finish();
                        break;

                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

3: 初始化渲染本地流的LocalVHRenderView和LocalStream

   localView.init(SharedContext, RenderEvents);
   localStream = interactive.createLocalStream(Stream.VhallFrameResolutionValue.VhallFrameResolution320x240.getValue(), "paassdk");
    localView.setStream(localStream);

4: 初始化VHInteractive

初始化操作为异步操作,获取当前互动直播间信息

  interactive.init(mRoomId, mAccessToken, new VHInteractive.InitCallback());

初始化成功后,准备工作基本就绪,本地流开始渲染,接下来就可以进入房间,并进行上线麦操作。

房间内部操作流程为:进入房间、上麦或申请上麦、下麦、离开房间。

5: 进入房间、离开房间

   interactive.enterRoom();
   interactive.leaveRoom();

6: 上麦(需要权限,无权限可申请上麦)、下麦

   interactive.publish();
   interactive.unpublish();

7:邀请上麦、强制下麦、踢出房间(需要权限)

    mInteractive.invitePublish(mMember.userid, null);
    mInteractive.kickoutStream(mMember.userid, null);

8获取房间内成员状态信息

//成员状态为主动拉取,状态改变时要及时更新
   mInteractive.getMembers(Callback callback);

9推旁路直播

//旁路直播是将互动直播间多路流,混合为一直,以直播的形式发出,需要事先创建好一个直播间,将直播ID传入
   interactive.broadcastRoom(mBroadcastid, type, Callback callback);

流(stream)操作包括:静音与取消静音;开户与关闭视频流;如果为本地流(localStream),可以切换摄像头。本地流,可以在创建的interactive里取出,以本地流操作为例,远程流在回调中取到stream,调用相应接口即可。

10静音与取消静音

interactive.getLocalStream().muteVideo();
interactive.getLocalStream().unmuteVideo();

11开启与关闭视频

  interactive.getLocalStream().unmuteVideo();
  interactive.getLocalStream().muteVideo();

12切换摄像头

	//只有本地流才能切换摄像头
    interactive.getLocalStream().switchCamera();

#示例代码

package com.vhall.opensdk.interactive;


import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.vhall.ilss.VHInteractive;
import com.vhall.opensdk.R;
import com.vhall.vhallrtc.client.Room;
import com.vhall.vhallrtc.client.Stream;
import com.vhall.vhallrtc.client.VHRenderView;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

public class InteractiveFragment extends Fragment implements View.OnClickListener {
    private static final String TAG = "InteractiveFragment";
    Context mContext;
    LinearLayout mLayoutGroup;
    LinearLayout mMenuLayout;
    VHRenderView localView;
    Button mEnterBtn, mReqBtn, mJoinBtn, mQuitBtn, mLeaveBtn;

    Handler mHandler = new Handler();
    public String mRoomId;
    public String mAccessToken;
    VHInteractive interactive = null;
    boolean isEnable = false;//是否可用
    //    boolean isEnter = false;//是否进入房间
    boolean isOnline = false;//是否上麦
    OnMemberChangedListener mMemberChangedListener;

    AlertDialog mDialog;
    ToggleButton mBroadcastTB, mVideoTB, mAudioTB;

    String mBroadcastid = "";

    public interface OnMemberChangedListener {
        void onMemberChanged(VHInteractive interactive, List<Member> list);
    }

    public void setOnMemberChangedListener(OnMemberChangedListener listener) {
        mMemberChangedListener = listener;
    }

    public static InteractiveFragment getInstance(String roomid, String accessToken, String broadcastid) {
        InteractiveFragment fragment = new InteractiveFragment();
        fragment.mRoomId = roomid;
        fragment.mAccessToken = accessToken;
        fragment.mBroadcastid = broadcastid;
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        mContext = context;
        super.onAttach(context);
    }


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.frag_interactive, container, false);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initView();
        interactive = new VHInteractive(mContext, new RoomListener());
        interactive.setOnMessageListener(new MyMessageListener());
        localView.init(interactive.getEglBase().getEglBaseContext(), null);
        interactive.setLocalView(localView);
        localView.setTag(interactive.getLocalStream());
        interactive.init(mRoomId, mAccessToken, new VHInteractive.InitCallback() {
            @Override
            public void onSuccess() {
                isEnable = true;
                ClickEventEnter();
            }

            @Override
            public void onFailure(int errorCode, String errorMsg) {
                isEnable = false;
                Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onClick(View v) {

        switch (v.getId()) {
            case R.id.btn_enter:
                ClickEventEnter();
                break;
            case R.id.btn_join:
                clickEventJoin();
                break;
            case R.id.btn_quit:
                clickEventQuit();
                break;
            case R.id.btn_leave:
                clickEventLeave();
                break;
            case R.id.btn_request:
                clickEventReq();
                break;
            case R.id.localView:
                clickEventMenu();
                break;
        }
    }

    public void ClickEventEnter() {//进入房间
        if (!isEnable)
            return;
        interactive.enterRoom();
    }

    public void clickEventReq() {//申请上麦
        if (!isEnable)
            return;
        interactive.requestPublish(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                try {
                    final JSONObject obj = new JSONObject(result);
                    int code = obj.optInt("code");
                    if (code != 200) {
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(mContext, obj.optString("msg"), Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    public void clickEventJoin() {//上麦
        if (!isEnable)
            return;
        if (isOnline)
            return;
        if (!interactive.isPushAvailable()) {
            Toast.makeText(mContext, "无上麦权限", Toast.LENGTH_SHORT).show();
            return;
        }
        interactive.publish();
    }

    public void clickEventQuit() {//下麦
        if (!isEnable)
            return;
        interactive.unpublish();
    }

    public void clickEventLeave() {//离开互动房间
        if (!isEnable)
            return;
        interactive.leaveRoom();
    }

    public void clickEventMenu() {
        if (mMenuLayout.getVisibility() == View.GONE)
            mMenuLayout.setVisibility(View.VISIBLE);
        else
            mMenuLayout.setVisibility(View.GONE);
    }

    public void refreshMembers() {
        interactive.getMembers(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String res = response.body().string();
                Log.i(TAG, "members:" + res);
                try {
                    JSONObject result = new JSONObject(res);
                    String msg = result.optString("msg");
                    int code = result.optInt("code");
                    if (code == 200) {
                        JSONObject data = result.getJSONObject("data");
                        JSONArray list = data.getJSONArray("lists");
                        if (list != null && list.length() > 0) {
                            final List<Member> members = new LinkedList<>();
                            for (int i = 0; i < list.length(); i++) {
                                JSONObject obj = list.getJSONObject(i);
                                Member member = new Member();
                                member.userid = obj.getString("third_party_user_id");
                                member.status = obj.getInt("status");
                                members.add(member);
                            }
                            if (mMemberChangedListener != null) {
                                mHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        mMemberChangedListener.onMemberChanged(interactive, members);
                                    }
                                });
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    class RoomListener implements Room.RoomDelegate {

        @Override
        public void onDidConnect(Room room, JSONObject jsonObject) {//进入房间
            Log.e(TAG, "onDidConnect");
            //订阅房间内的其他流
            for (Stream stream : room.getRemoteStreams()) {
                room.subscribe(stream);
            }
            refreshMembers();
        }

        @Override
        public void onDidError(Room room, Room.VHRoomErrorStatus vhRoomErrorStatus, String s) {//进入房间失败
            Log.e(TAG, "onDidError");
            for (Stream stream : room.getRemoteStreams()) {
                removeStream(stream);
            }
            refreshMembers();
        }

        @Override
        public void onDidPublishStream(Room room, Stream stream) {//上麦
            Log.e(TAG, "onDidPublishStream");
            isOnline = true;
        }

        @Override
        public void onDidUnPublishStream(Room room, Stream stream) {//下麦
            Log.e(TAG, "onDidUnPublishStream");
            isOnline = false;
        }

        @Override
        public void onDidSubscribeStream(Room room, Stream stream) {//订阅其他流
            Log.e(TAG, "onDidSubscribeStream");
            addStream(stream);
        }

        @Override
        public void onDidUnSubscribeStream(Room room, Stream stream) {//取消订阅
            Log.e(TAG, "onDidUnSubscribeStream");
            removeStream(stream);
        }

        @Override
        public void onDidChangeStatus(Room room, Room.VHRoomStatus vhRoomStatus) {//状态改变
            Log.e(TAG, "onDidChangeStatus");
        }

        @Override
        public void onDidAddStream(Room room, Stream stream) {//有流加入
            Log.e(TAG, "onDidAddStream");
            room.subscribe(stream);
        }

        @Override
        public void onDidRemoveStream(Room room, Stream stream) {//有流退出
            Log.e(TAG, "onDidRemoveStream");
            removeStream(stream);
        }

        @Override
        public void onDidUpdateOfStream(Stream stream, JSONObject jsonObject) {//流状态更新
            Log.e(TAG, "onDidUpdateOfStream");

        }
    }

    class MyMessageListener implements VHInteractive.OnMessageListener {

        @Override
        public void onMessage(JSONObject data) {
            refreshMembers();
            try {
                String event = data.getString("inav_event");
                int status = data.optInt("status");
                String userid = data.optString("third_party_user_id");
                switch (event) {
                    case VHInteractive.apply_inav_publish://申请上麦消息
                        showDialog(VHInteractive.apply_inav_publish, userid);
                        break;
                    case VHInteractive.audit_inav_publish://申请审核结果消息
                        if (status == 1) {//批准上麦
                            interactive.publish();
                        } else {
                            Toast.makeText(mContext, "您的上麦请求未通过!", Toast.LENGTH_SHORT).show();
                        }
                        break;
                    case VHInteractive.askfor_inav_publish://邀请上麦消息
                        showDialog(VHInteractive.askfor_inav_publish, userid);
                        break;
                    case VHInteractive.kick_inav_stream:
                        Toast.makeText(mContext, "您已被请下麦!", Toast.LENGTH_SHORT).show();
                        break;
                    case VHInteractive.kick_inav:
                        Toast.makeText(mContext, "您已被踢出房间!", Toast.LENGTH_SHORT).show();
                        getActivity().finish();
                        break;
                    case VHInteractive.user_publish_callback:
                        String action = "";
                        switch (status) {
                            case 1:
                                action = "上麦啦!";
                                break;
                            case 2:
                                action = "下麦啦!";
                                break;
                            case 3:
                                action = "拒绝上麦!";
                                break;
                        }
                        Toast.makeText(mContext, userid + ":" + action, Toast.LENGTH_SHORT).show();
                        break;
                    case VHInteractive.inav_close:
                        Toast.makeText(mContext, "直播间已关闭", Toast.LENGTH_SHORT).show();
                        getActivity().finish();
                        break;

                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    private void addStream(final Stream stream) {
        if (stream == null)
            return;
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                int height = mLayoutGroup.getHeight();
                int width = (3 * height) / 2;
                final VHRenderView view = new VHRenderView(mContext);
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(width, height);
                view.setLayoutParams(params);
                view.init(interactive.getEglBase().getEglBaseContext(), null);
                view.setStream(stream);
                view.setTag(stream);
                view.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        exchangeView(view);
                    }
                });
                mLayoutGroup.addView(view);


            }
        });
    }

    private void removeStream(final Stream stream) {
        if (stream == null)
            return;
        int childCount = mLayoutGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = mLayoutGroup.getChildAt(i);
            if ((view.getTag()) == stream) {
                Log.e(TAG, "存在该stream,移除!");
                mLayoutGroup.removeView(view);
            }
        }
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();

    }

    @Override
    public void onDestroy() {
        if (mDialog != null && mDialog.isShowing())
            mDialog.dismiss();
        interactive.destory();
        super.onDestroy();
    }


    private void initView() {
        mLayoutGroup = getView().findViewById(R.id.ll_streams);
        mMenuLayout = getView().findViewById(R.id.ll_menu);
        localView = getView().findViewById(R.id.localView);
        mEnterBtn = getView().findViewById(R.id.btn_enter);
        mJoinBtn = getView().findViewById(R.id.btn_join);
        mQuitBtn = getView().findViewById(R.id.btn_quit);
        mLeaveBtn = getView().findViewById(R.id.btn_leave);
        mReqBtn = getView().findViewById(R.id.btn_request);
        mBroadcastTB = getView().findViewById(R.id.tb_broadcast);
        mVideoTB = getView().findViewById(R.id.tb_video);
        mAudioTB = getView().findViewById(R.id.tb_audio);
        localView.setOnClickListener(this);
        mEnterBtn.setOnClickListener(this);
        mJoinBtn.setOnClickListener(this);
        mQuitBtn.setOnClickListener(this);
        mLeaveBtn.setOnClickListener(this);
        mReqBtn.setOnClickListener(this);
        mBroadcastTB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (TextUtils.isEmpty(mBroadcastid)) {
                    mBroadcastTB.setChecked(false);
                    return;
                }
                int type = isChecked ? 1 : 2;
                interactive.broadcastRoom(mBroadcastid, type, null);
            }
        });
        mVideoTB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                try {
                    if (isChecked)
                        interactive.getLocalStream().unmuteVideo();
                    else
                        interactive.getLocalStream().muteVideo();

                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        mAudioTB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                try {
                    if (isChecked)
                        interactive.getLocalStream().unmuteAudio();
                    else
                        interactive.getLocalStream().muteAudio();
                 
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * @param event
     * @param userid
     */
    private void showDialog(String event, final String userid) {
        if (getActivity().isFinishing())
            return;
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.dismiss();
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        switch (event) {
            case VHInteractive.apply_inav_publish:
                mDialog = builder.setTitle("申请上麦")
                        .setMessage(userid + " 申请上麦,是否批准!")
                        .setNegativeButton("不批", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                interactive.checkPublishRequest(userid, 2, null);
                            }
                        })
                        .setPositiveButton("批准", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                interactive.checkPublishRequest(userid, 1, null);
                            }
                        })
                        .create();
                mDialog.show();
                break;
            case VHInteractive.askfor_inav_publish:
                mDialog = builder.setTitle("邀请上麦")
                        .setMessage(userid + " 邀请您上麦,是否同意!")
                        .setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                interactive.refusePublish();
                            }
                        })
                        .setPositiveButton("同意", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                interactive.publish();
                            }
                        })
                        .create();
                mDialog.show();
                break;
        }


    }

    private void exchangeView(VHRenderView clickView) {
        Stream clickStream = (Stream) clickView.getTag();
        Stream targetStream = (Stream) localView.getTag();
        localView.setStream(clickStream);
        localView.setTag(clickStream);
        clickView.setStream(targetStream);
        clickView.setTag(targetStream);
    }
}

其他相关文档

互动模块参考手册

JS SDK 如何进行互动    

JS SDK基础模块    

要使用微吼云JS SDK,必须引入JS-SDK基础模块。

跟随本文的步骤,即使你对Javascript语言不熟悉,也完全可以做到。

一.接入前准备

  1. 注册微吼开发者账号

  2. 创建应用 获取AppID

二.引入SDK基础模块

接下来,我们正式接入sdk: 将下面sdk引入代码,插入页面的head标签里。

<script src="https://static.vhallyun.com/jssdk/vhall-jssdk-base-1.0.0.js"></script>

注意:

  1. 直接用本地网页(file协议,地址栏以file:///开头)是调试不了的,需要运行在Server上(https或http协议,地址栏以https:// 或http:// 开头)才能正常调试。

建议直接复制上面代码

三.代码对接

至此,你已完成JS集成的所有准备工作,接下来可参考各个服务的教程完成服务的代码对接工作。

JS互动SDK集成    

在集成前,我们先介绍一下兼容性要求,然后再介绍集成方式。

兼容性要求

  • 仅支持Chrome 72 以上版本浏览器。
  • 浏览器访问链接必须为https安全连接。
  • 对于高画质(720P以上)的视频互动,对计算机硬件要求较高,请斟酌使用。

时序图

注:虚线框部分请根据自身产品需求决定流程是否进行。

集成方式

下载SDK

  • JS基础SDK模块 互动JS-SDK的集成方式与其它服务的JS-SDK集成方式大致相同,都依赖JS-SDK基础模块。所以需要首先下载引入。
  • JS互动SDK模块

引入SDK

  • 将下面sdk引入代码,插入页面的head标签里。
<script src="vhall-jssdk-interaction-VERSION.js"></script>
<script src="vhall-jssdk-base.js-1.0.0.js"></script>
  • VERSION 为SDK版本号,当前版本号为2.0.0
  • SDK 引用顺序与上面保持一致。
  • 注意引用时务必使用HTTPS协议。

使用SDK

至此,互动JS-SDK集成已经完成。请跳转JS互动SDK使用

JS互动SDK使用    

  • 互动SDK主要分为 互动实例创建与销毁、实例事件监听、实例接口使用、全局定义,四大部分。

互动实例

  • 此SDK可支持创建多个实例,每一个实例对应一个互动房间。

创建实例

// 创建实例参数
var opt = {
  inavId: "", // 互动房间ID,必填
  appId: "", // 互动应用ID,必填
  accountId:"", // 第三方用户ID,必填
  token:"", // access_token,必填
  role: VhallRTC.MASTER // 用户身份
};

// 成功回调
var vhallrtc;
var success = function (event) {
 // 实例句柄
  vhallrtc = event.vhallrtc;
  // 房间内远端流Id列表
  var remoteStreams = event.streams;
};

// 失败回调
var failure = function (event) {
  console.log(event); // object 类型,{ code: 错误码, message: "", data: {} }
};

// 创建实例
VhallRTC.createInstance(opt, success, failure);
  • accountId 参数必须为英文字符,不能为中文。
  • VhallRTC 为SDK全局句柄。VhallRTC.MASTER 为全局定义
  • vhallrtc 为当前传入互动房间实例句柄,故以下所有操作均必须在成功回调后执行。
  • 失败回调内错误原因,详见错误码,下同。

销毁实例

//预留参数
var opt = {};
// 成功回调
var success = function (event) {
 
};
// 失败回调
var failure = function (event) {
  console.log(event); // object 类型,{ code: 错误码, message: "", data: {} }
};

// 销毁实例
vhallrtc.destroyInstance(opt, success, failure);

实例事件监听

  • 在调用实例接口前,建议全部监听以下互动事件。

事件监听

vhallrtc.on(event , handler); // event 为事件 , handler 为处理函数

房间连接出错事件

vhallrtc.on(VhallRTC.EVENT_ROOM_ERROR, function(event) {
	console.log(event); // {  event:"EVENT_ROOM_ERROR" ,  data:{} };
});

房间关闭事件

vhallrtc.on(VhallRTC.EVENT_ROOM_CLOSE, function(event) {
	console.log(event); // {  event:"EVENT_ROOM_CLOSE" ,  data:{} };
});

审核上麦事件

vhallrtc.on(VhallRTC.EVENT_ROOM_AUTH, function(event) {
	console.log(event); //  {  event:"EVENT_ROOM_AUTH" ,  data:{  userId:"" , status:0} }  status 1 为上麦  2 为下麦  3 为拒绝上麦
});

添加用户进黑名单事件

vhallrtc.on(VhallRTC.EVENT_ROOM_BLACKLIST, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_BLACKLIST" ,  data:{  userId:""} };
});

被邀请上麦事件

vhallrtc.on(VhallRTC.EVENT_ROOM_INVITED, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_INVITED" ,  data:{  userId:""} };
});

申请上麦事件

vhallrtc.on(VhallRTC.EVENT_ROOM_APPLY, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_APPLY" ,  data:{  userId:""} };
});

邀请上麦回复事件

vhallrtc.on(VhallRTC.EVENT_ROOM_CALLBACK, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_CALLBACK" ,  data:{  userId:"" , status:1} };   status 1 同意上麦  2 下麦  3 拒绝上麦
});

用户加入房间事件

vhallrtc.on(VhallRTC.EVENT_ROOM_JOIN, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_JOIN" ,  data:{  userId:""} };
});

用户离开房间事件

vhallrtc.on(VhallRTC.EVENT_ROOM_LEAVE, function(event) {
	console.log(event);  //  {  event:"EVENT_ROOM_LEAVE" ,  data:{  userId:""} };
});

推拉流异常事件

vhallrtc.on(VhallRTC.EVENT_REMOTESTREAM_FAILED, function(event) {
	console.log(event);  //  {  event:"EVENT_REMOTESTREAM_FAILED" ,  data:{  streamId:""} };
});

远端流添加事件

vhallrtc.on(VhallRTC.EVENT_REMOTESTREAM_ADD, function(event) {
	console.log(event);  //  {  event:"EVENT_REMOTESTREAM_ADD" ,  data:{  streamId:""} };
});

远端流删除事件

vhallrtc.on(VhallRTC.EVENT_REMOTESTREAM_REMOVED, function(event) {
	console.log(event);  //  {  event:"EVENT_REMOTESTREAM_REMOVED" ,  data:{  streamId:""} };
});

远端流音视频状态改变事件

vhallrtc.on(VhallRTC.EVENT_REMOTESTREAM_MUTE, function(event) {
	console.log(event);  //  {  event:"EVENT_REMOTESTREAM_MUTE" ,  data:{  streamId:""} };
});

实例接口使用

获取音视频设备列表

// 预留参数
var opt = {};

// 成功回调
var success = function (devices) {

};

// 失败回调
var failure = function (event) {
  console.log(event); // object 类型,{ code: 错误码, message: "", data: {} }
};

// 获取音视频设备列表
VhallRTC.getDevices(opt, success, failure);
  • 此方法挂载在VhallRTC下,不初始化互动房间也可调用

获取视频设备支持分辨率列表

// 参数
var opt = {
  deviceId: "设备ID"
};

// 成功回调
var success = function (res) {

};

// 失败回调
var failure = function (event) {
  console.log(event); // object 类型,{ code: 错误码, message: "", data: {} }
};

// 获取视频设备支持分辨率列表
vhallrtc.getVideoConstraints(opt, success, failure);
  • 由于chrome浏览器原因,获取时请确保相关视频设备未启用。如已启用可能会导致获取结果异常。

创建本地音视频流

// 配置流参数
var opt = {
  videoNode: "div", // 传入本地视频显示容器,必填
  audio: true, // 是否获取音频,选填,默认为true
  video: true, // 是否获取视频,选填,默认为true
  profile: VhallRTC.VIDEO_PROFILE_180P_1, // 自定义分辨率,使用分辨率模板,选填,与videoQuality参数互斥,优先使用profile参数,推荐使用。
  videoQuality: { width: 320, height: 180, bitrate: 200 , framerate:5 }, // 自定义分辨率、码率、帧率(5~30),选填 不使用profile模板时可选填,初学者不推荐使用。
  mute: {audio: false, video: false}, // 是否静音,关视频,选填,默认为false
  videoDevice: "deviceId", // 视频设备id,选填 默认为系统缺省
  audioDevice: "deviceId", // 视频设备id,选填 默认为系统缺省
};
// 成功回调
var success = function(event){
	var streamId = event.streamId;  // 获取流Id
};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 创建本地流
vhallrtc.createStream(opt , success , failure);
  • 传入profile参数时,默认按照profile的参数创建本地流。
  • 传入videoDevice 与 audioDevice 时,会按照指定设备创建本地流。

销毁本地音视频流

// 参数
var opt = {
	streamId:""  //  本地流ID
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 销毁本地流
vhallrtc.destroyStream(opt, success , failure);

推送本地流

// 参数
var opt = {
	streamId:''   // 本地流ID
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 推送本地流
vhallrtc.publish(opt, success, failure);

停止推送本地流

// 参数
var opt = {
	streamId:""  // 本地流ID
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 停止推送本地流
vhallrtc.unpublish(opt, success, failure);

订阅播放远端流

// 监听远端流添加事件
vhallrtc.on(VhallRTC.EVENT_REMOTESTREAM_ADD, function(event) {
	// 传入参数
	var opt = {
		streamId:event.data.streamId, // 远端流ID,必填
		videoNode:'remoteVideoDiv',  // 远端流显示容器, 必填
		mute:{
			audio:false,    // 是否静音     选填  默认 false
			video:false     // 是否关闭视频  选填 默认 false
		},
		dual:1 // 双流订阅选项, 0 为小流 , 1 为大流  选填。 默认为 1
	};
	// 成功回调
	var success = function(res){
	};
	// 失败回调
	var failure = function(event){
		console.log(event); // object 类型, { code:错误码, message:"", data:{} }
	};
	// 订阅播放远端流
	vhallrtc.subscribe(opt, success, failure);
});
  • 该方法需要在监听到远端流添加后调用
  • 每个远端流都需要单独的显示容器
  • 双流是为了减少不必要的带宽和系统资源占用,当页面小屏幕显示时,推荐订阅小流,此选项可通过接口动态切换

取消订阅远端流

	// 传入参数
	var opt = {
		streamId:event.data.streamId // 远端流ID , 必填
	};
	// 成功回调
	var success = function(res){
	};
	// 失败回调
	var failure = function(event){
		console.log(event); // object 类型, { code:错误码, message:"", data:{} }
	};
	// 取消订阅
	vhallrtc.unsubscribe(opt, success, failure);

获取房间信息

var info = vhallrtc.getRoomInfo();
/*
{
  local: {
    users: [
      {
        accountId,
        streams: [{ streamId, type, state }, { streamId, type, state }...]  // 流Id,流类型,状态
      } // state 0 未订阅 1 已订阅
    ]
  },
  remote: {
    users: [
      {
        accountId,
        streams: [{ streamId, type, state }, { streamId, type, state }...]
      } // state 0 未订阅 1 已订阅
    ]
  }
}
*/
  • 注意:内部获取用户列表时不只获取流媒体端用户和流列表,还要调用PaaS平台用户列表获取接口。取并集

开关音视频

// 传入参数
var opt = {
	streamId:"",  //  本地或远端流Id,必填
	mute:{
		audio:false,   // 是否关音频,选填。 不填则不改变当前流音频状态
		video:false,   // 是否关视频,选填。 不填则不改变当前流视频状态
	}
};
// 成功回调
var success = function(res){

}
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 开关音视频
vhallrtc.mute(opt, success, failure);

远端流双流配置

// 传入参数
var opt = {
	streamId:"", // 远端流Id,必填
	dual:1   // 双流订阅选项, 0 为小流, 1为大流 必填。
};
// 成功回调
var success = function(res){

}
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 远端流双流配置
vhallrtc.setDual(opt, success, failure);

开启旁路直播推流

// 传入参数
var opt = {
	roomId:"", // 直播房间ID,必填
	layout: VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_1,    // 旁路布局,选填 默认一人平铺类型
	profile:VhallRTC.VIDEO_PROFILE_120P_0,
	mainScreenStreamId: ""       // 旁路流中主屏显示流ID,选填 默认最先推送的流ID
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 开启推送旁路直播流
vhallrtc.startBroadCast(opt, success, failure);

停止旁路直播流

// 传入参数
var opt = {
	roomId:"" // 直播房间ID,必填
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 停止推送旁路直播流
vhallrtc.stopBroadCast(opt, success, failure);

动态配置旁路布局

// 传入参数
var opt = {
	layout: VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_1, // 旁路布局,选填 不填默认不改变原配置
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 配置旁路布局
vhallrtc.setBroadCastLayout(opt, success, failure);
  • 此接口必须在旁路直播推流成功后调用

动态配置旁路主屏

// 传入参数
var opt = {
	mainScreenStreamId: "" // 旁路流中主屏显示流ID,选填 不填默认不改变原配置
};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 动态配置旁路主屏
vhallrtc.setBroadCastScreen(opt, success, failure);
  • 此接口必须在旁路直播推流成功后调用

同意申请上麦

vhallrtc.on(VhallRTC.EVENT_ROOM_APPLY, event => {
	// 传入参数
	var userId = event.data.userId;
	// 成功回调
	var success = function(res){
	};
	// 失败回调
	var failure = function(event){
		console.log(event); // object 类型, { code:错误码, message:"", data:{} }
	};
	// 同意申请上麦
	vhallrtc.consentApply(userId, success, failure);
});
  • userId 需要通过监听到申请上麦后获得
  • 该方法只有主持端才能调用

拒绝申请上麦

vhallrtc.on(VhallRTC.EVENT_ROOM_APPLY, event => {
	// 传入参数
	var userId = event.data.userId;
	// 成功回调
	var success = function(res){
	};
	// 失败回调
	var failure = function(event){
		console.log(event); // object 类型, { code:错误码, message:"", data:{} }
	};
	// 拒绝申请上麦
	vhallrtc.rejectApply(userId, success, failure);
});
  • userId 需要通过监听到申请上麦后获得
  • 该方法只有主持端才能调用

邀请上麦

// 传入参数
var userId = "";
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 邀请上麦
vhallrtc.invite(userId, success, failure);
  • 该方法只有主持端才能调用

申请上麦

// 预留参数
var opt = {};
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 申请上麦
vhallrtc.apply(opt, success, failure);

获取黑名单列表

// 预留参数
var opt = {};
// 成功回调
var success = function(res){
	console.log(res);     // array 类型
};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 获取黑名单列表
vhallrtc.getBlackList(opt , success, failure);
  • 该方法只有主持端才能调用

加入黑名单列表

// 传入参数
var userId = "" ;
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 加入黑名单列表
vhallrtc.addBlackList(userId, success, failure);
  • 该方法只有主持端才能调用

从黑名单列表中移除

// 传入参数
var userId = "";
// 成功回调
var success = function(res){

};
// 失败回调
var failure = function(event){
	console.log(event); // object 类型, { code:错误码, message:"", data:{} }
};
// 从黑名单列表中移除
vhallrtc.removeBlackList(userId, success, failure);
  • 该方法只有主持端才能调用

获取上下行丢包率

// 传入参数
var type = "";   // up为获取上行   down为获取下行
// 获取上下行丢包率
vhallrtc.getPacketLossRate(type);

全局定义

互动角色参考表(role参数)

可选值 角色 备注
VhallRTC.MASTER 主持人 拥有全部接口使用权限
VhallRTC.GUEST 嘉宾、观众 仅拥有部分接口使用权限

互动流分辨率参考表(profile参数)

可选值 宽高比 分辨率(宽x高) 码率 (kbps) 备注
VhallRTC.VIDEO_PROFILE_120P_0 4:3 160x120 120~200
VhallRTC.VIDEO_PROFILE_120P_1 16:9 160x90 100~180
VhallRTC.VIDEO_PROFILE_180P_0 4:3 240x180 180~290
VhallRTC.VIDEO_PROFILE_180P_1 16:9 320X180 180~290
VhallRTC.VIDEO_PROFILE_240P_0 4:3 320x240 180~300
VhallRTC.VIDEO_PROFILE_240P_1 16:9[1] 424X240 190~350
VhallRTC.VIDEO_PROFILE_360P_0 4:3 480X360 190~400
VhallRTC.VIDEO_PROFILE_360P_1 16:9 640x360 200~480
VhallRTC.VIDEO_PROFILE_480P_0 4:3 640x480 215~720
VhallRTC.VIDEO_PROFILE_480P_1 16:9[1] 848x480 240~950
VhallRTC.VIDEO_PROFILE_720P_0 4:3 960x720 250~1100
VhallRTC.VIDEO_PROFILE_720P_1 16:9 1280x720 275~1250
VhallRTC.VIDEO_PROFILE_1080P_0 4:3 1440x1080 350~1500
VhallRTC.VIDEO_PROFILE_1080P_1 16:9 1920x1080 400~1800
VhallRTC.VIDEO_PROFILE_720P_DESKTOP 16:9 1280x720 此定义仅针对桌面采集模式
VhallRTC.VIDEO_PROFILE_1080P_DESKTOP 16:9 1920x1080 此定义仅针对桌面采集模式
  • 此参数为互动发起使用,注意与旁路区分。

互动旁路直播流分辨率参考表

可选值 宽高比 分辨率(宽x高) 码率 (kbps)
VhallRTC.BROADCAST_VIDEO_PROFILE_480P_0 4:3 640x480 600
VhallRTC.BROADCAST_VIDEO_PROFILE_480P_1 16:9 852x480 725
VhallRTC.BROADCAST_VIDEO_PROFILE_720P_0 4:3 960x720 1050
VhallRTC.BROADCAST_VIDEO_PROFILE_720P_1 16:9 1280X720 1100
VhallRTC.BROADCAST_VIDEO_PROFILE_960P_0 4:3 1280x960 1300
VhallRTC.BROADCAST_VIDEO_PROFILE_1080P_0 4:3 1440X1080 1350
VhallRTC.BROADCAST_VIDEO_PROFILE_1080P_1 16:9 1920X1080 1600
  • 此参数为旁路直播使用,注意与互动区分

互动旁路布局参考表(layout参数)

均匀布局表格

可选值 说明 图示(4:3画布) 图示(16:9画布)
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_1 一人铺满
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_2_H 左右两格
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_3_E 正品字
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_3_D 倒品字
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_4_M 2行x2列
VhallRTC.CANVAS_LAYOUT_PATTERN_GRID_9_E 3行x3列

主次屏浮窗布局

可选值 说明 图示(4:3画布) 图示(16:9画布)
VhallRTC.CANVAS_LAYOUT_PATTERN_FLOAT_2_1DR 大屏铺满,小屏悬浮右下角
VhallRTC.CANVAS_LAYOUT_PATTERN_FLOAT_2_1DL 大屏铺满,小屏悬浮左下角
VhallRTC.CANVAS_LAYOUT_PATTERN_FLOAT_3_2DL 大屏铺满,2小屏悬浮左下角
VhallRTC.CANVAS_LAYOUT_PATTERN_FLOAT_6_5D 大屏铺满,一行5个悬浮于下面
VhallRTC.CANVAS_LAYOUT_PATTERN_FLOAT_6_5T 大屏铺满,一行5个悬浮于上面

主次屏平铺布局

可选值 说明 图示(4:3画布) 图示(16:9画布)
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_5_1T4D 主次平铺,一行4个位于底部
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_5_1D4T 主次平铺,一行4个位于顶部
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_5_1L4R 主次平铺,一列4个位于右边
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_5_1R4L 主次平铺,一列4个位于左边
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_6_1T5D 主次平铺,一行5个位于底部
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_6_1D5T 主次平铺,一行5个位于顶部
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_9_1L8R 主次平铺,右边为(2列x4行=8个块)
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_9_1R8L 主次平铺,左边为(2列x4行=8个块)
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_9_1D8T 平铺,主屏在下,8个(2行x4列)在上
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_13_1L12R 主次平铺,右边为(3列x4行=12个块)
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_13_1TL12GRID 主次平铺,主屏在左上角,其余12个均铺于其他剩余区域
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_17_1TL16GRID 主次平铺,1V16,主屏在左上角
VhallRTC.CANVAS_LAYOUT_PATTERN_TILED_17_1TL16GRID_E 主次平铺,主屏在左上角,其余16个均铺于其他剩余区域
  • 便于理解,如 CANVAS_LAYOUT_PATTERN_TILED_5_1R4L,其中“5”表示共5块,H表示horizontal,E表示erected, M表示matts, T/D表示top/down,L/R表示left/right
  • 当房间内只有一路视频时,无论设置哪一种预置定义模板,混流结果均是该路视频等比铺满画布

互动事件参考表(监听实例事件)

可选值 事件 备注
VhallRTC.EVENT_ROOM_ERROR 房间连接出错事件 房间连接出错之后触发
VhallRTC.EVENT_ROOM_CLOSE 房间关闭事件 房间关闭后触发
VhallRTC.EVENT_ROOM_AUTH 审核上麦事件 观众端申请上麦,主持端审核后触发
VhallRTC.EVENT_ROOM_BLACKLIST 添加用户进黑名单事件 主持端给观众加入黑名单时触发
VhallRTC.EVENT_ROOM_INVITED 被邀请上麦事件 主持端邀请上麦后触发
VhallRTC.EVENT_ROOM_APPLY 申请上麦事件 观众端申请上麦后触发
VhallRTC.EVENT_ROOM_CALLBACK 邀请上麦回复事件 主持端邀请上麦,观众端接受或拒绝后触发
VhallRTC.EVENT_ROOM_JOIN 用户加入房间事件 用户初始化后触发
VhallRTC.EVENT_ROOM_LEAVE 用户离开房间事件 用户离开时触发
VhallRTC.EVENT_REMOTESTREAM_FAILED 推拉流异常事件 推拉流异常时触发
VhallRTC.EVENT_REMOTESTREAM_ADD 远端流添加事件 当房间内有推流时触发
VhallRTC.EVENT_REMOTESTREAM_REMOVED 远端流删除事件 当房间内有取消推流时触发
VhallRTC.EVENT_REMOTESTREAM_MUTE 远端流音视频状态改变事件 改变音视频状态后触发

错误码

code message
611000 初始化房间失败
611001 无效的参数
611002 用户已存在
611003 dom节点不存在
611004 创建本地流失败
611005 初始化本地流失败
611006 该streamId不存在
611007 推流失败
611008 无音频/视频设备
611009 权限验证失败
611010 停止推流失败
611011 订阅远端流失败
611012 取消订阅远端流失败
611013 未订阅该流
611014 该方法只有主持端能调用
611015 无法配置本地流双流
611016 停止旁路失败
611017 开启旁路失败
611018 切换旁路推流布局失败
611019 同意申请上麦失败
611020 拒绝申请上麦失败
611021 邀请上麦失败
611022 同意被邀请上麦失败
611023 拒绝被邀请上麦失败
611024 申请上麦失败
611025 获取黑名单列表失败
611026 获取当前用户列表失败
611027 添加用户进黑名单失败
611028 移除黑名单用户失败
611029 accountId不能为中文
611030 appId不存在
611031 获取远端房间信息失败
611032 inavId不存在
611033 token不存在

JS互动v1.0集成(旧版)    

使用微吼云JS SDK,只需简单几步,就能快速接入互动直播服务。

跟随本文的步骤,即使你对Javascript语言不熟悉,也完全可以做到。

其中,一、二步为准备工作,第三步为代码接入,下面,就分步来说明。

一.接入前准备

  1. 注册微吼开发者账号

  2. 创建应用 获取AppID

  3. 获取 互动房间ID: inavId

    可以通过 【测试工具】 或 【API】 获得

  4. 获取直播间包含看直播权限的access_token

    可以通过 【测试工具】 或 【API】 获得

时序图

注:虚线框部分请根据自身产品需求决定流程是否进行。

二.引入sdk文件

接下来,我们正式接入sdk: 将下面sdk引入代码,插入页面的head标签里。

<script type="text/javascript" src="https://static.vhallyun.com/jssdk/vhallILSSDK.js"></script>
<script src="https://static.vhallyun.com/jssdk/vhall-jssdk-interaction-1.0.0.js"></script>
<script src="https://static.vhallyun.com/jssdk/vhall-jssdk-base-1.0.0.js"></script>

注意:

  1. 直接用本地网页(file协议,地址栏以file:///开头)是调试不了的,需要运行在Server上(https或http协议,地址栏以https:// 或http:// 开头)才能正常调试。
  1. 上面两个文件都需要引用,而且顺序需要保持与上面一致。
  1. 建议直接复制上面代码

三.代码对接

至此,你已完成所有准备工作,距离完成,只差以下几步:

1.创建互动容器

在需要显示互动画面的位置,加入互动容器,也就是放一个div,然后给它取个名字,比如: my-interaction 。之后互动回显画面都会在这个容器里渲染,容器的大小控制您可以使用div的属性进行控制,示例代码如下:

  <div id="my-interaction"></div>

2.注册ready事件

接下来,在页面里写javascript代码,调用sdk提供的接口,注册sdk ready事件,接口调用方法如下:

/**
 * 注册ready事件
 */
Vhall.ready(function(){
    //to do
})

注意:所有文档相关逻辑需要写在此事件里。

3.初始化SDK配置

接下来需要传入相关参数,初始化配置:

 /**
  * 初始化配置
  */
 Vhall.config({
     appId :'',//应用 ID ,必填
     accountId :'',//第三方用户唯一标识,必填
     token:'',//token必填
 })     

4.初始化互动主持端

调用此方法后,主持端就能看到互动的画面了。

    /**
     * 初始化文档对象
     */
    var  interaction = new VhallInteraction({
        inavId:"", //互动房间id,必填
        videoNode:"my-interaction", //互动直播显示容器,必填
        //成功回调,非必填
        success: function(result){
          //TODO
        },
        //失败回调,非必填
        fail: function(reason){
          //TODO
        }
    });
    

5.初始化互动观众端

调用此方法后,观众端就能看到互动的画面了。

    /**
     * 初始化文档对象
     */
    var  interaction = new VhallInteractionGuest({
        inavId:"", //互动房间id,必填
        videoNode:"my-interaction", //互动直播显示容器,必填
        //成功回调,非必填
        success: function(result){
          //TODO
        },
        //失败回调,非必填
        fail: function(reason){
          //TODO
        }
    });
    

参数说明:

参数名称 参数说明 是否必填
inavId 互动房间id
videoNode 互动直播显示容器
success 初始化成功回调
fail 初始化失败回调

完整例子代码


<html>
  <head>
    <meta charset="UTF-8">
    <title>互动直播demo</title>
    <style>
      body{
        padding-top: 20px;
      }
      #enterDiv{
        width:520px;
        height:100px;
        margin:160px auto 20px auto;
      }
      .btn1{
        height:80px;
        width:250px;
        font-size:20px;
        font-family: 黑体,serif;
        position: relative;
      }
      #roomContainerTable td{
        padding: 1px 2px;
      }
      .streamdiv{
        width: 320px; height: 240px;float:left;padding-bottom:30px;
      }
      .stream_btn{
        position: absolute;
        bottom:0;
      }
      .online{
        background: greenyellow;
      }
      .offline{
        background: darkred;
      }
      select{
        margin-left: 6px;
      }
      label{
        line-height: 30px;
      }
      .inputs{
        width:100%;
        text-align: center;
      }
      .actions{
        width: 100%;
        text-align:center;
        clear: both;
        margin-top:20px;
      }
      input[type=button],button{
        min-width: 80px;
        height: 34px;
        padding: 7px 10px;
        border: 1px solid #d2d2d2;
        border-radius: 4px;
        outline: none;
      }
      button{
        min-width:50px;
        height: 22px;
        padding: 2px 4px;
      }
      input[type=text]{
        height: 34px;
        width: 128px;
        padding: 10px 8px;
        border: 1px solid #d2d2d2;
        border-radius: 4px;
        outline: none;
      }


      .confs-title{
        display: inline-block;
        text-align: right;
      }
      .confs-title .confs-title-item{
        height: 60px;
      }
      .confs-info{
        display: inline-block;
        width: 500px;
        margin-left: 10px;
      }
      .confs-info .confs-info-item{
        height: 60px;
      }
      .action-box{
        text-align: center;
      }
      .btn-small{
        width: 50px !important;
        height: 26px;
        padding: 2px 6px;
      }
      .btn-white,.btn-white:hover,.btn-white:visited,.btn-white:active{
        background-color: #fff;
        color:#666;
        border:1px solid #666;
        border-radius: 3px;
      }
      .settings{
        display: none;
        position: absolute;
        border: 1px solid #d2d2d2;
        padding-top: 40px;
        padding-left: 40px;
        padding-right: 40px;
        padding-bottom: 40px;
        text-align: center;
        width: auto;
        vertical-align: middle;
        margin-top: -432px;
        margin-left: 498px;
        background-color: #fff;
        border-radius: 5px;
      }
      .settings .confs-info{
        text-align: left;
        width: 200px;
      }
      .settings-action-btn{
        margin-top: 0px;
      }
      .settings-action-btn div{
        width: 30%;
        display: inline-block;
        text-align: center;
      }
      .settings-action-btn button{
          margin-top: 0px;
      }
      select{
        max-width: 180px;
        height: 34px;
        padding: 6px 12px;
      }
    </style>
  </head>

  <body>

    <div class="inputs">
      <input id="appId" type="text" name="appId" value="277ed4af" placeholder="这里填写appId">
      <input id="inavId" type="text" name="inavId" value="inav_8ff374db" placeholder="这里填写互动房间ID">
      <input onclick="init()" type="button"  value="开始互动">
    </div>
    <div id="my-interaction" style="width: 853px;height: 480px;margin: 0 auto;border: 1px solid #d2d2d2;margin-top:20px;">
    </div>
    <div style="margin-top: 10px;text-align: center;">
      <div id="settings" class="settings">
        
      </div>
      <button onclick="setSettings();">设置</button>
    </div>
    <div class="actions">
      <!-- <button onclick="leave();">申请上麦</button> -->
      <input type="button" value="下麦" onclick="leave();">
      <input type="button" value="上麦" onclick="join();">
      <input type="button" value="关闭声音" onclick="muteAudio(this);">
      <input type="button" value="关闭画面" onclick="muteVideo(this);">
      <input type="button" value="打开旁路直播" onclick="setBroadCast(true);">
      <input type="button" value="关闭旁路直播" onclick="setBroadCast(false);">
      <input type="button" value="退出房间" onclick="window.interaction.exit();">
    </div>
    <table style="width: 500px;margin: 0 auto;margin-top: 30px;">
      <thead>
        <tr>
          <td colspan="3" style="text-align: center;height: 50px;font-size: 20px;">用户列表</td>
        </tr>
        <tr>
          <td>用户Id</td>
          <td>状态</td>
          <td>操作</td>
        </tr>
      </thead>
      <tbody id="user-list">
      </tbody>
    </table>

    <table style="width: 500px;margin: 0 auto;margin-top: 30px;">
      <thead>
        <tr>
          <td colspan="3" style="text-align: center;height: 50px;font-size: 20px;">被踢用户列表</td>
        </tr>
        <tr>
          <td>用户Id</td>
          <td>状态</td>
          <td>操作</td>
        </tr>
      </thead>
      <tbody id="black-list">
      </tbody>
    </table>

  </body>
  <script type="text/javascript" src="//code.jquery.com/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="https://static.vhallyun.com/jssdk/vhallILSSDK.js"></script>
<script src="https://static.vhallyun.com/jssdk/vhall-jssdk-interaction-1.0.0.js"></script>
<script src="https://static.vhallyun.com/jssdk/vhall-jssdk-base-1.0.0.js"></script>
  <script type="text/javascript" src="http://res.layui.com/layui/release/layer/dist/layer.js?v=3111"></script>
  <script>

    var alert = function(msg){
      layer.msg(msg,{
        time:2000
      });
    }

    var users = [
    {userId:'100026',username:'张三'},
    {userId:'100027',username:'李四'},
    {userId:'100028',username:'王五'},
    {userId:'100029',username:'赵四'},
    {userId:'100030',username:'小沈阳'},
    {userId:'100031',username:'郭德纲'},
    {userId:'100032',username:'于谦'},
    {userId:'100033',username:'岳云鹏'},
    {userId:'100034',username:'孙悦'},
    {userId:'100035',username:'范伟'},
    {userId:'100036',username:'冯巩'},
    {userId:'100037',username:'潘长江'},
    ]

    var us = {
      100026:'张三',
      100027:'李四',
      100028:'王五',
      100029:'赵四',
      100030:'小沈阳',
      100031:'郭德纲',
      100032:'于谦',
      100033:'岳云鹏',
      100034:'孙悦',
      100035:'范伟',
      100036:'冯巩',
      100037:'潘长江',
      Master:'Master'
    }
  </script>
  <script type="text/javascript">
      window.interaction = {};
      //获取URL传参
      function getParameterByName(name) {
          name = name.replace(/[\[]/, '\\\[').replace(/[\]]/, '\\\]');
          var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
              results = regex.exec(location.search);
          return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
      }

      window.onload = function () {


      }

      function invite(userId){

        window.interaction.invite({
          userId: userId,
          consent: function(userId){
            // alert('恭喜你,你邀请'+ userId +'上麦, Ta答应啦!');
          },
          reject: function(userId){
            alert('非常遗憾,你邀请'+ userId +'上麦,被Ta拒绝了!');
          },
          fail: function(userId){
            alert('非常遗憾,你的邀请没有送达!');
          }
        });

      }

      //下麦
      function leave(){
        window.interaction.unpublish();
      }

      //上麦
      function join(){
        window.interaction.publish();
      }

      //踢出
      function delUser(userId){
        window.interaction.deleteUser({
          userId: userId,
          success:function(res){
            // alert('恭喜你,成功把'+ userId +'踢出!');
          },
          fail: function(reason){
            alert(reason);
          }
        });
      }

      //取消踢出(移除黑名单?)
      function unDeleteUser(userId){
        window.interaction.unDeleteUser({
          userId:userId,
          success: function(res){
            showBlackList();
          },
          fail: function(reason){

          }
        });
      }

      function statusMapping(v){
        var status = {
          '1':'推流中',
          '2':'观看中',
          '3':'受邀中',
          '4':'申请中',
          '5':'被踢出'
        }
        return status[v];
      }

      function switchButton(status,userId){
          var buttons = '';
          switch(status){
            case 1:
              buttons += '<button onclick="delUser(\''+ userId +'\');">踢出</button>'
            break;
            case 2:
              buttons += '<button onclick="invite(\''+userId +'\');">邀请</button>'
              buttons += '<button onclick="delUser(\''+ userId +'\');">踢出</button>'
            break;
            case 3:
               buttons += '<button onclick="delUser(\''+ userId +'\');">踢出</button>'
            break;

            case 4:
              buttons += '<button onclick="window.interaction.consentApply(\''+userId +'\');">批准</button>';
              buttons += '<button onclick="window.interaction.rejectApply(\''+userId +'\');">拒绝</button>';
            break;

            case 5:
              buttons += '<button onclick="window.interaction.unDeleteUser(\''+ userId +'\');">取消踢出</button>'
            break

            default:
          }
          return buttons;
      }

      function showUserList(){
        window.interaction.getUsersList({
          success: function(users){
              var sHTML = '';
              for(var i = 0; i < users.length; i++){
                sHTML += '<tr>\
                  <td>'+  users[i].third_party_user_id +'</td>\
                  <td>'+ statusMapping(users[i].status) +'</td>\
                  <td>'+ switchButton(users[i].status,users[i].third_party_user_id) +'</td>\
                </tr>';
              }
              $('#user-list').html(sHTML);
          },
          update: showUserList
        })
      }

      function showBlackList(){
        window.interaction.getBlackList({
          success: function(list){

            var sHTML = '';
            for(var i = 0; i < list.length; i++){
              sHTML += '<tr>\
                <td>'+  list[i] +'</td>\
                <td>被踢出</td>\
                <td><button onclick="unDeleteUser(\''+ list[i] +'\');">取消踢出</button></td>\
              </tr>';
            }
            $('#black-list').html(sHTML);

          },
          fail: function(reason){

          },
          update: showBlackList
        });
      }

      function setBroadCast(b){
          layer.prompt({title: '请输入直播房间ID',value:'lss_2de8530c', formType: 3}, function(roomId, index){
            if(roomId){
              if(b){
                window.interaction.startBroadCast(roomId);
              }else{
                window.interaction.stopBroadCast(roomId);
              }
            }
            layer.close(index);
          });
      }

      function init(){
        if(window.interaction.getStreamId && window.interaction.getStreamId()){
          alert('你已经在互动!');
          return
        }
        /**
         *  注册ready事件
         */
         Vhall.ready(function(){

           //TODO
           window.interaction = new VhallInteraction({
              inavId:$('#inavId').val(),
              videoNode:"my-interaction",
              conf:{
                resolution:'180'
              }
           });

           window.interaction.onEnter(function(userId){
              alert('用户[' + userId + ']进入房间');
           });

           window.interaction.onQuit(function(userId){
              alert('用户[' + userId + ']离开房间');
           });

           /**
            * 监听用户申请上麦事件,处理方式一,调对话框,把选择权给用户
            * @param  {object} event 回调事件参数,包含一个属性userId,和两个方法:consent 、 reject
            */
           window.interaction.onApply(function(event){
              layer.confirm('用户:[' + event.userId + ']申请上麦,同意吗?',{
                btn: ['同意','拒绝','忽略']
              },function(index){
                event.consent();
                layer.close(index);
              },function(){
                event.reject();
              });
           }); 

            //监听用户申请上麦事件,处理方式二, 直接上麦
           // window.interaction.onApply(function(event){
           //      event.consent();
           // });

           //显示用户列表
           showUserList();
           //显示黑名单列表(被踢出用户列表)
           showBlackList();
           
         });
         
        /**
         * 初始化SDK配置
         */
          Vhall.config({
            appId : $('#appId').val(),//应用 ID ,必填
            accountId :'Master',//第三方用户唯一标识,必填
            token:'vhall',//token必填
          });  
      }

      function muteAudio(obj){

        window.interaction.muteAudio();
        console.info(window.interaction.isAudioMuted());
        if(window.interaction.isAudioMuted()){
          obj.value = '打开声音';
        }else{
          obj.value = '关闭声音';
        }
        
      }

      function muteVideo(obj){
        window.interaction.muteVideo();
        console.info(window.interaction.isVideoMuted());
        if(window.interaction.isVideoMuted()){
          obj.value = '打开画面';
        }else{
          obj.value = '关闭画面';
        }
      }

      function start(){
        if(window.interaction){
          window.interaction.exit();
          setTimeout(function(){
             init();
           },3000);
         
        }
      }

      function setSettings(){

        window.interaction.getDevices(function(devices){
          var mics = devices.mics,
              cameras = devices.cameras;
              var html = '\
                      <div class="confs-title">\
                        <div class="confs-title-item">摄像头:</div>\
                        <div class="confs-title-item">麦克风:</div>\
                        <div class="confs-title-item">分辨率:</div>\
                        <div class="confs-title-item">帧率:</div>\
                      </div>\
                      <div class="confs-info">\
                        <div class="confs-info-item">\
                          <select class="form-control" id="camera"  >\
                            {{cameras}}\
                            <option value="">不使用摄像头</option>\
                          </select>\
                        </div>\
                        <div class="confs-info-item">\
                          <select class="form-control" id="mic"  >\
                            {{mics}}\
                            <option value="">不使用麦克风</option>\
                          </select>\
                        </div>\
                        <div class="confs-info-item">\
                          <select class="form-control" id="sizes"  >\
                            {{sizes}}\
                          </select>\
                        </div>\
                        <div class="confs-info-item">\
                          <select class="form-control" id="rates" >\
                            {{rates}}\
                          </select>\
                        </div>\
                      </div>\
                      <div class="settings-action-btn">\
                        <div><button id="btn-settings-ok" class="btn btn-primary btn-small">确定</button></div>\
                        <div><button id="btn-settings-cancel" class="btn-small btn-white">取消</button></div>\
                      </div>';

              var camerasOption = '', 
                  micsOption = '' ,
                  sizesOption = '',
                  ratesOption = '',
                  selected = '';
              var sizes = window.interaction.getVideoSize(),
                  rates =window.interaction.getVideoFrameRate();
              for(var i = 0; i < cameras.length; i++){
                if(i==0){
                  selected = 'selected="selected"';
                }
                camerasOption += '<option  value="'+ cameras[i].deviceId +'">'+ cameras[i].label +'</option>';
              }
              for(var j = 0; j < mics.length; j++){
                if(i==0){
                  selected = 'selected="selected"';
                }
                micsOption += '<option  value="'+ mics[j].deviceId +'">'+ mics[j].label +'</option>';
              }
              for(var x = 0; x < sizes.length; x++){
                if(i==0){
                  selected = 'selected="selected"';
                }
                sizesOption += '<option value="'+ JSON.stringify( sizes[x].size ) +'">'+ sizes[x].discription +'</option>';
              }
              for(var y = 0; y < rates.length; y++){
                if(i==0){
                  selected = 'selected="selected"';
                }
                ratesOption += '<option value="'+ JSON.stringify( rates[y].rate )+'">'+ rates[y].discription +'</option>';
              }
              html = html.replace(/{{cameras}}/g,camerasOption);
              html = html.replace(/{{mics}}/g,micsOption);
              html = html.replace(/{{sizes}}/g,sizesOption);
              html = html.replace(/{{rates}}/g,ratesOption);
              var isEmpty = $('#settings').html().trim().length == 0;
              if(isEmpty){
                 $('#settings').html(html);
              }
              $('#settings').show();
              $('#btn-settings-ok').off('click');
              $('#btn-settings-ok').on('click',function(){
                changeSettings();
                $('#settings').hide();
              });
              //设置按钮
              $('#btn-settings-cancel').off('click');
              $('#btn-settings-cancel').on('click',function(){
                $('#settings').hide();
              });

        });
      }


      function changeSettings(){
        var conf = window.interaction.getSetting();
        var v = $('#camera').val();
        var a = $('#mic').val();
        conf.video = v ? { deviceId: v } : false;
        conf.audio = a ? { deviceId: a } : false;
        conf.videoSize =  JSON.parse($('#sizes').val());
        conf.videoFrameRate =  JSON.parse($('#rates').val());
        console.dir(conf);
        window.interaction.changeSetting({
          conf:conf
        });
      }

  </script>
</html>

其他相关文档

PC SDK如何进行互动    

使用微吼云PC SDK,只需简单几步,就能快速接入互动直播服务。

跟随本文的步骤,即使你对C++/Qt语言不熟悉,也完全可以做到。

其中,一、二步为准备工作,第三步为代码接入,下面分步来说明。

一.接入前准备

  1. 注册微吼开发者账号

  2. 创建应用 获取AppID

  3. 设置应用Bundle ID

  4. 获取互动房间ID roomId

  5. 获取互动房间包含各种权限的access_token

  1. 获取旁路直播房间ID liveRoomId

时序图

注:虚线框部分请根据自身产品需求决定流程是否进行。

二.开发环境搭建

1.安装Visual Studio 2017 2.下载安装Qt5.9.5

下载文件选择如图

安装中组件选择,如图

3.将Qt安装路径添加到开发机环境变量中,如图;

4.在Visual Studio 2017进行Qt关联设置

运行Visual Studio 2017,点击工具栏【工具】>【扩展和更新】,如图:

在弹出对话框中选择【联机】>【搜索 Qt】>【单击下载】,如图:

安装完成后重启Visual Studio 2017配置 Qt 目录,选择【Qt VS Tools 菜单项】>【Qt Options】>【add】>【选择 Qt 安装目录】,如图:

然后点击【OK】按钮,如图,则添加成功: 然后点击【OK】,则完成Qt关联;

三.引入sdk文件

接下来,我们正式接入sdk: 1.工程项目添加头文件目录:具体操作右击项目工程名称,属性》C/C++》常规》附加包含目录中添加VhallPaasSDK\include所在路径; 2.添加附加依赖项:具体操作右击项目工程名称,属性》连接器》常规》附加库目录中添加VHPaasSDK.lib所在目录;右击项目工程名称,属性》连接器》输入》附加依赖项中添加VHPaasSDK.lib; 4.添加引入头文件

#include "VHPaasInteractionRoom.h"

四.代码对接

至此,你已完成所有准备工作,之后可以根据如下内容描述开始研发工作:

1.创建互动房间以及房间监测者

创建room监视者类如ToolManager,类ToolManager继承VHRoomMonitor,并在类中声明VHPassInteractionRoom类型成员指针:

class  ToolManager :public VHRoomMonitor
{
public:
ToolManager();
~ToolManager();
……
private:
VHPassInteractionRoom* mpVHPaasInteractionRoom = nullptr;
}

2.获取互动房间成员指针

vlive::GetPaasSDKInstance() 创建并初始化互动房间

实例

if (mpVHPaasInteractionRoom == nullptr) {
   mpVHPaasInteractionRoom = GetPaasSDKInstance();
}

3.注册房间监视者RoomMonitor

bool VHPaasRoomImpl::RegisterVHRoomMonitor(VHRoomMonitor* monitor)

参数说明:

参数名称 参数说明 是否必填
monitor 房间事件监测者指针

实例

if (mpVHPaasInteractionRoom == nullptr) {
   mpVHPaasInteractionRoom = GetPaasSDKInstance();
   if (mpVHPaasInteractionRoom) {
       mpVHPaasInteractionRoom->RegisterVHRoomMonitor(this);
   }

}

注意:this指本实例中ToolManager指针,返回值bool类型表示设置成功与否

4.初始化房间成员指针

void VHPaasRoomImpl::InitSDK()

实例

if (mpVHPaasInteractionRoom) {
    mpVHPaasInteractionRoom->InitSDK();
}

注意:初始化话接口必须在main函数中调用。

5.登录互动业务服务器

bool VHPaasRoomImpl::LoginHVRoom(const std::string& accesstoken, const std::string& appid, const std::string& thrid_user_id, const std::string& roomid) 登录互动业务服务器.登录成功之后即可加入“互动媒体房间

参数说明:

参数名称 参数说明 是否必填
accesstoken 包含各种权限的access_token
appid 获取AppID
thrid_user_id 登录账号
roomid 互动房间id

实例

bool bRet = ToolManager::GetInstance()->GetPaasSDK()->LoginHVRoom(accesstoken.toStdString(), appid.toStdString(), thrid_user_id.toStdString(), roomid.toStdString());
if (bRet) {

}
else {
    ui.label_notice->setText(QStringLiteral("登录失败,请重试.."));
}

注意:登录成功或者失败将通过如下回调实现,我们只需要重写即可: 1.房间登录失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData),其中roomEvent值为CustomEvent_Login; 2.房间登录成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData),其中roomEvent值为CustomEvent_Login; 3.返回值bool类型,表示请求发送成功与否;

6.登出互动服务处理

void VHPaasRoomImpl::LogoutHVRoom() 登出互动服务处理

实例

void WebRtcLiveWdg::slot_OnClose()
{
globalToolManager->GetPaasSDK()->LeaveVHMediaRoom();
globalToolManager->GetPaasSDK()->LogoutHVRoom();
m_bClose = true;
}

7.进入互动媒体房间

virtual void VHPaasRoomImpl::JoinVHMediaRoom() 进入互动媒体房间

实例

void WebRtcLiveWdg::Notice(const CSTDMapParam &mapParam) {
std::map<QString, QString>::const_iterator iter = mapParam.find(START_WEB_RTC_LIVE);
if (iter != mapParam.end()) {
    globalToolManager->PostEventToMainThread(new QEventDestoryWnd(CustomEvent_DestoryWnd, 	WND_ID_LOGIN));
    globalToolManager->GetDataManager()->WriteLog("JoinVHMediaRoom %s", __FUNCTION__);
    globalToolManager->GetPaasSDK()->JoinVHMediaRoom();
    ui.widget_title->SetTitleType(eVhallIALiveHost);
    ui.widget_ctrl->InitBtnState(true);
}

}

注意:登录成功或者失败将通过如下回调实现,我们只需要重写即可: 1.房间登录结果,将通过回调VHRoomMonitor::OnWebRtcRoomConnetEventCallback(const VHRoomConnectEnum type)进行反馈,具体详实实现参照ToolManager::OnWebRtcRoomConnetEventCallback(const VHRoomConnectEnum type); 2.远端流退出,将通过回调VHRoomMonitor::OnRemoveRemoteUserLiveStream(const std::wstring& user, const std::string& stream, const VHStreamType type) 进行反馈,具体详实实现参照ToolManager::OnRemoveRemoteUserLiveStream(const std::wstring& user, const std::string& stream, const VHStreamType type) ;

VHRoomConnectEnum说明:

说明
VHRoomConnectEnum::VHRoomConnect_ROOM_CONNECTED 互动房间连接成功
VHRoomConnectEnum::VHRoomConnect_ROOM_ERROR 互动房间连接失败,连接重试超时之后上报次消息
VHRoomConnectEnum::VHRoomConnect_ROOM_DISCONNECTED 网络异常时提示网络已经断开
VHRoomConnectEnum::VHRoomConnect_ROOM_RECONNECTING 正在进行网络重连
VHRoomConnectEnum::VHRoomConnect_RE_CONNECTED 网络重连已经恢复
VHRoomConnectEnum::VHRoomConnect_ROOM_RECOVER 网络已经恢复
VHRoomConnectEnum::VHRoomConnect_ROOM_NETWORKCHANGED 网络发生变化
VHRoomConnectEnum::VHRoomConnect_ROOM_NETWORKRECOVER 网络已经恢复
VHRoomConnectEnum::VHRoomConnect_ROOM_MIXED_STREAM_READY 互动房间旁路布局使能事件,当接收到此消息之后SDK端才能进行旁路与大画面设置

参数说明:

参数名称 参数说明 是否必填
user 用户账号
stream 流名称
type 流类型

参数VHStreamType各个值详细说明

说明
VHStreamType::VHStreamType_PureAudio 纯音频数据
VHStreamType::VHStreamType_PureVideo 纯视频数据
VHStreamType::VHStreamType_AVCapture 音视频数据
VHStreamType::VHStreamType_Desktop 桌面共享数据
VHStreamType::VHStreamType_MediaFile 插播视频数据
VHStreamType::VHStreamType_Preview_Video 预览视频数据

8.退出互动媒体房间

virtual void VHPaasRoomImpl::LeaveVHMediaRoom() 退出互动媒体房间

实例

void WebRtcLiveWdg::slot_OnClose()
{
globalToolManager->GetPaasSDK()->LeaveVHMediaRoom();
globalToolManager->GetPaasSDK()->LogoutHVRoom();
m_bClose = true;
}

9.获取账号权限

virtual Permission VHPaasRoomImpl::GetPermission() 获取当前用户权限

返回值(Permission)数说明:

参数名称 参数说明
Permission::kick_inav 踢出互动房间 / 取消踢出互动房间
Permission::kick_inav_stream 踢出路流 / 取消踢出流
Permission::publish_inav_another 推旁路直播 / 结束推旁路直播
Permission::apply_inav_publish 申请上麦
Permission::publish_inav_stream 推流
Permission::askfor_inav_publish 邀请推流
Permission::audit_inav_publish 审核申请上麦

实例

Permission per = globalToolManager->GetPaasSDK()->GetPermission();
//ui.widget_ctrl->initToSpeakBtnState(per.apply_inav_publish);//申请上麦
ui.widget_ctrl->ShowPublishInavAnother(per.publish_inav_another);//旁路直播
ui.widget_ctrl->intShowStartLive(per.publish_inav_stream);//推流 /上麦
ui.widget_ctrl->SetToSpeakBtnState(!per.apply_inav_publish);//申请上麦

10.获取设备列表

void VHPaasRoomImpl::GetCameraDevices(std::listvhall::VideoDevProperty &cameras) 获取摄像头列表 void VHPaasRoomImpl::GetMicDevices(std::listvhall::AudioDevProperty &micDevList) 获取麦克风列表 void VHPaasRoomImpl::GetPlayerDevices(std::listvhall::AudioDevProperty &playerDevList) 获取扬声器列表 bool VHPaasRoomImpl::IsSupported(const std::string& devGuid, VideoProfileIndex index) 判断所选设备是否支持所选分辨率 bool VHPaasRoomImpl::IsSupported(std::shared_ptrvhall::VideoDevProperty& dev, int iProfileIndex) 判断所选设备是否支持所选分辨率

参数说明:

参数名称 参数说明 是否必填
cameras 摄像机设备列表
micDevList 麦克风设备列表
playerDevList 播放器设备列表
devGuid 需要检测的摄像机设备ID
index 所支持的分辨率类型
dev 需要检测的摄像机设备信息
iProfileIndex 所支持的分辨率类型(取值范围:VideoProfileIndex)

实例

void VhallIALiveSettingDlg::init()
{
mbEnableUserSelectCamera = false;
mbEnableUserSelectMic = false;
mbEnableUserSelectPlayer = false;

VHPassInteractionRoom* pInterActionRoom = globalToolManager->GetPaasSDK();
if (pInterActionRoom ) {

	mcameraList.clear();
	pInterActionRoom->GetCameraDevices(mcameraList);

	//摄像设备显示
	ui.comboBox_cameraSelect->clear();
	std::list<vhall::VideoDevProperty>::iterator it = mcameraList.begin();

	mbEnableUserSelectCamera = (mcameraList.size()>0);
	for (; it != mcameraList.end(); it++) {
		//CameraInfo info(QString::fromStdString(it->mDevId), it->mIndex, it->mDevFormatList);
		ui.comboBox_cameraSelect->addItem(QString::fromStdString(it->mDevName), QVariant::fromValue(*it));
        mLastCameraIndex = 0;
	}
	ui.comboBox_cameraSelect->setCurrentIndex(mLastCameraIndex);

	//麦克风显示
	std::list<vhall::AudioDevProperty> micList;
	pInterActionRoom->GetMicDevices(micList);
	mbEnableUserSelectMic = (micList.size()>0);
	ui.comboBox_micDev->clear();

    std::list<vhall::AudioDevProperty>::iterator itMic = micList.begin();
    while (itMic != micList.end()) {
		CameraInfo info(QString::fromStdString(itMic->mDevGuid), itMic->mIndex);
        ui.comboBox_micDev->addItem(QString::fromStdString(itMic->mDevName), QVariant::fromValue(info));
        mLastMicIndex = 0;
        itMic++;
    }
	ui.comboBox_micDev->setCurrentIndex(mLastMicIndex);

	//扬声器
	std::list<vhall::AudioDevProperty> playerList;
	pInterActionRoom->GetPlayerDevices(playerList);

	ui.comboBox_playerDev->clear();
	mbEnableUserSelectPlayer = (playerList.size()>0);
    std::list<vhall::AudioDevProperty>::iterator player = playerList.begin();
    while (player != playerList.end()) {
        //QVariant var(QString::fromStdString(player->mDevGuid));
		CameraInfo info(QString::fromStdString(player->mDevGuid), player->mIndex);
        ui.comboBox_playerDev->addItem(QString::fromStdString(player->mDevName), QVariant::fromValue(info));
        mLastPlayerIndex = 0;
        player++;
    }
	ui.comboBox_playerDev->setCurrentIndex(mLastPlayerIndex);
}
}

void VhallIALiveSettingDlg::slot_CurrentCameraSelectChanged(int index) {
ui.comboBox_cameraRatio->clear();
std::shared_ptr<vhall::VideoDevProperty> info = std::make_shared<vhall::VideoDevProperty>();
*info = (ui.comboBox_cameraSelect->currentData().value<vhall::VideoDevProperty>());
for (int profile = VIDEO_PROFILE_120P_0; profile < VIDEO_PROFILE_720P_DESKTOP; profile++) {
	if (globalToolManager->GetPaasSDK()->IsSupported(info, (VideoProfileIndex)profile))
	{
		ui.comboBox_cameraRatio->addItem(mMapProfileIndex[profile], QVariant::fromValue(profile));
	}
}
ui.comboBox_cameraRatio->setCurrentIndex(0);
}

11.获取成员列表

virtual void VHPaasRoomImpl::AsynGetVHRoomMembers() SDK互动房间人员列表 virtual void VHPaasRoomImpl::GetKickInavList() 获取被踢出互动房间人列表

注意:互动房间人员列表成功与否将通过如下回调实现,我们只需要重写即可: 1.获取成员列表成功,将通过回调VHRoomMonitor::OnGetVHRoomMembers(const std::string& msg, std::list& members)进行反馈,具体详实实现参照ToolManager::OnGetVHRoomMembers(const std::string& msg, std::list& members); 2.获取被踢出成员列表成功,将通过回调VHRoomMonitor::OnGetVHRoomKickOutMembers(std::list& members)进行反馈,具体详实实现参照ToolManager::OnGetVHRoomKickOutMembers(std::list& members); 3.获取成员列表成功,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData);

实例

void WebRtcLiveWdg::slot_showMemberList() {
ClearAllOnLineUser();
ClearAllKickOutUSer();
globalToolManager->GetPaasSDK()->AsynGetVHRoomMembers();
globalToolManager->GetPaasSDK()->GetKickInavList();
}

12.获取互动房间是否连接成功

virtual bool VHPaasRoomImpl::IsVHMediaConnected() 获取互动房间是否连接成功

实例

……
    if (!globalToolManager->GetPaasSDK()->IsVHMediaConnected()) {
        QString msg;
        msg = QString::fromWCharArray(L"互动房间连接失败,暂不能开始直播,请稍后再试");
 ……
        return;
    }
……

13.获取流ID

virtual std::string VHPaasRoomImpl::GetUserStreamID(const std::wstring user_id, VHStreamType streamType) 获取流ID

参数说明:

参数名称 参数说明 是否必填
user_id 账号id
streamType 流类型

实例

void WebRtcLiveWdg::slot_setToSpeaker(QString user_id) {
std::string id = globalToolManager->GetPaasSDK()->GetUserStreamID(user_id.toStdWString(), VHStreamType_AVCapture);
if (id.length() > 0) {
    int nRet = globalToolManager->GetPaasSDK()->SetMainView(id);
    if (nRet == VhallLive_SERVER_NOT_READY) {
        FadeOutTip(QStringLiteral("服务器端混流未就绪,请稍后再试"));
    }
}

}

14.麦克风相关

void VHPaasRoomImpl::SetUseMicIndex(int index) 设置使用的麦克风 virtual int VHPaasRoomImpl::GetCurrentMicVol() 获取当前使用的麦克风音量 virtual bool VHPaasRoomImpl::SetCurrentMicVol(int vol) 设置当前使用的麦克风音量 virtual bool VHPaasRoomImpl::CloseMic() 关闭麦克风 virtual bool VHPaasRoomImpl::OpenMic() 打开麦克风 virtual bool VHPaasRoomImpl::IsMicOpen() 打开摄像头 virtual int VHPaasRoomImpl::OperateRemoteUserAudio(std::wstring user_id, bool close = false) 打开、关闭远端用户设备声音

参数说明:

参数名称 参数说明 是否必填
index 麦克风的索引
vol 改变麦克风的音量值

15.摄像头相关

void VHPaasRoomImpl::SetUseCameraGuid(std::string guid) 设置使用的摄像头 virtual bool VHPaasRoomImpl::CloseCamera() 关闭摄像头 virtual bool VHPaasRoomImpl::OpenCamera() 打开摄像头 virtual bool VHPaasRoomImpl::IsCameraOpen() 摄像头是否是打开状态 virtual int VHPaasRoomImpl::OperateRemoteUserVideo(std::wstring user_id, bool close = false) 打开、关闭远端用户设备视频

参数说明:

参数名称 参数说明 是否必填
guid 摄像机设备id

16.扬声器相关

void VHPaasRoomImpl::SetUserPlayIndex(int index) 设置使用的扬声器 virtual int GetCurrentPlayVol() 获取当前使用的扬声器音量 virtual bool SetCurrentPlayVol(int vol) 设置当前使用的扬声器音量

参数说明:

参数名称 参数说明 是否必填
index 扬声器的索引
vol 改变扬声器的音量值

17.是否正在进行数据源采集/推流

virtual bool VHPaasRoomImpl::IsCapturingStream(VHStreamType streamType) 当前采集类型是否正在进行本地数据源采集

18.是否正在推流

virtual bool IsPushingStream(VHStreamType streamType) 当前采集类型是否正在推流 virtual bool VHPaasRoomImpl::IsUserPushingDesktopStream() 当前互动房间是否存在桌面共享视频流 virtual bool VHPaasRoomImpl::IsUserPushingMediaFileStream()当前互动房间是否存在插播视频流

19.控制本地摄像头音视频采集

int VHPaasRoomImpl::StartLocalCapture(VideoProfileIndex index) 开始摄像头\麦克风采集 void VHPaasRoomImpl::StopLocalCapture() 停止摄像头\麦克风采集

参数说明:

参数名称 参数说明 是否必填
index 摄像机设备支持分辨率的索引

参数VHCapture各个值详细说明

说明
VHCapture::VHCapture_OK 采集成功
VHCapture::VHCapture_ACCESS_DENIED 采集失败
VHCapture::VHCapture_VIDEO_DENIED 视频采集失败
VHCapture::VHCapture_AUDIO_DENIED 音频采集失败
VHCapture::VHCapture_STREAM_SOURCE_LOST 摄像设备采集过程中采集失败 ,SDK将结束采集与推流
VHCapture::VHCapture_PLAY_FILE_ERR 播放文件失败

返回值VhallLiveErrCode类型说明:

返回值 返回值描述
VhallLiveErrCode::VhallLive_OK 请求成功
VhallLiveErrCode::VhallLive_ROOM_DISCONNECT 房间已经断开或网络异常
VhallLiveErrCode::VhallLive_NO_OBJ 调用的对象为空
VhallLiveErrCode::VhallLive_NO_DEVICE 没有音视频设备
VhallLiveErrCode::VhallLive_NO_PERMISSION 无权限
VhallLiveErrCode::VhallLive_MUTEX_OPERATE 互斥操作,桌面共享和插播一个房间只能一个成员触发
VhallLiveErrCode::VhallLive_NOT_SUPPROT 不支持的分辨率
VhallLiveErrCode::VhallLive_PARAM_ERR 参数错误
VhallLiveErrCode::VhallLive_IS_PROCESSING 正在处理中
VhallLiveErrCode::VhallLive_SERVER_NOT_READY 服务器端未就绪
VhallLiveErrCode::VhallLive_ERROR 其它错误

注意:开启本地摄像头音视频采集成功失败通过如下回调实现,我们只需要重写即可: 1.VHRoomMonitor::OnOpenCaptureCallback(VHStreamType streamType, VHCapture code);具体详实实现参照ToolManager::OnOpenCaptureCallback(VHStreamType streamType, VHCapture code); 2.VHPaasRoomImpl::StartLocalCapture()返回值类型VhallLiveErrCode,表示开启操作结果; 3.VHPaasRoomImpl::StopLocalCapture()返回值bool类型,表示关闭操作成功与否;

实例

void WebRtcLiveWdg::HandleDeviceEvent(QEvent* event) {
	if (event == NULL) {
  	return;
	}
	CustomDevChangedEvent* devEvent = (CustomDevChangedEvent*)event;
	if (devEvent && globalToolManager->GetPaasSDK()) {

    	m_IsPushingLocalStream = globalToolManager->GetPaasSDK()->IsPushingStream(VHStreamType_PureAudio)
        	|| globalToolManager->GetPaasSDK()->IsPushingStream(VHStreamType_PureVideo)
        	|| globalToolManager->GetPaasSDK()->IsPushingStream(VHStreamType_AVCapture);

    	globalToolManager->GetPaasSDK()->StopLocalCapture();
    	mCurCameraDevID = devEvent->cameraDevId;
    	mCurMicDevID = devEvent->micDevId;
    	mCurPlayerID = devEvent->playerDevId;

    	globalToolManager->GetPaasSDK()->SetUseMicIndex(devEvent->micIndex);
    	globalToolManager->GetPaasSDK()->SetUseCameraGuid(devEvent->cameraDevId.toStdString());

    	globalToolManager->GetPaasSDK()->SetUserPlayIndex(devEvent->playerIndex);
	globalToolManager->GetPaasSDK()->StartLocalCapture((VideoProfileIndex)devEvent->cameraIndex);

	}
}


/*本地打开采集设备,包括摄像头、桌面共享、开始插播视频回调*/
void ToolManager::OnOpenCaptureCallback(VHStreamType streamType, VHCapture code) {
 if (nullptr != mWndManager){
	 CBaseWnd* pRtcLiveWdg = mWndManager->FindWnd(WND_ID_WEBRTC_MAIN);

	 //QString join_uid = QString::number(mLocalStream->mId);
	 //TRACE6("%s  mLocalStream ACCESS_ACCEPTED\n", __FUNCTION__);
	 QCoreApplication::postEvent(pRtcLiveWdg, new QEventStream(CustomEvent_GetLocalStreamSuc, "", "", streamType, code));
 } 
}

void WebRtcLiveWdg::customEvent(QEvent *event) {
if (event == nullptr) {
    return;
}
int type = event->type();
globalToolManager->GetDataManager()->WriteLog("Enter %s type:%d", __FUNCTION__, type);
switch (type) {
case CustomEvent_GetLocalStreamSuc:
    HandleGetStream(event);
	……

}

void WebRtcLiveWdg::HandleGetStream(QEvent* event) { if (event == NULL) { return; } QEventStream msgEvent = (QEventStream)event;

if (msgEvent->mStreamType< VHStreamType_Desktop) {
    if (msgEvent->mIVHCapture != VHCapture_OK) {
        QString msg = QStringLiteral("设备打开失败,请检测当前采集设备");
        FadeOutTip(msg);
        VhallRenderWdg* render = GetRenderWnd(globalToolManager->GetDataManager()->GetThridPartyUserId());
        if (render) {
            render->SetViewState(RenderView_NoCamera);
        }
        if (mCurMainShowUid == globalToolManager->GetDataManager()->GetThridPartyUserId()) {
            ui.widget_mainView->SetViewState(RenderView_NoCamera);
        }
    }
    else {
        HandleGetLocalStream(msgEvent);
    }
}
else if (VHStreamType_Desktop == msgEvent->mStreamType) {
    if (msgEvent->mIVHCapture != VHCapture_OK) {
        QString msg = QStringLiteral("桌面采集失败");
        FadeOutTip(msg);
    }
    else {
        HandleGetDesktopStream(msgEvent);
    }
}
else if (VHStreamType_MediaFile == msgEvent->mStreamType) {
    if (msgEvent->mIVHCapture != VHCapture_OK) {
        QString msg = QStringLiteral("插播文件采集失败");
        FadeOutTip(msg);
    }
    else {
        HandleGetMediaFileStream(msgEvent);
    }
}

}

20.开始本地音视频数据推流

void VHPaasRoomImpl::StartPushLocalStream() 开始本地音视频数据推流

注意:推流成功或者失败通过如下回调实现,我们只需要重写即可: 1.推流成功,将通过回调VHRoomMonitor::OnPushStreamSuc(VHStreamType streamType, std::string& streamid)进行反馈,具体详实实现参照ToolManager::OnPushStreamSuc(VHStreamType streamType, std::string& streamid); 2.推流失败,将通过回调VHRoomMonitor::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg )进行反馈,具体详实实现参照ToolManager::::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg );

21.停止本地音视频推流

int VHPaasRoomImpl::StopPushLocalStream() 停止摄像头数据推流

注意:结束推流成功或者失败通过如下回调实现,我们只需要重写即可: 1.结束推流成功或者失败,将通过回调VHRoomMonitor::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg)进行反馈,具体详实实现参照ToolManager::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg); 2.返回值类型VhallLiveErrCode,表示操作结果;

参数说明:

参数名称 参数说明 是否必填
streamType 被操作流类型
code 错误码
msg 错误类型描述

22.设置桌面共享推流最大宽高

virtual void VHPaasRoomImpl::SetDesktopPushStreamParam(int width, int height, int fps = 20, int bitRate = 600) 设置桌面共享推流最大宽高,如果不调用此接口,默认推流最大按照1280 * 720 分辨率推流

注意:设置桌面共享推流最大宽高后,桌面共享推流宽高都小于以上设置时,按照给定分辨率推流,宽或高大于推流最大宽或高时,内部按照给定最大宽高进行等比例缩放后推流

参数说明:

参数名称 参数说明 是否必填
width 桌面共享最大推流宽度
height 桌面共享最大推流高度
fps 桌面共享推流帧率
bitRate 桌面共享推流比特率

23.控制桌面共享采集

virtual int VHPaasRoomImpl::StartDesktopCapture(int index, int width, int height) 开始桌面共享采集 virtual void VHPaasRoomImpl::StopDesktopCapture() 停止桌面共享采集

参数说明:

参数名称 参数说明 是否必填
index 共享桌面索引
width 被共享桌面宽度
height 被共享桌面高度

注意:开启桌面共享成功失败通过如下回调实现,我们只需要重写即可: 1.VHRoomMonitor::OnOpenCaptureCallback(VHStreamType streamType, VHCapture code);处理和《19.控制本地摄像头音视频采集》相同; 2.返回值类型VhallLiveErrCode,表示操作结果;

24.开始桌面共享采集推流

virtual bool VHPaasRoomImpl::StartPushDesktopStream() 开始桌面共享采集推流

注意:推流成功或者失败通过如下回调实现,我们只需要重写即可: 1.推流成功,将通过回调VHRoomMonitor::OnPushStreamSuc(VHStreamType streamType, std::string& streamid)进行反馈,具体详实实现参照ToolManager::OnPushStreamSuc(VHStreamType streamType, std::string& streamid); 2.推流失败,将通过回调VHRoomMonitor::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg )进行反馈,具体详实实现参照ToolManager::::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg );处理和《20.开始本地音视频数据推流》相同; 3.返回值bool类型,表示请求发送成功与否;

25.停止桌面共享采集推流

virtual int VHPaasRoomImpl::StopPushDesktopStream() 停止桌面共享采集推流 注意:结束推流成功或者失败通过如下回调实现,我们只需要重写即可: 1.结束推流成功或者失败,将通过回调VHRoomMonitor::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg)进行反馈,具体详实实现参照ToolManager::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg);处理和《21.停止本地音视频推流》相同; 2.返回值类型VhallLiveErrCode,表示操作结果;

26.设置插播文件推流最大宽高

virtual void VHPaasRoomImpl::SetMediaFilePushStreamParam(int width, int height, int fps = 20, int bitRate = 600) 设置插播文件推流最大宽高,如果不调用此接口,默认推流最大按照960 * 540 分辨率推流

注意:设置插播文件推流最大宽高后,插播文件推流宽高都小于以上设置时,按照给定分辨率推流,宽或高大于推流最大宽或高时,内部按照给定最大宽高进行等比例缩放后推流

参数说明:

参数名称 参数说明 是否必填
width 插播文件最大推流宽度
height 插播文件最大推流高度
fps 插播文件推流帧率
bitRate 插播文件推流比特率

27.查看当前播放的文件是否支持音频或视频格式

virtual bool VHPaasRoomImpl::IsSupportMediaFormat(CaptureStreamAVType type) 查看当前播放的文件是否支持音频或视频格式,用于判断当前播放的是音频文件还是视频文件

CaptureStreamAVType类型说明:

值类型说明
CaptureStreamAVType::CaptureStreamAVType_Audio 音频类型
CaptureStreamAVType::CaptureStreamAVType_Video 视频频类型

28.控制插播文件采集

virtual int VHPaasRoomImpl::StartPlayMediaFile(std::string filePath) 开始插播文件 virtual void VHPaasRoomImpl::StopMediaFileCapture() 停止插播文件

参数说明:

参数名称 参数说明 是否必填
filePath 插播文件路径

注意:开启桌面共享成功失败通过如下回调实现,我们只需要重写即可: 1.VHRoomMonitor::OnOpenCaptureCallback(VHStreamType streamType, VHCapture code);处理和《19.控制本地摄像头音视频采集》相同; 2.返回值类型VhallLiveErrCode,表示操作结果;

29.渲染媒体数据流

virtual bool VHPaasRoomImpl::StartRenderStream(const std::wstring& user, void* wnd, vlive::VHStreamType streamType) 开始渲染媒体数据流

参数说明:

参数名称 参数说明 是否必填
user 用户id
streamType 数据类型

注意:返回值bool类型,表示渲染成功与否;

30.文件插播相关参数获取

virtual const long long VHPaasRoomImpl::MediaFileGetMaxDuration() 插播文件总时长 virtual const long long VHPaasRoomImpl::MediaFileGetCurrentDuration() 插播文件当前时长 virtual int VHPaasRoomImpl::MediaGetPlayerState() 插播文件的当前状态(MEDIA_FILE_PLAY_STATE)

MEDIA_FILE_PLAY_STATE类型说明:

值描述说明
MEDIA_FILE_PLAY_STATE::PLAY_STATE_NothingSpecial 未操作
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Opening 正在打开文件
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Buffering 正在加载文件
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Playing 正在播放中
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Paused 已暂停
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Stopped 已停止
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Ended 播放完毕
MEDIA_FILE_PLAY_STATE::PLAY_STATE_Error 播放失败

31.插播文件播放控制

virtual void VHPaasRoomImpl::MediaFilePause() 插播文件暂停处理 virtual void VHPaasRoomImpl::MediaFileResume() 插播文件恢复处理 virtual void VHPaasRoomImpl::MediaFileSeek(const unsigned long long& seekTimeInMs) 插播文件快进处理

参数说明:

参数名称 参数说明 是否必填
user 用户id
seekTimeInMs 快进后新的开始播放起始点

32.开始插播文件推流

virtual int VHPaasRoomImpl::StartPushMediaFileStream() 开始插播文件推流

注意:推流成功或者失败通过如下回调实现,我们只需要重写即可: 1.推流成功,将通过回调VHRoomMonitor::OnPushStreamSuc(VHStreamType streamType, std::string& streamid)进行反馈,具体详实实现参照ToolManager::OnPushStreamSuc(VHStreamType streamType, std::string& streamid); 2.推流失败,将通过回调VHRoomMonitor::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg )进行反馈,具体详实实现参照ToolManager::::OnPushStreamError(VHStreamType streamType, const int codeErr, const std::string& msg );处理和《20.开始本地音视频数据推流》相同; 3.返回值类型VhallLiveErrCode,表示操作结果;

33.停止插播文件推流

virtual void VHPaasRoomImpl::StopPushMediaFileStream() 停止插播文件推流

注意:结束推流成功或者失败通过如下回调实现,我们只需要重写即可: 结束推流成功或者失败,将通过回调VHRoomMonitor::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg)进行反馈,具体详实实现参照ToolManager::OnStopPushStreamCallback(VHStreamType streamType, int code, const std::string& msg);处理和《21.停止本地音视频推流》相同;

34.申请上麦

virtual int VHPaasRoomImpl::ApplyInavPublish() 申请上麦

注意:申请上麦结果会通过如下回调实现,我们只需要重写即可: 1.申请上麦结果,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.返回值类型VhallLiveErrCode,表示操作结果;

35.审核申请上麦

virtual int VHPaasRoomImpl::AuditInavPublish(const std::string& audit_user_id, AuditPublish type) 审核申请上麦

参数说明:

参数名称 参数说明 是否必填
audit_user_id 被审核用户账号
type 审核结果

AuditPublish 类型说明:

值描述说明
AuditPublish::AuditPublish_Accept 同意上麦
AuditPublish::AuditPublish_Refused 拒绝上麦

注意:申请上麦结果会通过如下回调实现,我们只需要重写即可: 1.审核上麦成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.审核上麦失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.返回值类型VhallLiveErrCode,表示操作结果;

36.邀请上麦

virtual int VHPaasRoomImpl::AskforInavPublish(const std::string& audit_user_id) 邀请上麦

参数说明:

参数名称 参数说明 是否必填
audit_user_id 被邀请上麦用户账号

注意:邀请上麦结果会通过如下回调实现,我们只需要重写即可: 1.邀请上麦成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.邀请上麦失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.返回值类型VhallLiveErrCode,表示操作结果;

37.回应邀请上麦状态回执

virtual void VHPaasRoomImpl::UserPublishCallback(PushStreamEvent type, const std::string& stream_id = std::string()) 上/下/拒绝上麦状态回执,用户成功推流后调用服务端API

参数说明:

参数名称 参数说明 是否必填
type 回应状态
stream_id 回应的流ID

PushStreamEvent类型说明:

值描述说明
PushStreamEvent::PushStreamEvent_Upper 上麦(默认)
PushStreamEvent::PushStreamEvent_Lower 下麦
PushStreamEvent::PushStreamEvent_Refused 拒绝上麦

注意:回应邀请上麦的结果会通过如下回调实现,我们只需要重写即可: 1.回应邀请上麦成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.回应邀请上麦失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData);

38.踢出流/取消踢出流

virtual int VHPaasRoomImpl::KickInavStream(const std::string& kick_user_id, KickStream type) 踢出流/取消踢出流

参数说明:

参数名称 参数说明 是否必填
audit_user_id 被踢出/取消踢出用户流id
type 操作类型

KickStream说明:

类型描述
KickStream::KickStream_KickOutUser 踢出流
KickStream::KickStream_CancelKickOutUser 取消踢出用户流id

注意:操作结果会通过如下回调实现,我们只需要重写即可: 1.操作结果成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.操作结果失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.函数返回值类型VhallLiveErrCode;

39.踢出互动房间/取消踢出互动房间

virtual int VHPaasRoomImpl::KickInav(const std::string& kick_user_id, KickStream type) 踢出互动房间/取消踢出互动房间 作用于进入房间

参数说明:

参数名称 参数说明 是否必填
kick_user_id 回应状态
type 操作类型

注意:操作结果会通过如下回调实现,我们只需要重写即可: 1.操作结果成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.操作结果失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.函数返回值类型VhallLiveErrCode;

40.开启旁路直播

virtual int VHPaasRoomImpl::StartPublishInavAnother(std::string live_room_id, LayoutMode layoutMode, std::string stream_id = std::string() , BROAD_CAST_DPI dip = BROAD_CAST_DPI_UHD, FRAME_FPS fps = FRAME_FPS_20, BITRATE bitrate = BITRATE_1000) 开启旁路直播

参数说明:

参数名称 参数说明 是否必填
live_room_id 被设置的直播房间id
layoutMode 旁路显示布局类型
stream_id 大画面流id
dip 旁路直播混流端分辨率
fps 帧率
bitrate 码率
bitrate 比特率

注意:操作结果会通过如下回调实现,我们只需要重写即可: 1.操作结果成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.操作结果失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.函数返回值类型VhallLiveErrCode;

41.停止旁路直播

virtual int VHPaasRoomImpl::StopPublishInavAnother(std::string live_room_id) 停止旁路直播

参数说明:

参数名称 参数说明 是否必填
live_room_id 被设置的直播房间id

注意:操作结果会通过如下回调实现,我们只需要重写即可: 1.操作结果成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.操作结果失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.函数返回值类型VhallLiveErrCode;

42.设置旁路直播大画面

virtual int VHPaasRoomImpl::SetMainView(std::string stream_id) 当开启旁路直播之后,可以将某个用户的流设置在混流画面的主画面当中

参数说明:

参数名称 参数说明 是否必填
stream_id 大画面流id

注意:操作结果会通过如下回调实现,我们只需要重写即可: 1.操作结果成功,将通过回调VHRoomMonitor::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData) 进行反馈,具体详实实现参照ToolManager::OnRoomSuccessedEvent(RoomEvent roomEvent, std::string userData); 2.操作结果失败,将通过回调VHRoomMonitor::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData)进行反馈,具体详实实现参照ToolManager::OnRoomFailedEvent(RoomEvent roomEvent, int respCode, const std::string& msg, std::string userData); 3.函数返回值类型VhallLiveErrCode;

Demo使用指南    

iOS Demo    

1 下载地址

https://github.com/vhall/VHYun_SDK_iOS

2 填写app_id

进入iOS SDK Demo,弹出输入app_id对话框,对话框中填写应用管理页面获取到的app_id。如何获取app_id

3 设置

点击设置,进入设置页面,可进行基础设置和互动设置

基础设置:填写第三方用户ID(是开发者自有用户系统里用户的唯一标识)和access_token。详情参考:第三方用户ID(third_party_user_id)是什么?获取access_token

互动设置:填写互动房间ID、旁路房间ID、推流参数

互动房间ID、旁路房间ID可通过控制台/接口/API在线测试工具的方式进行获取,具体参考获取互动直播房间ID获取旁路房间ID

推流参数支持默认、标清、高清、超清、自定义四种配置

互动设置 互动设置

规格说明:

默认:直接获取控制台配置

标清:分辨率 192*144 码率 200 帧率 30

高清:分辨率 480*360 码率 200 帧率 30

超清:分辨率 960*540 码率 200 帧率 30

自定义:支持手动输入推流配置参数

4 互动直播

互动直播SDK操作权限可通过接口进行配置,可依照需求中选择需要的权限进行配置,具体参考生成access_token

微吼云提供的DEMO支持下麦、申请上麦、房间管理、开启旁路、关闭旁路操作权限,点击互动直播进入互动房间,且默认为已经上麦

互动直播 互动直播

下麦:点击下麦,用户下麦,停止直播

申请上麦:下麦后,点击申请上麦,主持人同意后,可重新上麦

房间管理:支持对房间内的人员进行管理,可对房间内的人进行邀请上麦、下麦和踢出房间操作

开启旁路:开启旁路直播功能的房间,点击开启旁路可将混流画面推送至旁路直播房间

关闭旁路:开启旁路且推流成功后,开启旁路变为关闭旁路,点击关闭旁路停止向旁路直播房间进行推流

互动直播

Android Demo    

1 下载地址

https://github.com/vhall/VHYun_SDK_Android

2 填写app_id和用户ID

进入Android SDK Demo,页面中输入控制台应用管理中获取的app_id和用户ID(是开发者自有用户系统里用户的唯一标识),详情参考:第三方用户ID(third_party_user_id)是什么?获取app_id,填写完毕点击进入,进入操作页面

3 填写互动直播房间ID、旁路房间ID和access_token

互动直播房间ID:参考获取互动直播房间ID

access_token:参考获取access_token

互动设置

点击设置,填写旁路直播ID和推流参数

互动设置

4 互动直播

互动直播SDK操作权限可通过接口进行配置,可依照需求选择需要的权限进行配置,具体参考生成access_token

微吼云提供的Demo支持房间管理、申请上麦、上麦、下麦、开启/关闭旁路直播、开启/关闭视频、开启/关闭音频的操作权限

互动直播

互动直播

申请上麦:点击申请上麦,主持人同意后,可上麦

上麦:有上麦权限,直接点击上麦,即可进行上麦

下麦:点击下麦,用户下麦,停止直播

开启/关闭视频:点击开启视频可采集视频画面,点击关闭视频将无法采集视频画面

开启/关闭音频:点击开启音频可采集声音,点击关闭音频将无法采集声音

开启/关闭旁路:点击开启旁路可将混流画面推送至旁路直播房间;点击关闭旁路停止向旁路直播房间进行推流

房间人员:点击房间人员,可以查看互动房间中的人员,并可对房间中的每个人员进行操作,操作项包含踢出房间、邀请上麦、下麦三种

JS Demo    

主持端

主持端演示地址

https://static.vhallyun.com/jssdk/vhall-jssdk-interaction/test/master_new.html

主持端初始化互动房间

依次填写app_id(如何获取app_id)和互动直播ID(获取互动直播房间ID),点击进入互动房间,则进入互动直播房间

初始化互动房间

主持端开始互动

进入互动直播房间后可在房间内进行设置、上麦、下麦、关闭/开启声音、关闭/开启视频、开启/关闭旁路直播、用户管理等操作。 开始互动

设置:支持对音视频设备进行设置

上麦:有上麦权限,直接点击上麦,即可进行上麦

下麦:点击下麦,用户下麦

开启/关闭视频:点击开启视频可采集视频画面,点击关闭视频将无法采集视频画面

开启/关闭音频:点击开启音频可采集声音,点击关闭音频将无法采集声音

开启/关闭旁路:点击开启旁路,弹出填写旁路直播房间ID的对话框,填写完毕点击确定,即可将混流画面推送至旁路直播房间;点击关闭旁路停止向旁路直播房间进行推流

用户列表:包含用户列表和被踢用户列表,可以查看列表中的所有人员,并可对列表中的人员进行操作,操作项包含踢出房间、邀请上麦、下麦三种

观众端

观众端演示地址

https://static.vhallyun.com/jssdk/vhall-jssdk-interaction/test/guest_new.html?v=20180611

观众端初始化互动房间

依次填写app_id和互动直播ID,点击进入互动房间,则进入互动直播房间

初始化互动房间

观众端开始互动

进入互动直播房间后可在房间内进行上麦、下麦、申请上麦、关闭/开启声音、关闭/开启视频。

开始互动

上麦/下麦:有上麦权限的观众端,点击上麦,即可在观众端进行上麦互动;点击下麦,即下麦停止互动。

申请上麦:不具备上麦权限的观众端,点击请求上麦,向主持端发起请求,主持端收到请求并通过,观众即可上麦进行互动。

关闭/开启声音:点击开启声音可采集声音,点击关闭声音将无法采集声音

关闭/开启画面:点击开启画面可采集视频画面,点击关画面频将无法采集视频画面

PC Demo    

1 下载地址

Demo与应用下载 创建应用 获取AppID
设置应用Bundle ID 如何创建互动房间 如何获取互动房间 获取旁路直播房间

2 进入房间

安装后双击运行Demo,弹出登陆对话框,对话框中填写对应参数,点击【进入房间】,所有账号进入房间默认打开本地摄像头并回显出来;

3 进入房间后主界面介绍

##3.1 权限账号主界面 该账号配置有如下权限:推流(上/下麦)、开启/关闭旁路直播、审核申请上麦、邀请上麦、踢出流(将别人下麦)、踢出/取消踢出;

桌面共享:

点击桌面共享,隐藏Demo主界面,在被共享的桌面显示悬浮的桌面共享操作栏,详情见《桌面共享 》

插播文件:

点击插播文件,则可以进行文件插播的设置,并播放,详情见《插播文件》

成员:

点击成员,则刷新请求成员列表(包括:在线、踢出)

上麦:

点击上麦,则开始本地推流(包括摄像头、音频采集、视频插播、桌面共享)到服务器,推流成功则认为上麦成功,按钮变成“下麦”;

开启旁路直播:

点击开启旁路直播,开启旁路直播功能的房间,开启旁路直播成功后,按钮变成“关闭旁路直播”,同时将混流画面推送至旁路直播房间,详见《开启旁路直播》

摄像设备:

点击摄像设备,可关闭/打开当前被选中正在采集画面的摄像机

麦克风:

点击麦克风,可关闭/打开当前被选中正在采集音频的麦克风

扬声器:

点击扬声器,可关闭/打开当前被选中正在播放声音的扬声器

下麦:

点击主界面下方工具栏红色“下麦”,用户自己下麦,停止直播,下麦成功则按钮变成“上麦” 点击成员列表中推流成员id右侧“下麦”,则是将该成员踢下麦克,对方下麦成功则变成“邀请上麦”

关闭旁路直播:

点击关闭旁路直播,关闭旁路直播功能的房间,关闭旁路直播成功后,按钮变成“开启旁路直播”,详见《关闭旁路直播》

邀请上麦:

点击邀请上麦,则可以邀请被邀请账号上麦,对方同意,按钮变成“下麦”,此时进入房间的各端可以收到有人进入房间并显示对方推送的数据流,详细见《邀请上麦》

踢出:

点击成员列表中成员id右侧“踢出”,则可以把对方踢出房间,同时按钮变成“取消踢出”,对方将退出本房间回到登录页面,对方将无法进入本房间,直到取消踢出后

取消踢出:

点击踢出列表中成员id右侧“取消踢出”,则可以把对方取消踢出房间,同时按钮变成“踢出”,对方可重新进入本房间

设置:

点击主界面右上方小齿轮形状的按钮,即设置按钮,可以对互动相关参数进行设置,详见《设置》

##3.2 权限账号主界面 该账号配置有如下权限:申请上麦;

申请上麦:

点击申请上麦,既可以向具有审核申请上麦的账号发出上麦申请,具有审核申请上麦的账号将弹出申请上麦消息如下

具有审核权限的账号可以“批准”和“不批准”该申请 批准:

点击批准,即同意对方获取上麦的权限,对方收到该回执会自动上麦

不批准:

点击不批准,将拒绝对方获取上麦权限,并给对方以下提醒 ##3.3 权限账号主界面 该账号配置有如下权限:推流(上/下麦);

#4 设置功能介绍 ##4.1设置视频 点击主界面设置按钮【】> 【视频】将得到如下界面:

摄像头:

摄像头下拉列表将自动加载当前pc连接的所有摄像设备,选择摄像设备,分辨率下拉别表框将自动加载当前选中摄像头支持的采集分辨率,点击确定时候,将设置选中的摄像设备进行数据采集

分辨率:

列出当前选中摄像头所支持的采集分辨率,点击确定时候,将设置选中的分辨率进行数据采集

##4.2设置音频 点击主界面设置按钮【】> 【音频】将得到如下界面:

麦克风:

麦克风下拉列表将自动加载当前pc连接的所有麦克风设备,点击确定时候,将设置选中的麦克风设备进行数据采集

扬声器:

扬声器下拉列表将自动加载当前pc连接的所有扬声器设备,点击确定时候,将设置选中的扬声器设备进行声音播放

##4.3设置布局 该设置只有具备开启/关闭旁路直播功能的账号才有,点击主界面设置按钮【】> 【布局】将得到如下界面:

本页设置的是观看端观看的布局画面,点击确定时候,即改变观看端的布局方式

##4.4关于我们 该设置只有具备开启/关闭旁路直播功能的账号才有,点击主界面设置按钮【】> 【关于我们】将看到如下界面:

#5 桌面共享

点击主界面桌面共享按钮【】,如果房间内有人正在桌面共享或者文件插播,则共享失败;如果没有房间内没有用户进行桌面共享和文件插播,将能看到下图,则桌面共享采集成功:

#6 插播文件 点击主界面桌面共享按钮【】,如果房间内有人正在桌面共享或者文件插播,则文件插播失败;如果没有房间内没有用户进行桌面共享和视频插播,将弹出文件选择列表:

插播文件:

点击插播文件,将弹出文件路径选择对话框,选择要插播的文件即可

循环方式:

循环方式列表提供顺序播放、列表循环、单视频循环三种循环方式

开始插播:

点击开始插播,开始插播选择文件

#7 邀请上麦

具有邀请上麦权限的账号邀请普通账号上麦,被邀请者将收到以下邀请信息:

现在上麦:

被邀请者点击现在上麦,则同意邀请,自动开启本地推流

没准备好:

被邀请者点没准备好,则拒绝邀请,超过15s没有选择则认为拒绝邀请

#8 开启/关闭旁路直播 具有开启/关闭旁路直播权限的账号点击主界面开启旁路直播【】或者关闭旁路直播按钮【】,则弹出旁路直播确认对话框:

房间:

旁路直播房间号是可以修改的,如果进入房间时填写错误,可在此修改,否则开启/关闭会提示操作失败

确认:

点击确认,则开启或者关闭当前输入的直播房间,如果开启成功,观看端可正常观看;如果关闭成功,则观看端无法观看

取消:

点击取消,则放弃当前操作

互动API    

API名称 功能
create 创建互动房间
delete 删除房间
disable 封停互动房间
enable 解封房间
lists 互动房间列表
get-status 获取互动房间状态
get-stream 获取互动房间流信息
push-another 发起旁路直播
set-max-screen-stream 旁路直播设置最大屏占比的流
set-layout 旁路直播设置布局
stop-push-another 停止推旁路直播
get-kick-inav-list 获取互动被踢出人列表
reset-kick-inav 恢复互动被踢出人
get-inav-data 获取互动数据
get-inav-room-data 获取互动流量数据
inav-user-list 获取互动房间人员列表

服务定价    

1、计费类型及说明

微吼云互动服务采用预付费&后付费方式,您需要先在微吼云账户预先充值,系统会按照使用的功能进行扣费。

2、计费方式说明

您可以通过控制台查看微吼云互动服务的实际使用量,具体计费方式如下。

计费项 计费方式 付费方式
互动直播在线时长 按时长 后付费
旁路直播 按功能 预付费
直播 按带宽/流量 后付费

2.1 互动直播时长

计费规则

按进入互动房间的在线时长计费,实时进行扣费,按照实际时长乘以单价计费。

计费项 价格(元/千分钟) 付费方式
在线时长 22 后付费

计费周期

实时扣费,每个小时生成消费明细,按自然月出账单,每个月的账单会在次月 1 号生成。

计费公式:

在线时长(分钟) * 单价(元/千分钟)

计费示例:

进入互动房间的在线时长为1000分钟,则收费为 1 * 22 = 22 元

时长消耗示例

互动过程中有人进出房间的情况,以实际在房间的时长为准

如互动房间5人同时互动10分钟,则计费时长为5人*10分钟=50分钟

计费说明

1.实时扣费,请确保账户余额充足,以免影响您的业务

2.2 旁路直播

计费规则

功能开通即进行扣费,按月进行扣费。

计费项 价格(元/月) 付费方式
旁路直播 2000 预付费

计费周期

开启功能即进行扣费,每月出一条消费明细,按自然月出账单。每个月的账单会在次月1号生成。

计费说明

1.功能默认关闭,可在微吼云控制台手动开启。

2.使用旁路直播功能,将会收取相应的功能费。

3.控制台开通该功能,则立即产生扣费,次月一号出账单。

4.按月扣费,请确保账户余额充足,以免影响您的业务。

2.3 直播

开启旁路直播后,可将混流推送至指定的直播房间,所产生的的直播费用具体参考直播服务报价

3、欠费说明

1.欠费后工作人员会与您进行联系,如还未进行充值,将会停掉您的服务。

2.开通策略:账户充值余额为正后则会自动开通服务。

常见问题    

使用类问题    

1、 如何开通互动直播服务?

创建应用后,点击添加服务,弹出添加服务对话框,选择互动直播服务,点击确定,互动直播服务添加成功。

添加互动直播服务

互动直播服务

2、 如何配置分辨率码率?

通过控制台推流配置对推流参数进行配置,推流配置包含配置模式、编码格式、码率、帧率四部分。

系统默认配置对应参数如下

配置模式:超清 编码格式:1280*720 码率:30 帧率:20

同时还支持标清、高清、自定义几种配置模式

配置模式:标清 编码模式:480*360 码率:200 帧率:20

配置模式:高清 编码模式:640*480 码率:200 帧率:20

自定义:支持自由选择系统提供的编码格式、码率、帧率进行组合配置

手机端SDK也可以支持视频参数配置,配置参数如下:

iOS 标清:176*114 高清:320*240 超清:480*360

Android 标清:192*144 高清: 352*288 超清:480*360

3、 如何实现混流功能?

可在后台开启旁路直播功能,开启后,可对视频直播进行混流并推送到指定的直播房间。

4、 如何进行互动直播观看?

支持iOS、Android、JS SDK

5、 如何观看混流画面?

可在后台进行预览,也可通过iOS、Android、JS SDK播放SDK

6、 最多同时支持多少路直播互动?

支持不超过8路

7、 支持哪些数据统计?

支持在线时长、峰值带宽、在线人数统计

8、 Chrome屏幕共享插件安装

对于Chrome M72 之后的版本已经支持原生接口进行屏幕分享,不需要再安装插件。在当前版本的 互动SDK 中已适配 Chrome M72 之后的版本,将自动检测版本情况并给与提示。

如果是chrome72以下版本,仍需安装插件。安装方法如下:

第一次使用时,会提示未安装插件

点击下载Chrome插件按钮,将插件下载到本地,点击下载完的插件,选择“在文件夹中显示”菜单,找到下载文件所在目录,然后点击“CHROME屏幕共享插件安装教程”按钮,阅读插件安装指导。

打开 Chrome 浏览器右上角,找到『更多工具』-『扩展程序』;

将下载完的插件拖拽上传到扩展程序页面

点击 按钮,安装屏幕共享插件,当Chrome浏览器器的工具栏右上角显示图标时,则下载安装完成。

  • 如果插件拖拽安转失败,请重新启动浏览器再尝试安装,如果此种方式失败,再重启电脑尝试安装

  • 如果您可以正常访问Chrome应用商店,请搜索“桌面共享插件”或搜索“微吼”,或点击这里访问微吼插件地址

点击“添加至chrome”按钮,安装屏幕共享插件

点击添加至Chrome按钮,安装屏幕共享插件,当Chrome浏览器器的工具栏右上角显示 图标,则下载安装完成。

屏幕共享功能可以正常使用。

如果是Chrome M72 之后的版本,还需要指定待共享的窗口,点击分享,则可以将窗口分享出去。

桌面共享成功:

计费问题    

1、 如何购买互动直播?

使用微吼云平台服务,需要预先在账户进行充值,账户中心中点击充值,输入充值金额,点击提交,订单完成后完成充值,或者联系您的客户经理。

账户充值链接:http://www.vhallyun.com/console/financing/index

2、 互动直播的计费方式?

互动直播采用后付费&预付费结合的计费方式,计费项包含在线时长/旁路直播功能和直播三项。

具体的服务定价规则查看:互动直播服务定价

3、 账户余额不足时会进行提醒吗?

用户可在账户中心设置余额提醒,默认为0,低于设置的余额时会通过短信的方式对用户进行提醒,及时充值,确保服务的顺利使用。

4、 欠费后会立刻停止服务吗?

欠费后工作人员会与您进行联系,如还未进行充值,将会停掉您的服务。 开通策略:账户充值余额为正后则会自动开通服务。