iOS

# 音视频录制

# 1. 本地音视频录制

本地录制支持实时的通话过程音频录制,录制文件保存在用户本地设备中,适用于通话过程录音录像场景等其他音视频相关场景。优点不受网络影响,录制画面质量高,减少带宽压力。

录制通话在本地生成视频文件。

调用下面的接口 enableLocalRecord (opens new window) 开启或关闭本地录制

/**
 * 开启/关闭本地录制
 * @param enable 开启或关闭本地录制
 * - true: 开启本地录制
 * - false: 关闭本地录制
 * @param recordParam 本地录制参数配置,当 enable == true 时,{@link JRTCRecordLocalParam#filePath} 必须设置,其余参数不设置则使用默认配置;当 enable == false 时,recordParam 可传 null
 * @see JRTCRecordLocalParam
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean enableLocalRecord(boolean enable, JRTCRecordLocalParam recordParam);

本地录制参数详见 JRTCRecordLocalParam (opens new window)

示例代码:

//开启本地录制
JRTCRecordLocalParam param = new JRTCRecordLocalParam();
param.setFilePath("录制文件本地保存路径");//设置录制文件名称
param.setRecVideo(true);//录制包含视频
param.setRecAudio(true);//录制包含音频
param.setIncludeSelf(true);//录制包含自己
param.setFrameRate(24);
param.setMergeMode(JRTCEnum.VIDEO_MERGE_MODE_CUSTOM_LAYOUT);//自定义布局,需要提供录制配置文件
param.setVideoWidth(640);//录制视频宽
param.setVideoHeight(360);//录制视频高
Map<String, String> watermarkTextMap map = new HashMap();
map.put("name", "我是占位符");// 会替换配置文件中 $@name@$ 占位符内容
param.setWatermarkTextMap(map);//设置水印
List<JRTCRecordLocalParam.RecordLayout> layoutList = new ArrayList();
JRTCRecordLocalParam.RecordLayout layout0 = new JRTCRecordLocalParam.RecordLayout();
//设置布局位置视频流标识
layout0.setId(mGuest.getParticipants().get(0).getUserId(), true);//通话中其他成员用户ID
//layout0.setId(mGuest.getShareStreamId(), false);//通话中屏幕共享视频流ID
//layout0.setId(mMediaDevice.getScreenCaptureId(), false);//屏幕采集视频流ID
//layout0.setId(mMediaDevice.getCurrentCamera().cameraId, false);//本地摄像头视频流ID
layout0.setPosition(0);//设置布局位置为0
layoutList.add(layout0);
JRTCRecordLocalParam.RecordLayout layout1 = new JRTCRecordLocalParam.RecordLayout();
layout1.setId("streamId1", false);
layout1.setPosition(1);//设置布局位置为1
layoutList.add(layout1);
param.setLayoutList(layoutList);//设置录制成员布局列表,mergeMode 为自定义布局需要
param.setConfigFilePath("C:\\Users\\aaa\\record.cfg");//本地录制配置配置路径

//开启本地录制
agent.enableLocalRecord(true, param);
//关闭本地录制
agent.enableLocalRecord(false, null);

更新本地录制自定义布局 updateLocalRecordLayout (opens new window),当本地录制已经在进行时,可以通过该接口实时更新本地录制布局

/**
 * 更新本地录制自定义布局
 *
 * @param layoutList 需要更新的布局列表
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean updateLocalRecordLayout(List<JRTCRecordLocalParam.RecordLayout> layoutList);

示例代码:

List<JRTCRecordLocalParam.RecordLayout> layoutList = new ArrayList();
JRTCRecordLocalParam.RecordLayout layout1 = new JRTCRecordLocalParam.RecordLayout();
layout1.setId("streamId0",false);
layout1.setPosition(0);
layoutList.add(layout1);

JRTCRecordLocalParam.RecordLayout layout2 = new JRTCRecordLocalParam.RecordLayout();
layout1.setId("streamId1", false);
layout1.setPosition(1);
layoutList.add(layout2);

agent.updateLocalRecordLayout(layoutList);

通过配置文件实现本地录制

流程图如下:

img

本地录制配置文件示例如下:

{
 "default" : {   //录制配置类型, 固定值.
     "videoRecord" : {   //视频录制配置, 固定值.
          "layout" : [        //自定义布局,mergeMode 字段为自定义布局模式 {@link JRTCEnum#VIDEO_MERGE_MODE_CUSTOM_LAYOUT VIDEO_MERGE_MODE_CUSTOM_LAYOUT(4)} 时生效, JSON数组格式, 可同时设置多个, 实际应用时通过窗口号区分.
              {
                  "posX" : 0.25,                  //画面左上角的横坐标(双精度浮点数类型), 代表占画面总宽度的比例.
                  "posY" : 0.26805556000000003,   //画面左上角的纵坐标(双精度浮点数类型), 代表占画面总高度的比例.
                  "width" : 0.1140625,            //画面的宽度(双精度浮点数类型), 代表占画面总宽度的比例.
                  "height" : 0.47916666000000002, //画面的高度(双精度浮点数类型), 代表占画面总高度的比例.
                  "window" : 0                    //窗口号(大于或等于的整数), 在使用 {@link RecordLayout#setLayoutList(List) setLayoutList} 自定义视频窗口布局时需要, 对应于用户自定义的位置信息 {@link RecordLayout#setPosition(int)} setPosition} 设置布局位置.
              },
              {
                  "posX" : 0.49609375,            //画面左顶点的横坐标(双精度浮点数类型), 范围为[0, 1], 代表占画面总宽度的比例.
                  "posY" : 0.033333334999999999,  //画面左顶点的纵坐标(双精度浮点数类型), 范围为[0, 1], 代表占画面总高度的比例.
                  "width" : 0.36406250000000001,  //画面的宽度(双精度浮点数类型), 范围为[0, 1], 代表占画面总宽度的比例.
                  "height" : 0.94027775999999996, //画面的高度(双精度浮点数类型) 范围为[0, 1], 代表占画面总高度的比例.
                  "window" : 1                    //窗口号(大于或等于的整数), 在使用 {@link RecordLayout#setLayoutList(List) setLayoutList} 自定义视频窗口布局时需要, 对应于用户自定义的位置信息 {@link RecordLayout#setPosition(int)} setPosition} 设置布局位置.
              }
          ],
          "mergeBitrate" : 0, //设置录制视频的码率, 单位为千比特每秒(kbps), 默认值为0, 此时码率由内部媒体算法进行自适应调节
          "mergeFPS" : 30,    //设置录制视频的帧率, 单位为每秒传输帧数(fps), 默认值为20.
          "mergeWidth" : 1280,//设置录制视频的宽度, 默认值为 640
          "mergeHeight" : 720,//设置录制视频的高度, 默认值为 360
          "mergeMode" : 4,    //设置媒体推流的视频合并模式, 默认值为 {@link JRTCEnum#VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT(5)}, 目前可支持自定义布局 {@link JRTCEnum#VIDEO_MERGE_MODE_CUSTOM_LAYOUT VIDEO_MERGE_MODE_CUSTOM_LAYOUT(4)} 和智能布局 {@link JRTCEnum#VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT(5)}.
          "mergeModeI" : 1,   //设置智能分屏模式下的布局样式(无屏幕共享), 默认值为自由布局 {@link JRTCEnum#INTELLIGENT_MERGE_MODE_FREE_LAYOUT INTELLIGENT_MERGE_MODE_FREE_LAYOUT(1)}, 有效值参考 {@link JRTCEnum.IntelligentMergeMode 智能分屏模式下的布局样式(无屏幕共享)}, 本字段仅在 mergeMode 为智能布局 {@link JRTCEnum#VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT(5)} 时生效.
          "screenShareType" : 1 //设置智能分屏模式下的布局样式(有屏幕共享), 默认值为屏幕共享独占 {@link JRTCEnum#SCS_MERGE_MODE_SCREEN_SHARE SCS_MERGE_MODE_SCREEN_SHARE(1)}, 有效值参考 {@link JRTCEnum.ScsMergeMode 智能分屏模式下的布局样式(有屏幕共享)}, 本字段仅在 mergeMode 为智能布局 {@link JRTCEnum#VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT VIDEO_MERGE_MODE_INTELLIGENT_LAYOUT(5)} 时生效.
      },
      "watermark" : { //水印配置, 固定值.
          "picture" : [   //图片水印配置, 固定值, 图片水印目前只支持 png 格式.
              {
                  "enable" : true,    //是否启用(布尔类型).
                  "pcUrl" : "http://192.168.17.60:10042/protected_files/6afa960e-fe4a-49ae-a939-48c788122192?attname=juphoon.png",    //图片水印链接地址.
                  "index" : 1,        //水印的序号.
                  "state" : 1,        //水印的状态. 设置值为1表示使用水印,设置值为2表示关闭水印.
                  "posX" : 0,         //相对于基准位置的水平偏移值(整数类型), 负值向左偏移, 正值向右偏移, 实际大小非比例值.
                  "posY" : 600        //相对于基准位置的垂直偏移值(整数类型), 负值向上偏移, 正值向下偏移, 实际大小非比例值.
              }
          ],
          "text" : {  //文本水印配置, 固定值.
              "enable" : true,    //是否启用(布尔类型).
              "memo" : [          //文本水印样式配置(JSON数组类型), 如果不设置就使用默认全局样式.
                  "Dialogue: 0,0:00:00.0,60:00:00.0,Default,,0,0,0,,{\\pos(34, 56)\\an7}菊风文本水印1$@name@$",
                  "Dialogue: 0,0:00:00.0,60:00:00.0,Default,,0,0,0,,{\\pos(50, 100)\\an7}菊风文本水印2"
                  //数组的每个元素为一条 ASS 字幕的 event 字符串, 支持通过标签来设置各种文字特效, 添加的event字符串必须是utf-8编码, 否则中文会出现乱码. ASS 格式规范下载地址: http://www.perlfu.co.uk/projects/asa/ass-specs.doc
                  //其中以 "$@" 开始并且以 "@$" 结束的部分内容"$@name@$"将提取出关键字"name", 通过解析用户通过 {@link #setWatermarkTextMap(Map) setWatermarkTextMap} 设置的自定义文本水印信息获取其对应值, "$@name@$"格式的内容被其对应值替换后就是实际应用的 event 字符串, "$@xxx@$" 格式的内容支持同时设置多个.
              ],
              "style" : {         //文本水印格式, 固定值.
                  "enable" : true,        //格式是否启用(布尔类型).
                  "alignment" : 0,        //对齐方式, 有效值参考 0:左对齐;1:居中对齐;2:右对齐;
                  "backColor" : 16777215, //背景颜色(整数类型), 10进制颜色代码.
                  "blod" : false,         //是否使用粗体(布尔类型).
                  "fontColor" : 16777215, //字体颜色(整数类型), 10进制颜色代码.
                  "fontFile" : "SourceHanSansCN-Normal.otf",//字体文件路径,Windows系统上只能用相对路径(相对配置文件所在路径),不可以带盘符,建议和配置文件放在同个目录下.
                  "fontSize" : 36,        //字体尺寸(整数类型).
                  "italic" : true,        //是否使用斜体(布尔类型).
                  "underline" : false     //是否带有下划线(布尔类型).
              }
          },
          "timestamp" : { //时间戳水印配置, 固定值.
              "enable" : true,    //是否启用(布尔类型).
              "basePosType" : 0,  //水印基准位置类型(整数类型), 有效值参考 0:左上;1:左下;2:右上;3:右下;4:居中;
              "borderWidth" : 2,  //字体边界宽度(整数类型), 取值范围为[0, 5].
              "fontFile" : "SourceHanSansCN-Normal.otf",//字体文件路径,Windows系统上只能用相对路径(相对配置文件所在路径),不可以带盘符,建议和配置文件放在同个目录下.
              "fontColor" : 0, //字体颜色(整数类型), 有效值参考 0:红色;1:黄色;2:绿色;3:青色;4:蓝色;5:洋红色;6:白色;7:中和色;8:黑色;
              "fontSize" : 36,    //字体尺寸(整数类型).
              "isMs" : true,      //是否显示毫秒值(布尔类型).
              "posX" : 0,         //相对于基准位置的水平偏移值(整数类型), 负值向左偏移, 正值向右偏移, 实际大小非比例值.
              "posY" : 0          //相对于基准位置的垂直偏移值(整数类型), 负值向上偏移, 正值向下偏移, 实际大小非比例值.
          }
      }
  }
}

配置样例文件(不带注解):📎record.cfg.zip (opens new window)

# 2. 本地音视频录制(不需要建立通信)

调用 startVideoCaptureRecord (opens new window)stopVideoCaptureRecord (opens new window)startAudioRecord (opens new window)stopAudioRecord (opens new window) 接口开启或关闭本地音视频录制,视频录制参数详见:JRTCRecordVideoCaptureParam (opens new window)

/**
 * 开启视频录制(本地录制,不需要建立通信,不能和音频录制 {@link #startAudioRecord(String, int, int) startAudioRecord} 同时开启)
 * @param streamId                  视频流ID, (包括摄像头ID、文件视频源ID、屏幕ID等)
 * @param recordVideoCaptureParam   录制参数
 * @see JRTCRecordVideoCaptureParam
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean startVideoCaptureRecord(@NonNull String streamId, @NonNull JRTCRecordVideoCaptureParam recordVideoCaptureParam);

/**
 * 关闭视频录制(本地录制,不需要建立通信,不能和音频同时录制)
 *
 * @param streamId 视频流ID, (包括摄像头ID、文件视频源ID、屏幕ID等)
 * @return 关闭视频录制是否成功
 */
public abstract boolean stopVideoCaptureRecord(@NonNull String streamId);

/**
 * 开启音频录制(本地录制,不需要建立通信,不能和视频录制 {@link #startVideoCaptureRecord startVideoCaptureRecord} 同时开启)
 *
 * @param filePath    保存的文件路径,必须包含文件名(xxx.wav或者pcm)
 * @param audioSource 录制文件音频源
 * @param fileType    录制文件编码封装类型
 * @return
 */
public abstract boolean startAudioRecord(@NonNull String filePath, @RecordAudioSource int audioSource, @AudioRecordFileType int fileType);

/**
 * 关闭音频录制(本地录制,不需要建立通信)
 *
 * @return
 */
public abstract boolean stopAudioRecord();

录制水印:本地录制支持图片、文字、时间戳水印,通过录制参数设置,详见:Image (opens new window) Text (opens new window) TimeStamp (opens new window)

/**
 * 文字水印
 */
public static class Text extends JRTCRecordWatermark {
    /**
     * 文字水印构造方法
     *
     * @param posX 相对于原点(左上角),X轴坐标(右为正数)
     * @param posY 相对于原点(左上角),Y轴坐标(下为正数)
     * @param font      字体样式
     * @param textColor 字体颜色
     * @param text      水印内容
     */
    public Text(int posX, int posY, Font font, Color textColor, String text);
}

/**
 * 时间戳水印
 */
public static class TimeStamp extends Text {
    /**
     * 时间戳水印构造方法
     *
     * @param posX 相对于原点(左上角),X轴坐标(右为正数)
     * @param posY 相对于原点(左上角),Y轴坐标(下为正数)
     * @param font       字体样式
     * @param textColor  字体颜色
     * @param dateFormat 时间戳格式化参数
     */
    public TimeStamp(int posX, int posY, Font font, Color textColor, SimpleDateFormat dateFormat);
}

/**
 * 图片水印
 */
public static class Image extends JRTCRecordWatermark {
    /**
     * 图片水印构造方法
     *
     * @param posX 相对于原点(左上角),X轴坐标(右为正数)
     * @param posY 相对于原点(左上角),Y轴坐标(下为正数)
     * @param filePath 图片路径
     */
    public Image(int posX, int posY, String filePath);
    /**
     * 图片水印构造方法
     *
     * @param posX 相对于原点(左上角),X轴坐标(右为正数)
     * @param posY 相对于原点(左上角),Y轴坐标(下为正数)
     * @param image 图片对象
     */
    public Image(int posX, int posY, ImageIcon image);
}

示例代码:

//打开摄像头
mediaDevice.startCamera(camera);
//打开麦克风
mediaDevice.startAudioInput();
JRTCRecordVideoCaptureParam captureParam = new JRTCRecordVideoCaptureParam();
captureParam.setFilePath("C:\Users\admin\1.mp4");//设置录制文件路径
captureParam.setAudioSource(JRTCMediaDevice.RECORD_AUDIO_FROM_MICROPHONE);
captureParam.setFileType(JCMediaDevice.VIDEO_RECORD_FILE_MP4_H264);
captureParam.setWidth(1280);
captureParam.setHeight(720);
List<JRTCRecordWatermark> watermarkList = new ArrayList<>();
JRTCRecordWatermark.Text textWatermark = new JRTCRecordWatermark.Text(10, 10, new Font("Default",  Font.BOLD, 30), Color.GREEN, "测试水印");
watermarkList.add(textWatermark); //文本水印
JRTCRecordWatermark.TimeStamp timeStampWatermark = new JRTCRecordWatermark.TimeStamp(10, 100, new Font("Default",  Font.BOLD, 30), Color.RED, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
watermarkList.add(timeStampWatermark); //时间戳水印
URL url = Utils.getImage(SampleApplication.class, "icon.png");
JRTCRecordWatermark.Image imageWatermark = new JRTCRecordWatermark.Image(300, 300, new ImageIcon(url));
watermarkList.add(imageWatermark);//图片水印
captureParam.setWatermarkList(watermarkList);
//开启本地视频录制
mediaDevice.startVideoCaptureRecord(mediaDevice.getCurrentCamera().cameraId, captureParam);
//关闭本地视频录制
mediaDevice.stopVideoCaptureRecord(mediaDevice.getCurrentCamera().cameraId);

//开启本地音频录制
mediaDevice.startAudioRecord("C:\Users\admin\1.wav", JCMediaDevice.AUDIO_RECORD_FILE_WAV_PCMA);
//关闭本地音频录制
mediaDevice.startAudioRecord();

# 3. 远程录制

在线上金融的应用场景中,考虑取证、质检、审核、存档和回放等需求,常需要将整个视频通话过程录制,并存储。

JRTC 的远程录制,通过会场服务将收到的所有终端数据发送给录制服务器,进行实时录制。在实际的集成中,当 SDK 初始化完成后,集成方通过加入会场的方式将需要进行录制的音视频流上传至服务器并进行实时录制。

# 开启远程录制

远程录制提供两个方法,enableRemoteRecord (opens new window)controlRecord (opens new window)

访客在发起呼叫或者座席回呼的时候可以携带 JRTCCallCenterCallParam.setAutoRecord (opens new window)参数来设置是否在通话开始后就进行远程录制。如果该次通话没有设置自动远程录制,则可以通过终端来触发录制,在服务端形成通话过程的视频文件。录制的配置(如水印、时间戳等)与自动录制的相同,都来自系统管理平台后台的配置。

# 远程录制接口一

/**
 * 开启/关闭远程录制(不经过排队机)
 * <p>
 * 当呼叫参数 {@link JRTCCallCenterCallParam#autoRecord autoRecord} == false 时,可通过此接口开启服务端录制。<br>
 * 可用过 {@link #getRemoteRecordState} 接口获取当前服务器录制状态。
 *
 * @param enable      开启或关闭视频录制
 *                    - true: 开启视频录制
 *                    - false: 关闭视频录制
 * @param recordParam 录制参数,当 enable == false 时,可传 null;当 enable == true 且按照默认配置进行录制可传 null
 * @return 接口调用结果
 * - true: 接口调用成功,录制状态通过 {@link JRTCGuestCallback#onCallPropertyChanged onCallPropertyChanged} 回调获得,具体可关注 {@link JRTCRoom.PropChangeParam#remoteRecordState remoteRecordState}
 * - false: 接口调用异常
 */
public abstract boolean enableRemoteRecord(boolean enable, JRTCRecordRemoteParam recordParam);

/**
 * 获取远程视频录制状态
 *
 * @return 远程视频录制状态,详见 {@link JRTCEnum.RemoteRecordState}
 * - {@link JRTCEnum#RECORD_STATE_NONE RECORD_STATE_NONE} : 无法进行视频录制。用户不在房间中或者加入房间时没有设置视频录制参数
 * - {@link JRTCEnum#RECORD_STATE_READY RECORD_STATE_READY} : 可以开启视频录制。用户在加入房间时设置了录制参数,并且没有在录制视频
 * - {@link JRTCEnum#RECORD_STATE_RUNNING RECORD_STATE_RUNNING} : 视频录制中。用户在加入房间时设置了录制参数,并且正在视频录制中
 */
@JRTCEnum.RemoteRecordState
public abstract int getRemoteRecordState();

录制状态改变通过 onCallPropertyChanged (opens new window) 回调通知到访客。

通话属性改变详见 PropChangeParam (opens new window),录制状态具体可关注 getRemoteRecordState (opens new window)

示例代码:

JRTCRecordRemoteParam param = new JRTCRecordRemoteParam();
param.setRecordVideo(true);
...
param.setwatermarkTextMap("{\"guest\":\"juphoon001\"}");
//开启远程录制
agent.enableRemoteRecord(true, param);

# 远程录制接口二

通过 controlRecord (opens new window) 开始和停止远程视频录制。

/**
 * 开始/停止远程录制(经过排队机)
 * <p>
 * 仅在 {@link #isAutoRecord} == false 时调用接口有效,即访客发起呼叫时,呼叫参数 {@link JRTCCallCenterCallParam#autoRecord} 需要为 false 。<br>
 * 由服务器自动开启远程录制,即 {@link #isAutoRecord} == true 时,水印内容由业务管理平台配置。<br>
 * 调用接口手动开启远程录制时,水印内容由参数 watermarkText 决定。
 *
 * @param action      打开或停止录制
 *                    - {@link JRTCCallCenter#AGENT_RECORD_ACTION_START AGENT_RECORD_ACTION_START} : 打开录制
 *                    - {@link JRTCCallCenter#AGENT_RECORD_ACTION_STOP AGENT_RECORD_ACTION_STOP} : 停止录制
 * @param recordParam 录制参数对象
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean controlRecord(@AgentRecordAction int action, JRTCRecordControlParam recordParam);

示例代码:

JRTCRecordControlParam param = new JRTCRecordControlParam();
param.watermarkTextMap = new HashMap<String, String>();
param.watermarkTextMap.put("key1", "水印内容");
param.watermarkTextMap.put("key2", "水印内容");
param.extraInfo = "";
boolean result = agent.controlRecord(param.action, param);

# 是否自动开启远程录制

通过 isAutoRecord (opens new window) 判断是否由服务器自动开启远程录制,自动录制开启不能手动控制开始/停止远程录制。

/**
 * 是否由服务器自动开启录制
 * <p>
 * 由访客 {@link JRTCCallCenterCallParam#autoRecord autoRecord} 属性决定,在通话状态为 {@link JRTCCallCenter#CALL_STATE_TALKING CALL_STATE_TALKING} 时可以获取 <br>
 * 如果当前为自动录制模式,即 isAutoRecord == true,则座席的 {@link #controlRecord} 远程录制接口不生效
 */
public abstract boolean isAutoRecord();

# 远程录制异常回调

/**
 * 录制异常回调
 * <p>
 * 远程录制异常退出时会上报此回调。
 *
 * @param isShutDown     录制异常时服务器是否自动结束通话
 *                       - true: 自动结束通话
 *                       - false: 不自动结束通话
 * @param deliveryUserId 录制异常的用户ID
 * @param reason         录制异常的原因
 */
void onDeliveryAbort(boolean isShutDown, String deliveryUserId, String reason);

# 水印

开启远程录制接口,支持配置文字水印,录制完成后视频保存于服务端。

Juphoon RTC SDK 支持以下三种画布水印设置:

  • 文字水印:使用一段文字信息作为水印,支持设置字体和字号。
  • 动态时间戳水印:使用当前时间戳作为水印,显示格式为“2021-03-18 14:30:35"。
  • 静态图片水印:使用图片作为水印。

# 注意事项

/**
 * 设置录制水印内容,需跟业务管理平台配置对应使用
 */
public void setWatermarkTextMap(Map<String, String> watermarkTextMap);

# 添加、修改或删除水印

  • 文字水印,如要求水印内容需具备客户经理工号、姓名、地理位置、经纬度;则需要终端开启录制时通过 setWatermarkTextMap (opens new window) 设置: {"userInfo": "工号+姓名"} 、{"location":"地理位置"}、{"JWD": "经纬度"}这样的键值对;具体体现和系统管理平台端配置如下 ,红框内就是文字水印,支持在业务管理平台调整位置和字体颜色等,而$@xxx$@最终会替换成键xxx对应的值,如$@userInfo$@,替换成“工号+姓名”,$@location$@,替换成“地理位置”,$@JWD$@,替换成“经纬度”。
  • 图片水印,在业务管理平台支持配置图片水印,目前知支持png格式图片,可以自由调整位置。
  • 时间戳水印,在业务管理平台支持配置时间戳水印,可以自由调整位置,字体颜色、大小等。

img

# 自定义布局

在系统管理平台端中的录制配置目录下,可以配置录制的一些参数,为了录制布局更加灵活,还提供了自定义布局,自定义布局可以配置每个录制流的位置和大小。

系统管理平台端默认使用的是智能分屏,如需切换自定义布局,在系统管理平台端修改如下:

img

img

配置每个成员的布局位置和大小,配置完成后点保存:

图中的0,1表示每个窗口对应的窗口位置,终端开启远程录制绑定视频流和窗口位置需要用到。

img

以上就是系统管理平台端的配置,实现自定义布局还需要在代码中绑定对应的窗口位置,

使用 enableRemoteRecord (opens new window) 示例代码:

JRTCRecordRemoteParam param = new JRTCRecordRemoteParam();
param.setFrameRate(24);
param.setIBitrate(500);
// 设置录制合并模式为自定义布局模式
param.setMergeMode(VIDEO_MERGE_MODE_CUSTOM_LAYOUT);
param.setVideoWidth(640);
param.setvVideoHeight(360);
param.setRecordVideo(true);
param.layoutType = "default";//设置录制样式,对应业务管理平台上录制配置中的编号ID, 不传则用默认
List<JRTCRecordRemoteParam.RecordLayout> layoutList = new ArrayList<>();
int window = 0; // windows表示窗口id,需要事先在系统管理平台中配置完成
for (JRTCRoomParticipant participant : mGuest.getParticipants()) {
    JRTCRecordRemoteParam.RecordLayout layout = new JRTCRecordRemoteParam.RecordLayout();
    // 绑定成员的流
    layout.setId(participant.get(0).getUserId(), true);
    // 设置系统管理平台中对应的窗口位置
    layout.setPosition(window);
    layoutList.add(layout);
    window++;
}

// 判断通话中是否有屏幕共享存在
if (!TextUtils.isEmpty(mGuest.getShareUserId())) {
    JRTCRecordRemoteParam.RecordLayout layout = new JRTCRecordRemoteParam.RecordLayout();
    // 绑定屏幕共享的流
    layout.setId(mGuest.getShareStreamId(), false);
    // 绑定系统管理平台中对应的窗口位置
    layout.setPosition(windows);
    layoutList.add(layout);
}

// 开启远程录制
agent.enableRemoteRecord(true, param);

# 更新远程录制自定义布局

如果需要在远程录制已经开启的情况下更改录制布局,使用场景:比如录制中有新成员加入,需要调整布局位置,可以使用如下接口,需要在远程录制进行中调用,详见:updateRemoteRecordLayout (opens new window)

/**
 * 更新远程录制自定义布局
 *
 * @param layoutList 需要更新的布局列表
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean updateRemoteRecordLayout(List<JRTCRecordRemoteParam.RecordLayout> layoutList);

示例代码:

JRTCRecordRemoteParam param = new JRTCRecordRemoteParam();
// ......远程录制参数设置
// 开启远程录制
agent.enableRemoteRecord(true, param);
List<JRTCRecordRemoteParam.RecordLayout> layoutList = new ArrayList<>();
JRTCRecordRemoteParam.RecordLayout layout = new JRTCRecordRemoteParam.RecordLayout();
layout.setId("成员用户ID", true);
// 设置系统管理平台中对应的窗口位置
layout.setPosition(0);
layoutList.add(layout);
//......设置其他成员视频流或者屏幕共享流对应窗口位置
//更新录制自定义布局
boolean result = agent.updateRemoteRecordLayout(layoutList);
// 关闭远程录制
agent.enableRemoteRecord(false, null);

# 更新远程录制水印信息

如果需要在远程录制已经开启的情况下更改水印信息,使用场景:比如录制中有成员离开,需要修改该成员原先位置水印标签(可能是用户名字),可以使用如下接口,需要在远程录制进行中调用,详见:updateRemoteRecordWatermark (opens new window)

/**
 * 更新远程录制水印信息
 *
 * @param watermarkTextMap 水印信息
 * @return 接口调用结果
 * - true: 接口调用成功
 * - false: 接口调用异常
 */
public abstract boolean updateRemoteRecordWatermark(Map<String, String> watermarkTextMap);

示例代码:

JRTCRecordRemoteParam param = new JRTCRecordRemoteParam();
Map<String, String> watermarkTextMap1 = new HashMap<String, String>();
watermarkTextMap1.put("w1", "张三");
watermarkTextMap1.put("w2", "李四");
param.setWatermarkTextMap(watermarkTextMap1);//初始水印
// ......远程录制参数设置
// 开启远程录制
agent.enableRemoteRecord(true, param);
Map<String, String> watermarkTextMap2 = new HashMap<String, String>();
watermarkTextMap2.put("w1", "李四");//修改水印信息
watermarkTextMap2.put("w2", "王五");//修改水印信息
boolean result = agent.updateRemoteRecordWatermark(watermarkTextMap2);//更改录制水印信息
// 关闭远程录制
agent.enableRemoteRecord(false, null);

# 4. 分片录制

# 初始化分片录制模块

参考本文档第四条第三款第一项,序号为4.3.1,实现方法为create (opens new window)

# 开启视频录制

startVideoCaptureRecord (opens new window)开始录制,该录制为本地录制,不需要建立通信,可直接开启。该方法有两个参数参数一captureId为视频采集id,包括摄像头id、文件视频源渲染id、屏幕共享采集id等,因此这个接口是录制指定的某个摄像头或者桌面;参数二为其他录制参数,下文将会详解。调用方法之后返回值为boolean类型,可用来判断调用是否成功。

录制参数JRTCRecordVideoCaptureParam (opens new window)

JRTCRecordVideoCaptureParam param = new JRTCRecordVideoCaptureParam();
/**
 * 设置录制文件保存的文件路径,必须包含文件名(xxx.mp4或者xxx.avi)
 */
param.setFilePath(String filePath);

/**
 * 设置录制文件音频源,默认 {@link JRTCMediaDevice#RECORD_AUDIO_FROM_MICROPHONE RECORD_AUDIO_FROM_MICROPHONE}
 * @see RecordAudioSource
 */
param.setAudioSource(@RecordAudioSource int audioSource);

/**
 * 设置录制文件编码封装类型,默认 {@link JRTCMediaDevice#VIDEO_RECORD_FILE_MP4_H264 VIDEO_RECORD_FILE_MP4_H264}
 * @see VideoRecordFileType
 */
param.setFileType(@VideoRecordFileType int fileType);

/**
 * 设置录制文件宽(传-1,则使用实际视频采集的宽)
 */
param.setWidth(int width);

/**
 * 设置录制文件高(传-1,则使用实际视频采集的高)
 */
param.setHeight(int height);

/**
 * 设置本地录制视频水印信息
 * @note 支持录制过程中,实时更新水印信息
 * @see JRTCRecordWatermark
 */
param.setWatermarkList(List<JRTCRecordWatermark> watermarkList);

/**
 * 设置录制文件是否加密,默认不加密
 */
param.setEnableEncrypt(boolean enableEncrypt);

/**
 * 设置录制文件末尾是否增加Md5校验值,默认不增加
 */
param.setAppendMd5(boolean appendMd5);

/**
 * 设置分片录制单个文件录制时长,单位秒,<=0 不分片录制,默认不分片录制
 */
param.setSplitIntervalSec(int splitIntervalSec);

/**
 * 设置分片录制文件最大分割个数,超过最大文件录制分割个数会循环覆盖,默认0x7fffffff
 */
param.setSplitMaxNum(int splitMaxNum);

/**
 * 设置录制码率
 */
param.setBitrate(int bitrate);

对于以上参数的补充解释:

(1 )设置录制文件音频源audioSource:不录制音频、录制输入音频、录制输出音频、录制输入音频和输出音频。请查看RecordAudioSource (opens new window)

(2)设置录制文件编码封装类型fileType:

封装格式AVI,视频编码格式VP8, 音频编码格式PCM;

封装格式AVI,视频编码格式I420, 音频编码格式PCM;

封装格式AVI,视频编码格式H264, 音频编码格式PCM;

封装格式MP4,视频编码格式H264,音频编码格式AAC;

封装格式MP4,视频编码格式AV1,音频编码格式AAC。

请查看VideoRecordFileType (opens new window)

(3) 设置本地录制视频水印信息,参数对象JRTCRecordWatermark (opens new window)

int posX; //相对画布左上角原点的X轴偏移(右为正数)
int posY; //相对画布左上角原点的X轴偏移(下为正数)
int width; //水印区域宽
int height; //水印区域高

示例代码:

JRTCRecordVideoCaptureParam recordCaptureParam = new JRTCRecordVideoCaptureParam();
recordCaptureParam.setFilePath(filePath);
recordCaptureParam.setAudioSource(JRTCMediaDevice.RECORD_AUDIO_BOTH);
recordCaptureParam.setHeight(recordHeight);
recordCaptureParam.setWidth(recordWidth);
List<JRTCRecordWatermark> watermarkList = new ArrayList<>();
if (PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.enable) {
    JRTCRecordWatermark.Text textWatermark = new JRTCRecordWatermark.Text(
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.posX,
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.posY,
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.font,
        WatermarkManager.getInstance().transferColor(PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.fontColor),
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.text);
    watermarkList.add(textWatermark);
}
if (PluginPropertyDAO.getInstance().getRecordWatermarkBean().timeStampWatermark.enable) {
    JRTCRecordWatermark.TimeStamp timeStampWatermark = new JRTCRecordWatermark.TimeStamp(
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().timeStampWatermark.posX,
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().timeStampWatermark.posY,
        PluginPropertyDAO.getInstance().getRecordWatermarkBean().timeStampWatermark.font,
        WatermarkManager.getInstance().transferColor(PluginPropertyDAO.getInstance().getRecordWatermarkBean().textWatermark.fontColor),
        new SimpleDateFormat(PluginPropertyDAO.getInstance().getRecordWatermarkBean().timeStampWatermark.format), System.currentTimeMillis());

    watermarkList.add(timeStampWatermark);
}
if (PluginPropertyDAO.getInstance().getRecordWatermarkBean().imageWatermark.enable) {
    JRTCRecordWatermark.Image imageWatermark = null;
    try {
        imageWatermark = new JRTCRecordWatermark.Image(
            PluginPropertyDAO.getInstance().getRecordWatermarkBean().imageWatermark.posX,
            PluginPropertyDAO.getInstance().getRecordWatermarkBean().imageWatermark.posY,
            PluginPropertyDAO.getInstance().getRecordWatermarkBean().imageWatermark.picture);
        watermarkList.add(imageWatermark);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
recordCaptureParam.setWatermarkList(watermarkList);
int fileType = translateToVideoEncodeType(videoEncode);
recordCaptureParam.setFileType(fileType);
if (splitInterval > 0) {
    mCurrentUploadFileBean.setSliceRecord(1);
    recordCaptureParam.setAppendMd5(true);
    recordCaptureParam.setEnableEncrypt(true);
    recordCaptureParam.setSplitIntervalSec(splitInterval);
} else {
    mCurrentUploadFileBean.setSliceRecord(0);
}
if (MediaDeviceManager.getInstance().getRecordFileType() > 0) {
    recordCaptureParam.setFileType(MediaDeviceManager.getInstance().getRecordFileType());
}
boolean res = record.startVideoCaptureRecord(streamId, recordCaptureParam);

# 停止录制

stopVideoCaptureRecord (opens new window)接口停止录制根据上文讲述中开启录制中的captureId去停止指定的录制。返回值为boolean类型,可用于判断调用是否成功。

示例代码:

boolean res = record.stopVideoCaptureRecord(streamId);

# 录制完成或者异常的事件

# 本地视频录制完成通知

本地视频录制完成通知onVideoCaptureRecordEnd (opens new window),会上报录制文件本地路径(分片录制情况下,表示第一个录制文件的文件路径)、录制文件时长,单位 ms(分片录制情况下,表示所有分片录制文件的总时长)。

示例代码:

@Override
public void onVideoCaptureRecordEnd(String filePath, long duration) {
    notifyRecordEnd(duration);
}

# 分片录制文件录制完成回调

分片录制文件录制完成回调onVideoCaptureRecordFileSlice (opens new window),上报信息为录制文件本地路径、是否最后一个录制文件。

@Override
public void onVideoCaptureRecordFileSlice(String filePath, boolean isLast) {
    if (!TextUtils.isEmpty(filePath)) {
        File file = new File(filePath);
        if (file.exists()) {
            SliceRecordManager.getInstance().videoRecordCompletely(filePath, isLast);
            // 录制文件添加到FileManager
            FileManager.getInstance().addVideoRecordFile(filePath, isLast);
        }
    }
}

# 视频录制异常回调

示例代码:

@Override
public void onVideoCaptureRecordError(String reason) {
	videoRecordErrorNotify(reason);
}

# 合并录制文件

录制完成之后录制文件是切片状态,如果需要合并录制文件可通过mergeRecordFile (opens new window)接口实现合并录制文件。参数有四,如下:

List<String> filePathList   录制文件路径列表,因为是分片录制,所以是文件列表
boolean enableEncrypt  源录制文件是否加密
boolean appendMd5      源录制文件是否增加了Md5校验值
String mergedFilePath 合并后文件保存路径,必须包含文件名(xxx.mp4或者xxx.avi)

总而言之,录制成功之后,需要分片文件的文件列表,就可合并。合并时可设置加密、md5校验、合并后地址。

示例代码:

List<String> filePathList = new ArrayList<>();
for (SliceRecordFile.SliceFile sliceFile : sliceFileList) {
    String sliceFilePath = sliceFile.getSliceFilePath();
    filePathList.add(sliceFilePath);
}
boolean res = record.mergeRecordFile(filePathList, true, true, mergedFilePath);

# 完成分片录制文件上传

录制完成后,需要上传可通过completeSliceUpload (opens new window)将录制文件提交到服务器,返回值为int值,为-1时说明调用失败。提交的时候需要填写业务号,每一笔录制对应一个业务号,后台服务可通过业务号查询录制文件;除了业务号,可设置其他录制上传参数JRTCCompleteSliceUploadParam (opens new window),详解如下:

JRTCCompleteSliceUploadParam param = new JRTCCompleteSliceUploadParam();

/**
 * 设置视频打点信息
 */
param.setVideoDotList(List<JRTCVideoDotParam> videoDotList);

/**
 * 设置业务流水号
 */
param.void setBusinessId(String businessId);

/**
 * 设置所有已经上传的分片录制文件的文件名列表
 *
 * @note 文件名列表需要按照上传文件顺序
 */
param.setSliceFileNameList(List<String> sliceFileNameList);

/**
 * 设置服务端合成后的录制文件名,可不传,默认文件名为 ${yyyyMMddHHmmss}_${serialId}.mp4
 */
param.setMergedFileName(String mergedFileName);

/**
 * 设置开始录制时间戳
 */
param.setRecordBeginTimestamp(long recordBeginTimestamp);

/**
 * 设置结束录制时间戳
 */
param.setRecordEndTimestamp(long recordEndTimestamp);

/**
 * 设置上传目标服务Oid
 */
param.setServerOid(String serverOid);

/**
 * 设置随路参数,具体和服务端约定
 */
param.setExtraInfo(Map<String, Object> extraInfo);

参数补充说明:

(1) 设置视频打点信息JRTCVideoDotParam

JRTCVideoDotParam param = new JRTCVideoDotParam();
/**
 * 设置视频打点信息
 */
param.setContentMap(Map<String, Object> contentMap);

/**
 * 设置视频打点时间偏移量,单位:毫秒
 */
param.setTimeOffset(long timeOffset);

/**
 * 设置视频打点信息类型,默认 text
 */
param.setType(String type);

示例代码:

JRTCCompleteSliceUploadParam completeSliceUploadParam = new JRTCCompleteSliceUploadParam();
File file = new File(fileDirPath + File.separator + "info.json");
List<String> filePathList = new ArrayList<>();
if (file.exists()) {
    String str = FileUtils.readToString(file.getAbsolutePath());
    Gson gson = new Gson();
    SliceRecordFile sliceRecordFile = gson.fromJson(str, new TypeToken<SliceRecordFile>() {
    }.getType());
    uploadUrl = sliceRecordFile.getUploadUrl();
    String fileName = sliceRecordFile.getRecordBeginTimestamp() + "_" + sliceRecordFile.getSerialId() + ".mp4";
    completeSliceUploadParam.setMergedFileName(fileName);
    completeSliceUploadParam.setRecordBeginTimestamp(sliceRecordFile.getRecordBeginTimestamp());
    completeSliceUploadParam.setRecordEndTimestamp(sliceRecordFile.getRecordEndTimestamp());
    completeSliceUploadParam.setBusinessId(sliceRecordFile.getBusinessId());
    completeSliceUploadParam.setServerOid(sliceRecordFile.getServerOid());
    completeSliceUploadParam.setExtraInfo(sliceRecordFile.getExtraInfo());
    List<SliceRecordFile.SliceFile> sliceFileList = sliceRecordFile.getSliceFileList();
    for (SliceRecordFile.SliceFile sliceFile : sliceFileList) {
        filePathList.add(sliceFile.getSliceFileName());
    }
    completeSliceUploadParam.setSliceFileNameList(filePathList);
    List<SliceRecordFile.DotInfo> dotInfoList = sliceRecordFile.getDotInfoList();
    if (dotInfoList != null && !dotInfoList.isEmpty()) {
        List<JRTCVideoDotParam> videoDotParamList = new ArrayList<>();
        for (SliceRecordFile.DotInfo dotInfo : dotInfoList) {
            JRTCVideoDotParam param = new JRTCVideoDotParam();
            param.setContentMap(dotInfo.getContentMap());
            param.setTimeOffset(dotInfo.getTimestamp() - sliceRecordFile.getRecordBeginTimestamp());
            param.setType(dotInfo.getType());
            videoDotParamList.add(param);
        }
        completeSliceUploadParam.setVideoDotList(videoDotParamList);
    }
    if (mFileUploadCallback != null) {
        mFileUploadCallback.onNotifyUpdateRecordResult("serialId", serialId, "url", uploadUrl);
    }
}

int operatorId = record.completeSliceUpload(serialId, completeSliceUploadParam);

上传响应事件是通过onCompleteSliceUploadResponse (opens new window)上报,上报信息如下:

int operatorId   操作ID,对应 {@link JRTCRecord#completeSliceUpload(String, JRTCCompleteSliceUploadParam) completeSliceUpload} 的返回值
boolean result       请求是否成功 - true:请求成功 - false:请求失败
String fileName     服务器合并后的文件名
long fileSize     服务器合并后的文件大小
String fileMd5sum   服务器合并后的文件的Md5信息
String extraInfo    其他随路参数
List<String> missFileList 缺失文件列表,当 result 为 false 且 errorCode 为 713045J65时有效
String reason       请求失败原因描述,当 result 为 false 时有效

示例代码:

@Override
public void onCompleteSliceUploadResponse(int operatorId, boolean result, String fileName, long fileSize, String fileMd5sum, String extraInfo, List<String> missFileList, String reason) {
    if (result) {
        // 文件上传成功,停止上传超时线程
       
    } else {
        JRTCLog.error(FileManager.TAG, new JRTCLogParam.Builder(FileManager.DM)
                      .content("completeSliceUploadResponse.result:%b.reason:%s", false, reason)
                      .build());
    }
}

# 请求分片录制文件上传信息

上传完成后,上传的录制文件信息需要获取上传文件信息以便更好地进行业务。接口为requestSliceUploadInfo (opens new window),提交请求去查询录制文件需要查询参数,这个查询参数为业务id,是否第一个分片录制文件以及其他参数。其他参数JRTCRequestSliceUploadParam (opens new window)详解如下:

JRTCRequestSliceUploadParam param = new JRTCRequestSliceUploadParam();
/**
 * 设置开始录制时间戳,
 * @note 是整个录制开始的时间,不是待上传的分片录制文件的开始录制时间
 */
param.setRecordBeginTimestamp(long recordBeginTimestamp);

/**
 * 设置待上传的分片录制文件大小,单位字节
 */
param.setFileSize(long fileSize);

/**
 * 设置待上传的分片录制文件Md5信息
 */
param.setFileMd5sum(String fileMd5sum);

示例代码:

JRTCRequestSliceUploadParam param = new JRTCRequestSliceUploadParam();
param.setRecordBeginTimestamp(uploadParam.getRecordBeginTimestamp());
param.setFileSize(recordFile.length());
param.setFileMd5sum(FileUtils.getFileMd5Code(uploadParam.getFilePath()));
int operatorId = record.requestSliceUploadInfo(uploadParam.getSerialId(), uploadParam.isFirstSliceFile(), param);

请求上传录制文件信息事件通知onRequestSliceUploadInfoResponse (opens new window),上报信息如下:

int operatorId       操作ID,对应 {@link JRTCRecord#requestSliceUploadInfo(String, boolean, JRTCRequestSliceUploadParam) requestSliceUploadInfo} 的返回值
boolean result           请求是否成功 - true:请求成功 - false:请求失败
String url              上传地址,首次请求分片上传信息时有效
String token            文件上传所需token,用于校验上传合法性,需要在上传文件的时候携带
long requestTimestamp 本次请求发起时间戳,用于控制上传地址有效期,需要在上传文件的时候携带
String extraInfo        其他随路参数
String serverOid        上传目标服务Oid
String reason           请求失败原因描述,当 result 为 false 时有效

示例代码:

@Override
public void onRequestSliceUploadInfoResponse(int operatorId, boolean result, String url, String token, long requestTimestamp, String extraInfo, String serverOid, String reason) {
    JRTCLog.info(FileManager.TAG, new JRTCLogParam.Builder(FileManager.DM)
                 .content("requestSliceUploadInfoResponse operatorId=%d.result=%b.url=%s.token=%s.requestTimestamp=%d.extraInfo=%s.reason=%s", operatorId, result, url, token, requestTimestamp, extraInfo, reason)
                 .build());
    if (result) {
        // 成功
    } else {
        JRTCLog.error(FileManager.TAG, new JRTCLogParam.Builder(FileManager.DM)
                      .content("requestSliceUploadInfoResponse.result:%b.reason:%s", false, reason)
                      .build());
    }
}
String filePath 录制文件本地路径
boolean isLast   表示是否最后一个录制文件
@Override
public void onVideoCaptureRecordFileSlice(String filePath, boolean isLast) {
    if (!TextUtils.isEmpty(filePath)) {
        File file = new File(filePath);
    }
}

# 5. 恢复录制文件

recoveryRecordFile (opens new window)恢复录制文件

使用场景:
* 1. 开启音视频录制;
* 2. 录制过程中程序出现异常(比如崩溃),导致无法正常关闭音视频录制;
* 3. 本地录制目录下生成了录制过程文件,不是一个有效的音视频文件;
* 4. 当程序再次启动时可以调用该接口,将录制目录中已经录制的音视频过程文件恢复成有效的音视频文件。

示例代码:

if (mediaDevice.recoveryRecordFile(recordFileFoldPath)) {
    JOptionPane.showMessageDialog(null, "恢复录制文件成功", "提示", JOptionPane.INFORMATION_MESSAGE);
} else {
    JOptionPane.showMessageDialog(null, "恢复录制文件失败", "提示", JOptionPane.INFORMATION_MESSAGE);
}

# 6. 录制通知

# 本地视频录制完成通知

onVideoCaptureRecordEnd (opens new window)本地视频录制完成通知

String filePath 录制文件本地路径
                 分片录制情况下,表示第一个录制文件的文件路径
long duration 录制文件时长,单位 ms
                 分片录制情况下,表示所有分片录制文件的总时长
@Override
public void onVideoCaptureRecordEnd(String filePath, long duration) {
    notifyRecordEnd(duration);
}

# 分片录制文件录制完成回调

onVideoCaptureRecordFileSlice (opens new window)分片录制文件录制完成回调

String filePath 录制文件本地路径
boolean isLast   表示是否最后一个录制文件
@Override
public void onVideoCaptureRecordFileSlice(String filePath, boolean isLast) {
    if (!TextUtils.isEmpty(filePath)) {
        File file = new File(filePath);
    }
}

# 视频录制异常回调

onVideoCaptureRecordError (opens new window)视频录制异常回调

@Override
public void onVideoCaptureRecordError(String reason) {
System.out.println("error :" + reason);
}

# 文件上传到影像平台结果通知

onFileUploadPlatformNotify (opens new window) 服务端录制文件或者本地录制文件上传到影像平台后,都会通过该接口回调通知

@Override
public void onFileUploadPlatformNotify(boolean result, String serialId, JRTCUploadFileInfo uploadFileInfo) {
    System.out.println("上传是否成功:" + result); 
    System.out.println("业务id(如果是通话业务相关文件,对应的是 callId):" + serialId);
    System.out.println("文件信息对象:" + uploadFileInfo);
}

# 录制上传事件上报

这是一个接口,reportRecordUploadEvent (opens new window),接口上报录制相关的事件。第一个参数事件行为可参照eventAction (opens new window)

JRTCRecordUploadParam recordUploadParam = new JRTCRecordUploadParam();
......
record.reportRecordUploadEvent(eventAction, recordUploadParam);