Browse Source

更新版本到 1.5.0

ChangeLog
1. 支持发布图片流
2. 废弃了 startRecording, stopRecording 方法
3. 废弃了 startMix, stopMix, addMixStreams, removeMixStreams 方法
4. 新增 startRecord, stopRecord, updateRecordStreams, startRelay, stopRelay, updateRelayStreams 方法,用于录制及转推功能
5. 录制/转推新增 customMain, customFlow, single 的布局
6. 新增 record-notify 及 relay-notify 事件,用于通知录制/转推中的错误
7. 新增 setRole 方法,允许 live 模式下,切换角色,进行连麦
8. 对本地浏览器不支持的编码格式的流进行订阅时,将会收到订阅失败的错误提示
9. 新增 play, resume 方法,播放音视频流时,无需用户自己操作 video 和 audio 标签,使播放更简单
10. 发布流增加音轨,允许用户针对该音轨进行处理,如展示声音的波形图等
11. 其他一些问题的修复及内部优化
kevin.song 5 years ago
parent
commit
b424335ad0
6 changed files with 631 additions and 125 deletions
  1. 44 41
      AutoPlay.md
  2. 28 5
      Manual.md
  3. 470 73
      README.md
  4. 3 3
      lib/index.js
  5. 1 1
      package.json
  6. 85 2
      types/index.d.ts

+ 44 - 41
AutoPlay.md

@@ -13,73 +13,76 @@
 
 1. 调用 audio 或 video 的 play 方法时,会报类似于 `DOMException: play() failed because the user didn’t interact with the document first.` 的错误信息。
 
-2. 开启 audio 或 video 的 autoplay 时,即标签上填有 'autoplay' 属性,如 <audio autoplay/>,浏览器控制台会有类似于 `The AudioContext was not allowed to start` 的告警信息。此时,如果页面中使用的是 video 标签,一般会处于 `白屏` 状态。
+2. 开启 audio 或 video 的 autoplay 时,即标签上填有 'autoplay' 属性,如 `<audio autoplay/>`,浏览器控制台会有类似于 `The AudioContext was not allowed to start` 的告警信息。此时,如果页面中使用的是 video 标签,一般会处于 `白屏` 状态。
 
 
 ## 解决方法
 
-1. 提前检测浏览器是否能自动播放
+1. 调用 `play` 方法播放失败时,绕开 autoplay 的限制
 
-在用 URTC 创建 client 前,可使用 [can-autoplay](https://www.npmjs.com/package/can-autoplay) 第三方库进行检测,若不支持,可引导用户点击页面上的某个位置(譬如模态框上的确定按钮)来触发用户与页面的交互(即解锁 `the user didn’t interact with the document first`),然后再用 URTC 创建 client 并 joinRoom。示例代码:
+此时可引导用户点击页面上的某个位置,譬如模态框上的确定按钮,再在该  `click`  事件的监听函数里调用 `resume` 方法。示例代码:
 
 ```js
-canAutoplay.video().then(res => {
-  const { result } = res;
-  if (!result) {
-    // 弹出模态框提示用户点击
+client.play({
+  streamId: 'xxx'
+}, (err) => {
+  if (err) {
+    console.log('播放失败', err);
+    client.resume('xxx', (err) => {
+      if (err) {
+        console.log('恢复播放失败', err);
+      }
+    });
   }
 });
 ```
 
-2. 调用 audio 或 video 的 play 方法播放失败时,绕开 autoplay 的限制
+> 注:在调用 URTC sdk 的 `play` 方法失败时,会自动显示音视频播放元素的控制面板,面板里会有播放按钮等操作工具,您也可以不调用 `resume` 方法,仅提示用户点击音视频播放元素的控制面板上的播放按钮进行播放。
+
+2. 直接绕开 autoplay 的限制
 
-此时可与第一种方法类似,引导用户点击该 audio 或 video 标签(也可以是页面上的某个位置,譬如模态框上的确定按钮),但需要在该  `click`  事件的监听函数里再次调用 play 方法。示例代码:
+由于浏览器限制的是声音的自动播放,在以静音状态进行播放时,是不受 autoplay 的限制的,此时可在调用 `play` 方法时,传的参数中携带 mute: true,并在页面中提示用户,由用户来决定是否与页面进行交互并调用 `resume` 方法来恢复播放声音。示例代码:
+
+```html
+<span id="unmuteBtn" class="unmute-btn"></span>
+```
 
 ```js
-// 此处 videoElem 为页面中的 video 标签的 DOM 对象
-videoElem.play()
-  .catch(function () {
-    alert('自动播放失败,请点击 video 进行播放');
-    videoElem.onclick = function() {
-      videoElem.play()
-        .then(function() {
-          console.log('播放成功');
-        })
-        .catch(function(err) {
-          console.log('播放失败', err);
-        });
+let unmuteBtn = document.querySelector('#unmuteBtn');
+client.play({
+  streamId: 'xxx',
+  mute: true
+}, (err) => {
+  if (err) {
+    console.log('播放失败', err);
+  }
+});
+unmuteBtn.onclick = function() {
+  client.resume('xxx', (err) => {
+    if (err) {
+      console.log('恢复播放失败', err);
     }
   });
+}
 ```
 
-3. 直接绕开 autoplay 的限制
-
-由于浏览器限制的是声音的自动播放,在以静音状态进行播放时,是不受 autoplay 的限制的,此时可加上 muted 属性,并在页面中添加静音的图标,由用户来决定是否点击图标来播放声音。示例代码:
+3. 提前检测浏览器是否能自动播放
 
-```html
-<video id="player" autoplay muted/>
-<span id="unmuteBtn" class="unmute-btn"></span> <!-- 这里是静音图标 -->
-```
+在用 URTC 创建 client 前,可使用 [can-autoplay](https://www.npmjs.com/package/can-autoplay) 第三方库进行检测,若不支持,可引导用户点击页面上的某个位置(譬如模态框上的确定按钮)来触发用户与页面的交互(即解锁 `the user didn’t interact with the document first`),然后再用 URTC 创建 client 并 joinRoom。示例代码:
 
 ```js
-let player = document.querySelector('#player');
-let unmuteBtn = document.querySelector('#unmuteBtn');
-unmuteBtn.onclick = function() {
-  if (player.muted) {
-    player.muted = false;
-    unmuteBtn.className = 'mute-btn';
-  } else {
-    player.muted = true;
-    unmuteBtn.className = 'unmute-btn';
+canAutoplay.video().then(res => {
+  const { result } = res;
+  if (!result) {
+    // 弹出模态框提示用户点击
   }
-}
+});
 ```
 
 FAQ
 
 Q: 为什么有时可以播放,有时不行?
-A: 1. 因为页面不是 100% 被 Autoplay 限制,随着用户使用这个页面的次数增加,部分浏览器会把这个页面加入自己的 Autoplay 白名单列表中。2. 在推+拉流时,若客户端先推流(读取麦克风设备),再拉流,可以绕开 autoplay 的限制。
+A: 1. 部分浏览器会有自动播放的限制(如 Chrome 70 及以上和 Safari 浏览器)。2.因为页面不是 100% 被 Autoplay 限制,随着用户使用这个页面的次数增加,部分浏览器会把这个页面加入自己的 Autoplay 白名单列表中。3. 在推+拉流时,若客户端先推流(读取麦克风设备),再拉流,可以绕开 autoplay 的限制。
 
 Q: 为什么别的产品可以自动播放,URTC 却不可以?
-A: 排查问题时可先确定客户端是不是先推流再拉流(参考上面的问题)。如果是仅拉流,由于这是浏览器级别的限制,所有同类产品都会受到这个 autoplay 限制的影响,解决办法无外乎上面几种。
-部分产品里由于封装了 DOM 操作的方法,譬如播放时是由用户主动传入 video 标签的 id,在 sdk 内部调用 video 的 play 方法,并在失败时,在页面里插入 DOM 元素并提示用户进行交互,故被人容易理解为其支持自动播放。URTC 为了避免直接操作 DOM,并没有封装此类方法,以保证用户处理此类问题时的灵活性。
+A: 排查问题时可先确定客户端是不是先推流再拉流(参考上面的问题)。如果是仅拉流,由于这是浏览器级别的限制,所有同类产品都会受到这个 autoplay 限制的影响,解决办法无外乎上面几种。

+ 28 - 5
Manual.md

@@ -46,18 +46,41 @@ const client = new UCloudRTC.Client(appId, token);
 
 ```
 client.on('stream-published', (stream) => {
-    // 使用 HtmlMediaElement 播放媒体流。将流的 mediaStream 给 Video/Audio 元素的 srcObject 属性,即可播放,注意设置 autoplay 属性以支持视频的自动播放,其他属性请参见 [<video>](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/video)
-    htmlMediaElement.srcObject = stream.mediaStream;
+    client.play({
+        streamId: stream.sid,
+        container: divElement
+    });
+    // 此处 divElement 代表播放发布流的外层容器元素,也可以是这个外层容器元素的 ID,而外层容器一般是一个设置了宽高的 div 元素,请根据实际情况进行传值
 }); // 监听本地流发布成功事件,在当前用户执行 publish 后,与服务器经多次协商,成功后会触发此事件
 
 client.on('stream-subscribed', (stream) => {
-    // 使用 HtmlMediaElement 播放媒体流
-    htmlMediaElement.srcObject = stream.mediaStream;
+    client.play({
+        streamId: stream.sid,
+        container: divElement
+    });
+    // divElement 如上面所说
 }); // 监听远端流订阅成功事件,在当前用户执行 subscribe 后,与服务器经多次协商,成功后会触发此事件
 
 client.on('stream-added', (stream) => {
     client.subscribe(stream.sid);
-}); // 监听新增远端流事件,在远端用户新发布流后,服务器会推送此事件的消息。注:当刚进入房间时,若房间已有的正在发布的流,也会通过此事件进行通知业务侧
+}); // 监听新增远端流事件,在远端用户新发布流后,服务器会推送此事件的消息。注:当刚进入房间时,若房间已有的正在发布的流,也会通过此事件通知业务侧
+
+client.on('stream-removed', (stream) => {
+    // 在页面中删除播放该流的外层容器元素
+}); // 监听移除的远端流事件,在远端用户取消推流或流已关闭时,服务器会推送此事件的消息。
+
+client.on('stream-reconnected', (streams) => {
+    const { previous, current } = streams;
+    // 从本地缓存的流的数据里找到对应的流并用重连后该流的数据更新原流的数据,或直接删除原来的流,并使用新的流
+    let idx = allStreams.findIndex(item => item.sid === previous.sid);
+    if (idx >= 0) {
+        allStreams.splice(idx, 1, current);
+    }
+    client.play({
+        streamId: current.sid,
+        container: divElement
+    });
+}); // 当网络断开又恢复时,发布/订阅流可能会被重连,重连成功后,会通过此事件通知业务侧
 ```
 
 ## 3. 加入一个房间,然后发布本地流

File diff suppressed because it is too large
+ 470 - 73
README.md


File diff suppressed because it is too large
+ 3 - 3
lib/index.js


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "urtc-sdk",
-  "version": "1.4.20",
+  "version": "1.5.0",
   "description": "UCloud RTC javascript SDK",
   "main": "lib/index.js",
   "repository": {

+ 85 - 2
types/index.d.ts

@@ -9,7 +9,8 @@ export declare type DeviceType = 'audio'|'video';
 export declare type EventType = 'user-added' | 'user-removed' |
   'stream-added' | 'stream-removed' | 'stream-published' | 'stream-subscribed' |
   'mute-video' | 'unmute-video' | 'mute-audio' | 'unmute-audio' | 'screenshare-stopped' |
-  'connection-state-change' | 'kick-off' | 'network-quality' | 'stream-reconnected';
+  'connection-state-change' | 'kick-off' | 'network-quality' | 'stream-reconnected' |
+  'record-notify' | 'relay-notify';
 
 export declare type ConnectionState = 'OPEN' | 'CONNECTING' | 'CLOSING' | 'RECONNECTING' | 'CLOSED';
 
@@ -41,6 +42,8 @@ export interface PublishOptions {
   cameraId?: string     // 摄像头设备ID
   extensionId?: string  // chrome插件ID
   mediaStream?: MediaStream // 自定义的媒体流
+  file?: File,              // 图片流使用的图片文件
+  filePath?: string         // 图片流使用的图片的地址
 }
 
 export interface DeviceOptions {
@@ -183,13 +186,16 @@ export interface ReplaceTrackOptions {
 }
 
 export declare type MixType = 'relay' | 'record' | 'relay-and-record' | 'update-config'
-export declare type MixLayoutType = 'flow' | 'main' | 'custom' | 'customMain' | 'customFlow'
+export declare type MixLayoutType = 'flow' | 'main' | 'custom' | 'customMain' | 'customFlow' | 'single'
 export declare type MixAudioCodec = 'aac'
 export declare type MixVideoCodec = 'h264' | 'h265'
 export declare type H264Quality = 'B' | 'CB' | 'M' | 'E' | 'H'
+export declare type MixOutputMode = 'audio-video' | 'audio'   // 默认为 audio-video
+export declare type MixStreamAddMode = 'automatic' | 'manual' // 默认为 'automatic 自动的
 
 export interface MixLayoutOptions {
   type: MixLayoutType           // layout 类型
+  standbyTypes?: MixLayoutType[]// 待切换的 layout 类型
   custom?: Object[]             // layout 为 'custom',自定义布局填在custom里,格式参照RFC5707 Media Server Markup Language (MSML)
   mainViewUId?: string          // 指定某用户的流为主画面
   mainViewType?: MainViewType   // 主画面类型
@@ -221,6 +227,7 @@ export interface MixOptions {
   layout?: MixLayoutOptions
   audio?: MixAudioOptions
   video?: MixVideoOptions
+  outputMode?: MixOutputMode
 
   width?: number
   height?: number
@@ -229,6 +236,7 @@ export interface MixOptions {
   waterMark?: WaterMarkOptions
 
   streams?: MixStream[]
+  streamAddMode?: MixStreamAddMode
 }
 
 export interface StopMixOptions {
@@ -255,3 +263,78 @@ export interface AddMixStreamsOptions {
 export interface RemoveMixStreamsOptions {
   streams: MixStream[]
 }
+
+// 录制
+export interface StartRecordOptions {
+  bucket: string
+  region: string
+
+  layout?: MixLayoutOptions
+
+  width?: number
+  height?: number
+  backgroundColor?: BackgroundColorOptions
+
+  waterMark?: WaterMarkOptions
+
+  streams?: MixStream[]   // 如果列表为空,会自动添加房间内所有用户的流,如果指定了用户,则只添加该用户的指定流
+}
+
+export interface RecordResult {
+  Id: string
+  FileName?: string
+}
+
+declare type UpdateMixStreamsType = 'add' | 'remove';
+
+export interface UpdateMixStreamsOptions {
+  type: UpdateMixStreamsType,
+  streams: MixStream[]
+}
+
+export interface StartRelayOptions {
+  pushURL?: string[]
+  layout?: MixLayoutOptions
+  audio?: MixAudioOptions
+  video?: MixVideoOptions
+  outputMode?: MixOutputMode
+
+  width?: number
+  height?: number
+  backgroundColor?: BackgroundColorOptions
+
+  waterMark?: WaterMarkOptions
+
+  streams?: MixStream[]
+  streamAddMode?: MixStreamAddMode
+}
+
+export interface RelayResult {
+  Id: string
+  PushURL?: string[]
+}
+
+declare type UpdateRelayPushURLType = 'add' | 'remove';
+
+export interface UpdateRelayPushURLOptions {
+  type: UpdateRelayPushURLType,
+  pushURL: string[]
+}
+
+export interface UpdateRelayLayoutOptions {
+  layout: MixLayoutOptions
+}
+
+export interface UpdateRelayWaterMarkOptions {
+  waterMark: WaterMarkOptions
+}
+
+declare type VideoFitType = 'cover' | 'contain';  // cover 模式:优先保证视窗被填满。contain 模式:优先保证视频内容全部显示。
+
+export interface PlayOptions {
+  streamId: string,
+  container: HTMLElement | string,
+  mute?: boolean,
+  mirror?: boolean,
+  fit?: VideoFitType
+}