## 基础知识

本文主要介绍视频云 SDK 的直播播放功能，在此之前，先了解如下一些基本知识会大有裨益：

- **直播和点播**
  直播（LIVE）的视频源是主播实时推送的。因此，主播停止推送后，播放端的画面也会随即停止，而且由于是实时直播，所以播放器在播直播 URL 的时候是没有进度条的。

点播（VOD）的视频源是云端的一个视频文件，只要未被从云端移除，视频就可以随时播放， 播放中您可以通过进度条控制播放位置，腾讯视频和优酷土豆等视频网站上的视频观看就是典型的点播场景。

- **协议的支持**
  通常使用的直播协议如下，App 端推荐使用 FLV 协议的直播地址（以“http”打头，以“.flv”结尾）：
  ![](https://mc.qcloudimg.com/static/img/94c348ff7f854b481cdab7f5ba793921/image.jpg)

## 特别说明
- **是否有限制？**
  视频云 SDK **不会对播放地址的来源做限制**，即您可以用它来播放腾讯云或非腾讯云的播放地址。但视频云 SDK 中的播放器只支持 FLV 、RTMP 和 HLS（m3u8）三种格式的直播地址，以及 MP4、HLS（m3u8）和 FLV 三种格式的点播地址。

- **历史因素**
  SDK 早期版本只有 TXLivePlayer 一个 Class 承载直播和点播功能，但是由于点播功能越做越多，我们最终在 SDK 3.5版本开始，将点播功能单独分离出来，交由 TXVodPlayer 来负责。但是为了保证编译通过，您在 TXLivePlayer 中依然可以看到类似 seek 等点播才具备的功能。

## 对接攻略

### step 1: 创建 Player
视频云 SDK 中的 TXLivePlayer 模块负责实现直播播放功能。对应于 Flutter 是 TXLivePlayerController
```dart
TXLivePlayerController controller = TXLivePlayerController();
```

### step 2: 渲染 View
接下来我们要给播放器的视频画面找个地方来显示，Flutter 系统中使用 Widget 作为基本的界面渲染单位，所以您只需要准备一个 Widget 并调整好布局就可以了。你可以直接使用 TXPlayerVideo 或者继承它来显示，也可以参考源码实现自定义视图。

```dart
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('直播'),
      ),
      body: SafeArea(
        child: Column(
          children: [
            Container(
              height: 150,
              color: Colors.black,
              child: Center(
                child: _aspectRatio>0?AspectRatio(
                  aspectRatio: _aspectRatio,
                  child: TXPlayerVideo(controller: _controller),
                ):Container(),
              ),
            ),],
        ),
      ),
    );
  }
```

### step 3: 启动播放
```dart
String flvUrl = "http://liteavapp.qcloud.com/live/liteavdemoplayerstreamid_demo1080p.flv";
await _controller.startLivePlay("flvUrl", playType: TXPlayType.LIVE_FLV);
```

| 可选值 | 枚举值 | 含义 |
|---------|---------|---------|
| PLAY_TYPE_LIVE_RTMP | 0 | 传入的 URL 为 RTMP 直播地址 |
| PLAY_TYPE_LIVE_FLV | 1 | 传入的 URL 为 FLV 直播地址 |
| PLAY_TYPE_LIVE_RTMP_ACC | 5 | 低延迟链路地址（仅适合于连麦场景） |
| PLAY_TYPE_VOD_HLS | 3 | 传入的 URL 为 HLS（m3u8） 播放地址 |

> **关于 HLS（m3u8）**
> 在 App 上我们不推荐使用 HLS 这种播放协议播放直播视频源（虽然它很适合用来做点播），因为延迟太高，在 App 上推荐使用 LIVE_FLV 或者 LIVE_RTMP 播放协议。

### step 4: 暂停播放
对于直播播放而言，并没有真正意义上的暂停，所谓的直播暂停，只是**画面冻结**和**关闭声音**，而云端的视频源还在不断地更新着，所以当您调用 resume 的时候，会从最新的时间点开始播放，这跟点播是有很大不同的（点播播放器的暂停和继续与播放本地视频文件时的表现相同)。

```dart
// 暂停
_controller.pause();
// 恢复
_controller.resume();
```

### step 5: 结束播放
```dart
// 停止播放
_controller.stop();
```

<h3 id="Message">step 7: 消息接收</h3>
此功能可以在推流端将一些自定义 message 随着音视频线路直接下发到观众端，适用场景例如：
（1）冲顶大会：推流端将<strong>题目</strong>下发到观众端，可以做到“音-画-题”完美同步。
（2）秀场直播：推流端将<strong>歌词</strong>下发到观众端，可以在播放端实时绘制出歌词特效，因而不受视频编码的降质影响。
（3）在线教育：推流端将<strong>激光笔</strong>和<strong>涂鸦</strong>操作下发到观众端，可以在播放端实时地划圈划线。

通过如下方案可以使用此功能：
- 通过 **onPlayerEventBroadcast** 监听消息，消息编号：**PLAY_EVT_GET_MESSAGE （2012）**

```dart
_controller.onPlayerEventBroadcast.listen((event) {//订阅事件分发
    if(event["event"] == 2012) {
        String msg = event["EVT_GET_MSG"];
    }
});
```

### step 6: 清晰度无缝切换
日常使用中，网络情况在不断发生变化。在网络较差的情况下，最好适度降低画质，以减少卡顿；反之，网速比较好，可以观看更高画质。
传统切流方式一般是重新播放，会导致切换前后画面衔接不上、黑屏、卡顿等问题。使用无缝切换方案，在不中断直播的情况下，能直接切到另条流上。

清晰度切换在直播开始后，任意时间都可以调用。调用方式如下
```objectivec
// 正在播放的是流http://5815.liveplay.myqcloud.com/live/5815_62fe94d692ab11e791eae435c87f075e.flv，
// 现切换到码率为 900kbps 的新流上
_controller.switchStream("http://5815.liveplay.myqcloud.com/live/5815_62fe94d692ab11e791eae435c87f075e_900.flv");
```

>清晰度无缝切换功能需要在后台配置 PTS 对齐，如您需要可 [提交工单](https://console.cloud.tencent.com/workorder) 申请使用。


### step 7: 直播回看
时移功能是腾讯云推出的特色能力，可以在直播过程中，随时观看回退到任意直播历史时间点，并能在此时间点一直观看直播。非常适合游戏、球赛等互动性不高，但观看连续性较强的场景。

```dart
// 设置直播回看前，先调用 startLivePlay
// 开始播放 ...
await _controller.setAppID(appId); // 配置 appId
await _controller.prepareLiveSeek(domian, bizidNum);    // 后台请求直播起始时间 
```
配置正确后，在 PLAY_EVT_PLAY_PROGRESS 事件里，当前进度就不是从0开始，而是根据实际开播时间计算而来。
调用 seek 方法，就能从历史事件点重新直播
```dart
await _controller.seek(600); // 从第10分钟开始播放
```

接入时移需要在后台打开2处配置：

1. 录制：配置时移时长、时移储存时长
2. 播放：时移获取元数据

时移功能处于公测申请阶段，如您需要可提交工单申请使用。


<h2 id="Delay">延时调节</h2>
腾讯云 SDK 的直播播放（LVB）功能，并非基于 ffmpeg 做二次开发， 而是采用了自研的播放引擎，所以相比于开源播放器，在直播的延迟控制方面有更好的表现，我们提供了三种延迟调节模式，分别适用于：秀场，游戏以及混合场景。

- **三种模式的特性对比**

| 控制模式 | 卡顿率 | 平均延迟 | 适用场景 | 原理简述 |
|---------|---------|---------| ------ | ----- |
| 极速模式 | 较流畅偏高 | 2s- 3s | 美女秀场（冲顶大会）| 在延迟控制上有优势，适用于对延迟大小比较敏感的场景|
| 流畅模式 | 卡顿率最低 | >= 5s | 游戏直播（企鹅电竞） | 对于超大码率的游戏直播（例如吃鸡）非常适合，卡顿率最低|
| 自动模式 | 网络自适应 | 2s-8s | 混合场景 | 观众端的网络越好，延迟就越低；观众端网络越差，延迟就越高 |


- **三种模式的对接代码**

```dart
// 自动模式
_controller.setLiveMode(TXPlayerLiveMode.Automatic);
// 极速模式
_controller.setLiveMode(TXPlayerLiveMode.Speed);
// 流畅模式
_controller.setLiveMode(TXPlayerLiveMode.Smooth);

// 设置完成之后再启动播放
```

> 更多关于卡顿和延迟优化的技术知识，可以阅读 [视频卡顿怎么办？](https://cloud.tencent.com/document/product/454/7946)

<h2 id="RealTimePlay">超低延时播放</h2>
支持<strong>400ms</strong>左右的超低延迟播放时腾讯云直播播放器的一个特点，它可以用于一些对时延要求极为苛刻的场景，例如<strong>远程夹娃娃</strong>或者<strong>主播连麦</strong>等，关于这个特性，您需要知道：

- **该功能是不需要开通的**
  该功能并不需要提前开通，但是要求直播流必须位于腾讯云，跨云商实现低延时链路的难度不仅仅是技术层面的。

- **播放地址需要带防盗链**
  播放 URL 不能用普通的 CDN URL， 必须要带防盗链签名，防盗链签名的计算方法见 [txTime&txSecret](https://cloud.tencent.com/document/product/267/32735)。

- **播放类型需要指定 ACC**
  在调用 startLivePlay 函数时，需要指定 type 为 **PLAY_TYPE_LIVE_RTMP_ACC**，SDK 会使用 RTMP-UDP 协议拉取直播流。

- **该功能有并发播放限制**
  目前最多同时10路并发播放，设置这个限制的原因并非是技术能力限制，而是希望您只考虑在互动场景中使用（例如连麦时只给主播使用， 或者夹娃娃直播中只给操控娃娃机的玩家使用），避免因为盲目追求低延时而产生不必要的费用损失（低延迟线路的价格要贵于 CDN 线路）。

- **Obs 的延时是不达标的**
  推流端如果是 [TXLivePusher](https://cloud.tencent.com/document/product/454/7879)，请使用 [setVideoQuality](https://cloud.tencent.com/document/product/454/7879#7.-.E8.AE.BE.E5.AE.9A.E7.94.BB.E9.9D.A2.E6.B8.85.E6.99.B0.E5.BA.A6) 将`quality`设置为 MAIN_PUBLISHER 或者 VIDEO_CHAT。Obs 的推流端积压比较严重，是无法达到低延时效果的。

- **该功能按播放时长收费**
  本功能按照播放时长收费，费用跟拉流的路数有关系，跟音视频流的码率无关，具体价格请参考 [价格总览](https://cloud.tencent.com/document/product/454/8008)。


## 功能使用
本节将介绍常见直播播放功能的使用方式。

### 1、暂停播放
对于直播播放而言，并没有真正意义上的暂停，所谓的直播暂停，只是**画面冻结**和**关闭声音**，而云端的视频源还在不断地更新着，所以当您调用 resume 的时候，会从最新的时间点开始播放，这跟点播是有很大不同的（点播播放器的暂停和继续与播放本地视频文件时的表现相同）。

```dart
// 暂停
_controller.pause();
// 继续
_controller.resume();
```

### 2、消息接收
此功能可以在推流端将一些自定义 message 随着音视频线路直接下发到观众端，适用场景如下：

- 冲顶大会：推流端将**题目**下发到观众端，可以做到“音-画-题”完美同步。
- 秀场直播：推流端将**歌词**下发到观众端，可以在播放端实时绘制出歌词特效，因而不受视频编码的降质影响。
- 在线教育：推流端将**激光笔**和**涂鸦**操作下发到观众端，可以在播放端实时地划圈划线。

通过如下方案可以使用此功能：
- FTXLivePlayConfig 中的 **enableMessage** 开关置为 **true**。
- TXLivePlayerController 通过 **onPlayerEventBroadcast** 监听消息，消息编号：**PLAY_EVT_GET_MESSAGE（2012）**

```dart
_controller.onPlayerEventBroadcast.listen((event) {
  if (event["event"] == TXVodPlayEvent.PLAY_EVT_GET_MESSAGE) {
    String msg = event[TXVodPlayEvent.EVT_GET_MSG];
  } else if (event["event"] == TXVodPlayEvent.PLAY_ERR_NET_DISCONNECT) {
    print("网络断开，拉流失败");
  }
});
```

### 3、清晰度无缝切换
日常使用中，网络情况在不断发生变化。在网络较差的情况下，最好适度降低画质，以减少卡顿；反之，网速比较好，可以观看更高画质。
传统切流方式一般是重新播放，会导致切换前后画面衔接不上、黑屏、卡顿等问题。使用无缝切换方案，在不中断直播的情况下，能直接切到另条流上。

清晰度切换在直播开始后，任意时间都可以调用。调用方式如下
```dart
// 正在播放的是流 http://5815.liveplay.myqcloud.com/live/5815_62fe94d692ab11e791eae435c87f075e.flv，
// 现切换到码率为900kbps的新流上
_controller.switchStream("http://5815.liveplay.myqcloud.com/live/5815_62fe94d692ab11e791eae435c87f075e_900.flv");
```

>?清晰度无缝切换功能需要在后台配置 PTS 对齐，如您需要可 [提交工单](https://console.cloud.tencent.com/workorder) 申请使用。


### 4、延时调节
腾讯云 SDK 的直播播放（LVB）功能，并非基于 ffmpeg 做二次开发， 而是采用了自研的播放引擎，所以相比于开源播放器，在直播的延迟控制方面有更好的表现，我们提供了三种延迟调节模式，分别适用于：秀场、游戏以及混合场景。

- **三种模式的特性对比**

| 控制模式 | 卡顿率 | 平均延迟 | 适用场景 | 原理简述 |
|---------|---------|---------| ------ | ----- |
| 极速模式 | 较流畅偏高 | 2s - 3s | 美女秀场（冲顶大会）| 在延迟控制上有优势，适用于对延迟大小比较敏感的场景|
| 流畅模式 | 卡顿率最低 | >= 5s | 游戏直播（企鹅电竞） | 对于超大码率的游戏直播（例如吃鸡）非常适合，卡顿率最低|
| 自动模式 | 网络自适应 | 2s - 8s | 混合场景 | 观众端的网络越好，延迟就越低；观众端网络越差，延迟就越高 |


- **三种模式的对接代码**
```dart
FTXLivePlayConfig mPlayConfig = FTXLivePlayConfig();

FTXLivePlayConfig mPlayConfig = FTXLivePlayConfig();
//自动模式
mPlayConfig.autoAdjustCacheTime = true;
mPlayConfig.minAutoAdjustCacheTime = 1;
mPlayConfig.maxAutoAdjustCacheTime = 5;
//极速模式
mPlayConfig.autoAdjustCacheTime = true;
mPlayConfig.minAutoAdjustCacheTime = 1;
mPlayConfig.maxAutoAdjustCacheTime = 1;
//流畅模式
mPlayConfig.autoAdjustCacheTime = false;
mPlayConfig.cacheTime = 5;

_controller.setConfig(mPlayConfig);
//设置完成之后再启动播放
```

>?更多关于卡顿和延迟优化的技术知识，可以阅读 [视频卡顿怎么办？](https://cloud.tencent.com/document/product/454/7946)

### 5、超低延时播放
支持**400ms左右的超低延迟播放**，是腾讯云直播播放器的一个特点，它可以用于一些对时延要求极为苛刻的场景，例如**远程夹娃娃或者主播连麦**等，关于这个特性，您需要知道：

- **该功能是不需要开通的**
  该功能并不需要提前开通，但是要求直播流必须位于腾讯云，跨云商实现低延时链路的难度不仅仅是技术层面的。

- **播放地址需要带防盗链**
  播放 URL 不能用普通的 CDN URL，必须要带防盗链签名，防盗链签名的计算方法见 [**txTime 与 txSecret**](https://cloud.tencent.com/document/product/267/32735)。

- **播放类型需要指定 ACC**
  在调用 startLivePlay 函数时，需要指定 playType 为 **LIVE_RTMP_ACC**，SDK 会使用 RTMP-UDP 协议拉取直播流。

- **该功能有并发播放限制**
  目前最多同时**10路**并发播放，设置这个限制的原因并非是技术能力限制，而是希望您只考虑在互动场景中使用（例如连麦时只给主播使用， 或者夹娃娃直播中只给操控娃娃机的玩家使用），避免因为盲目追求低延时而产生不必要的费用损失（低延迟线路的价格要贵于 CDN 线路）。

- **Obs 的延时是不达标的**
  推流端如果是 [TXLivePusher](https://cloud.tencent.com/document/product/454/7885)，请使用 [setVideoQuality](https://cloud.tencent.com/document/product/454/7885#7.-.E8.AE.BE.E5.AE.9A.E7.94.BB.E9.9D.A2.E6.B8.85.E6.99.B0.E5.BA.A6) 将`quality`设置为 MAIN_PUBLISHER 或者 VIDEO_CHAT。Obs 的推流端积压比较严重，是无法达到低延时效果的。

- **该功能按播放时长收费**
  本功能按照播放时长收费，费用跟拉流的路数有关系，跟音视频流的码率无关，具体价格请参考 [价格总览](https://cloud.tencent.com/document/product/454/8008)。


### 6、获取视频信息
播放器 SDK 通过 URL 字符串播放视频，URL 中本身不包含视频信息。为获取相关信息，需要通过访问云端服务器加载到相关视频信息，因此 SDK 只能以事件通知的方式将视频信息发送到您的应用程序中，更多内容参见 [事件监听](#monitor)。

例如您可以通过`onPlayerNetStatusBroadcast`的`NET_STATUS_VIDEO_WIDTH`和`NET_STATUS_VIDEO_HEIGHT`获取视频的宽和高。具体使用方法见 [状态反馈（onPlayerNetStatusBroadcast）](#onPlayerNetStatusBroadcast)。

[](id:monitor)
## 事件监听
您可以对 `TXLivePlayerController` 的 `onPlayerEventBroadcast`和 `onPlayerNetStatusBroadcast`进行监听，之后 SDK 的内部状态信息均会通过 `onPlayerEventBroadcast`（[事件通知](#onPlayEvent)）和 `onPlayerNetStatusBroadcast`（[状态反馈](#onNetStatus)）通知给您。

[](id:onPlayerEventBroadcast)
### 事件通知（onPlayerEventBroadcast）
#### 1. 播放事件
| 事件 ID                        | 数值   | 含义说明                                     |
| ---------------------------- | ---- | ---------------------------------------- |
| PLAY\_EVT\_PLAY\_BEGIN       | 2004 | 视频播放开始                  |
| PLAY\_EVT\_PLAY\_PROGRESS    | 2005 | 视频播放进度，会通知当前播放进度、加载进度 和总体时长              |
| PLAY\_EVT\_PLAY\_LOADING     | 2007 | 视频播放 loading，如果能够恢复，之后会有 LOADING\_END 事件 |
| PLAY\_EVT\_VOD\_LOADING\_END | 2014 | 视频播放 loading 结束，视频继续播放                   |

**不要在收到 PLAY_LOADING 后隐藏播放画面**：因为 PLAY_LOADING -> PLAY_BEGIN 的时间长短是不确定的，可能是5s或5ms，有些用户考虑在 LOADING 时隐藏画面， BEGIN 时显示画面，会造成严重的画面闪烁（尤其是直播场景下）。**推荐的做法是在视频播放画面上叠加一个半透明的 loading 动画。**

#### 2. 结束事件
| 事件 ID                 |    数值  |  含义说明                |   
| :-------------------  |:-------- |  :------------------------ | 
|PLAY_EVT_PLAY_END      |  2006|  视频播放结束   | 
|PLAY_ERR_NET_DISCONNECT |  -2301  |  网络断连,且经多次重连亦不能恢复,更多重试请自行重启播放 | 
|PLAY_ERR_HLS_KEY       | -2305 | HLS 解密 key 获取失败 |

**如何判断直播已结束？**
基于各种标准的实现原理不同，很多直播流通常没有结束事件（2006）抛出，此时可预期的表现是：主播结束推流后，SDK 会很快发现数据流拉取失败（PLAY_WARNING_RECONNECT），然后开始重试，直至三次重试失败后抛出 PLAY_ERR_NET_DISCONNECT 事件。

因此，2006和-2301都要监听，用来作为直播结束的判定事件。

#### 3. 警告事件
如下的这些事件您可以不用关心，它只是用来告知您 SDK 内部的一些事件。

| 事件 ID                 |    数值  |  含义说明                    |   
| :-------------------  |:-------- |  :------------------------ | 
| PLAY_WARNING_VIDEO_DECODE_FAIL   |  2101  | 当前视频帧解码失败  |
| PLAY_WARNING_AUDIO_DECODE_FAIL   |  2102  | 当前音频帧解码失败  |
| PLAY_WARNING_RECONNECT           |  2103  | 网络断连, 已启动自动重连 (重连超过三次就直接抛送 PLAY_ERR_NET_DISCONNECT 了) |
| PLAY_WARNING_HW_ACCELERATION_FAIL|  2106  | 硬解启动失败，采用软解   |

[](id:onPlayerNetStatusBroadcast)
### 状态反馈（onPlayerNetStatusBroadcast）
通知每秒都会被触发一次，目的是实时反馈当前的推流器状态，它就像汽车的仪表盘，可以告知您目前 SDK 内部的一些具体情况，以便您能对当前网络状况和视频信息等有所了解。
<table>
<tr><th>评估参数</th><th>含义说明</th></tr>
<tr>
<td>NET_STATUS_CPU_USAGE</td><td>当前瞬时 CPU 使用率</td>
</tr><tr>
<td>NET_STATUS_VIDEO_WIDTH</td><td>视频分辨率 - 宽</td>
</tr><tr>
<td>NET_STATUS_VIDEO_HEIGHT</td><td>视频分辨率 - 高</td>
</tr><tr>
<td>NET_STATUS_NET_SPEED</td><td>当前的网络数据接收速度</td>
</tr><tr>
<td>NET_STATUS_VIDEO_FPS</td><td>当前流媒体的视频帧率</td>
</tr><tr>
<td>NET_STATUS_VIDEO_BITRATE</td><td>当前流媒体的视频码率，单位 kbps</td>
</tr><tr>
<td>NET_STATUS_AUDIO_BITRATE</td><td>当前流媒体的音频码率，单位 kbps</td>
</tr><tr>
<td>NET_STATUS_CACHE_SIZE</td><td>缓冲区（jitterbuffer）大小，缓冲区当前长度为0，说明离卡顿就不远了</td>
</tr><tr>
<td>NET_STATUS_SERVER_IP</td><td>连接的服务器 IP</td>
</tr></table>