WeChat Mini Program SDK 3.10
DEMO URL
Product Introduction
Overview
The polyv Mini Program SDK provides live streaming playback, on-demand playback, document rendering, and other features for WeChat Mini Programs. It also offers a set of components that allow users to flexibly combine their own business logic.
Configure WeChat Live Streaming Permissions
The SDK uses WeChat's live-player for live streaming and live-pusher for co-hosting. You need to first pass the category review (see WeChat Mini Program SDK Category Application Qualification Requirements), and then enable the corresponding component permissions in the Mini Program Management Console under "Development" - "Interface Settings".
Features
| Feature | Description |
|---|---|
| Video | Supports live streaming and on-demand video playback. Encrypted videos are not supported. |
| Document | Supports document display and annotation. PPT animations are not supported. |
| Teaching & Live Interaction | Supports voice and video live interaction. Supports 1v1 live interaction. |
| Online Chat | Supports online chat. |
Target Audience
This document is a technical document intended for readers who:
- Have basic mini-program development skills.
- Are preparing to integrate or have already integrated with POLYV Video Cloud.
- Have a basic understanding of how to use POLYV Video Cloud.
Usage Steps
Development Preparation
Obtain Access Key
Log in to the POLYV Live Streaming Backend - Cloud Live Streaming - Development Settings - Identity Authentication
WeChat Interface Whitelist Configuration
request合法域名
https://miniapp.agoraio.cn
https://uni-webcollector.agora.io
https://router.polyv.net
https://api.polyv.net
https://prtas.videocc.net
https://rtas.videocc.net
https://hls.videocc.net
https://player.polyv.net
https://livestatic.videocc.net
https://livejson.polyv.net
https://live.polyv.net
https://doc-2.polyv.net
https://doc.polyv.net
https://img.videocc.net
https://liveimages.videocc.net
使用连麦功能需加上以下域名:
https://uap-ap-web-1.agora.io
https://uap-ap-web-2.agoraio.cn
https://uap-ap-web-3.agora.io
https://uap-ap-web-4.agoraio.cn
https://report-ad.agoralab.co
https://rest-argus-ad.agoralab.co
https://uni-webcollector.agora.io
https://cloud.tencent.com
https://yun.tim.qq.com
https://webim.tim.qq.com
socket合法域名
wss://chat.polyv.net
wss://miniapp.agoraio.cn
SDK Usage
The SDK uses TypeScript code, so you need to download the latest developer tools to support it. Native TypeScript Support
The SDK provides custom components. Polyv offers a complete set of business logic for users to use out of the box. It also provides player components, PPT document components, chatroom components, etc., allowing users to flexibly combine their own business logic.
Before use, you need to call the setApp method in app.js的onLaunch中.
Method 1 (Recommended): Pass the verifyUrl verification interface
import plv from '*/sdk/core/index';
onLaunch() {
plv.setApp({
apiId: '',
verifyUrl: ''
});
}
verifyUrl Verification Interface Rules
(1) When the mini-program requests the verifyUrl interface, it will include the following parameters (the number of parameters/parameter names are not fixed). All parameters except the sign parameter must be sorted alphabetically, then combined with the appSecret according to the rules for MD5 encryption, and the result should be returned to the mini-program.
Request Parameter Description
| Parameter Name | Type | Description |
|---|---|---|
| appId | string | Account appId [See Obtaining Secret Key] |
| timestamp | number | 13-digit millisecond-level timestamp |
| sign | string | verifyUrl verification sign reference (2) verifyUrl verification |
Response Parameter Description
| Parameter | Type | Description |
|---|---|---|
| code | Integer | Response status code, 200 for success, non-200 for failure [see Global Error Description] |
| status | String | Response status text |
| message | String | Response description, when code is 400 or 500, provides additional error details |
| data | Object | Returns account available live streaming minutes on success [see data field description] |
verifyUrl API PHP Code Example:
<?php
$appSecretKey = 'Your appSecretKey';
$sign = $_GET['sign'];
$appId = $_GET['appId'];
$timestamp = $_GET['timestamp'];
// 获取url query并转换成数组
parse_str($_SERVER["QUERY_STRING"], $params);
// 获取除sign外的其他参数的拼接字符串
$concated = sort_param($params);
// STEP 1
// 计算接口请求是否合法
$outPutData = '';
$verifyUrlSign = strtoupper(md5("plyMinApp".$concated."plyMinApp"));
if ($sign != $verifyUrlSign) {
$outPutData = '{"code": 200, "message" : "invalid sign", "status": "error", "data": ""}';
echo($outPutData);
return;
}
// STEP 2
// 输出正确sign返回给小程序
$outPutSign = strtoupper(md5($appSecretKey.$concated.$appSecretKey));
$outPutData = '{"code": 200, "message" : "", "status": "success", "data": {"sign": "'.$outPutSign.'"}}';
echo($outPutData);
/**
* 将参数按照ASCKII升序 key + value + key + value ... +value 拼接
* @return [type] [description]
*/
function sort_param($params){
ksort($params);
$sort_result = "";
foreach ($params as $key => $val) {
if(!is_null($val) && $key != 'sign'){
$sort_result=$sort_result.$key.$val;
}
}
return $sort_result;
}
?>
(2) verifyUrl validation
Sign Verification Rules:
concatedtakes the value of concatenating the parametersappId,timestamp, and other parameters in ascending ASCII order askey + value + key + value ... + value.verifyUrlSignvalue:plyMinApp${concated}plyMinAppuppercase MD5 value after string concatenation
$verifyUrlSign = strtoupper(md5("plyMinApp".$concated."plyMinApp"));
Successful Example
{
"code":200,
"status":"success",
"message":"",
"data":{
"sign":"3DDE7222C4264F225931053A661889BA"
}
}
Exception Example
{
"code": 400,
"status": "error",
"message": "invalid signature.",
"data": ""
}
Method 2: Pass the access key of the Polyv Cloud Live Streaming
Since the apiSecret is displayed in plaintext in the mini-program code, there is a risk of the mini-program being decompiled. Therefore, Method 1 is recommended.
import plv from '*/sdk/core/index';
onLaunch() {
plv.setApp({
apiId: '',
apiSecret: ''
});
}
Using Components
1. Using the polyv component. Refer to the polyv directory in the demo.
Copy the SDK code into your own project, and import the component in the JSON file of the page that uses the SDK.
{ "usingComponents": { "polyv": "*/sdk/components/polyv/polyv" } }Using polyv components in wxml
<view> <polyv /> </view>Call the
initmethod in the page'sonloadand thedestroymethod inonUnload.
The init method initializes the viewing process, retrieves channel details, initializes socket events, and so on.
import plv from '*/sdk/core/index';
// onLoad
onLoad() {
const options = {
channelId: '', // 频道ID
openId: '', // 用户openId
userName: '', // 用户名
avatarUrl: '', // 用户头像
param4: '', // 自定义参数
param5: '', // 自定义参数
};
plv.init(options);
}
// onUnload
onUnload() {
plv.destory();
}
2. Flexible Component Composition. Refer to the demo's polyv-sub.
Import the component in the JSON file of the page that uses the SDK.
{ "usingComponents": { "player": "*/sdk/components/player/player", "ppt": "*/sdk/components/ppt/ppt", ... } }Use the component in WXML and pass the necessary parameters.
<view> <player videoOption="{{ videoOption }}" bind:onLiveStatusChange="playerLiveStatusChange" /> <ppt /> </view>Call the
initmethod in the page'sonloadmethod.import plv from '*/sdk/core/index'; Page({ onLoad() { const options = { ... }; options.plvInsideUse = true; // 区分是学一学还是sdk, 当为 true 时开启抽奖模块。 plv.init(options) .then(data => { const { detail, chat } = data; // 处理业务逻辑 }) .catch(err => { // 异常处理 }); }, onUnload() { plv.destory(); } });
Component Details
Before using the following components, you must first introduce them in the JSON through the usingComponents field.
1. polyv Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| userBanned | Event | No | - | Triggered when user is kicked out |
| onError | Event | No | - | An error occurred |
| allowDanmu | Boolean | No | true | Whether to allow danmaku |
| skinAlwaysShow | Boolean | No | false | Whether to always show player skin |
| usePlayerSkin | Boolean | No | true | Whether to use player skin |
<polyv
bind:userBanned="handleUserBanned"
bind:onError="handlePolyvError"
allowDanmu="{{ false }}"
skinAlwaysShow="{{ true }}"
usePlayerSkin="{{ false }}"
hasAnswerCard="{{ true }}"
/>
2. Player Component
The player component can play both live and on-demand content. Live streaming uses live-player, 模拟器不能播放,查看效果请用真机。 while the on-demand simulator cannot play 加密视频,查看效果请用真机。.
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| videoOption | Object | Yes | Empty | Player initialization parameters (see JS code below) |
| vodSeek | Number | No | 0 | Seek operation time point for playback video (effective only when mode is vod or during temporary storage) |
| onLiveStatusChange | Event | No | - | Live stream status (triggered when mode is live) |
| onLiveStorageProgress | Event | No | - | Current playback time of live temporary storage |
| onVodProgress | Event | No | - | Current playback time of VOD playback |
| onVodEnd | Event | No | - | VOD playback ended |
| onError | Event | No | - | An error occurred |
| allowDanmu | Boolean | No | true | Whether to allow danmaku |
| skinAlwaysShow | Boolean | No | false | Whether to always show the player skin |
| usePlayerSkin | Boolean | No | true | Whether to use the player skin |
| hasAnswerCard | Boolean | No | false | Whether to use the answer card |
Note: The priority of skinAlwaysShow is higher than usePlayerSkin. If skinAlwaysShow is set to true, the usePlayerSkin parameter becomes invalid.
<player
videoOption="{{ videoOption }}"
allowDanmu="{{ false }}"
skinAlwaysShow="{{ true }}"
usePlayerSkin="{{ false }}"
bind:onLiveStatusChange="playerLiveStatusChange"
bind:playerVodProgress="playerVodProgress"
bind:onVodEnd="playerVodEnd"
bind:onLiveVodEnd="playerVodEnd"
bind:onError="playerError"
/>
// ###### 播放直播或者暂存视频 #######
videoOption = {
mode: 'live',
uid: userId, // 直播频道uid
cid: channelId, // 直播频道channelId
isAutoChange: true, // 自动切换直播和暂存。
vodsrc: '', // 指定回放地址。有暂存视频的情况下,传入暂存视频的mp4或者m3u8。
pipMode: '', // 是否使用小窗模式,默认为undefined。相关参数设置详情参考注意2.2
forceVideo: false, // 是否强制使用video标签作为播放器(播放m3u8),建议使用live-player
statistics: { // 播放器自定义统计参数, 如需添加param4、param5参数,详情见下面init方法详解
param1: 'param1', // 用户ID
param2: 'param2', // 用户昵称
},
// logoConfig: {
// enable: false, // 是否显示logo
// position: 'tl',// logo位置1 左上,2 右上(默认)3左下 4 右下
// opacity: 0.5, // 透明度
// src: '' // logo图片的url
// }
};
// 直播状态改变: 只有在mode为live时才会触发。
playerLiveStatusChange(e) {
const status = e.detail.status;
if (status === 'live') {
// 开始直播
}
if (status === 'end') {
// 结束直播
}
}
// 获取回放播放进度
playerVodProgress(e) {
console.info(e.detail.currentTime, '----currentTime---');
}
// 播放器异常捕获
playerError(e) {
console.info(e.detail, '-----e-----');
}
/*
* 回放播放结束事件
* onVodEnd: 点播回放列表播放结束触发,返回当前播放结束点播视频vid
* onLiveVodEnd: 直播暂存播放结束时触发,返回当前暂存视频播放地址
*/
playerVodEnd(e) {
console.info(e.detail.curVodVid, '---curVodVid---');
}
// ###### 播放点播视频 #######
videoOption = {
mode: 'vod',
vodVid: '' // 播回放时vodVid为videoPoolId
};
// 在mode为vod时,从点播切换到直播状态,云课堂和普通直播监听直播开始的方法不同。
// 1. 普通直播通过轮询api.getOrdinaryLiveStatus(stream)获取当前的状态
// 2. 云课堂通过chat.on(chat.events.SLICESTART, () => {})监听直播开始
Note:
2.1 Priority of skinAlwaysShow and usePlayerSkin
skinAlwaysShow takes precedence over usePlayerSkin. If skinAlwaysShow is set to true, the usePlayerSkin parameter becomes invalid.
2.2 pipMode
Parameter Range and Trigger Conditions: Refer to the official API live-player component parameters. According to the official documentation, the base library must be upgraded to version 2.11.0 to support the mini-window feature. It is recommended to log in to the official account platform, go to Settings -> Minimum Base Library Version Settings, and perform the upgrade.
3. PPT Document Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| chatData | Object | Yes | - | Channel details |
| videoId | String | No | - | videoId of current replay |
| vidCurrentTime | Number | No | - | Current replay play time |
| pptSize | Object | Yes | - | Document size { height, width } |
<ppt
chatData="{{ detail }}"
videoId="{{videoId}}"
vidCurrentTime="{{vodPlayerProgress}}"
pptSize='{{pptSize}}'
/>
// 直播时传入chatData
// 播放点播时传入回放的videoId和当前回放播放时间
4. Concat Co-hosting Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| channelDetail | Object | Yes | - | Channel details |
| applyData | Object | Yes | {show: false, txt: 'Request connection'} | |
| show | Event | Yes | - | Room connection status: enabled/disabled |
| refreshStatus | Event | Yes | - | Connection status change: raise hand apply/wait for allow cancel/hang up stop |
| stop | Event | Yes | - | Stop connection |
<concat
id="test"
channelDetail="{{ channelDetail }}"
applyData="{{ applyData }}"
bind:show="handleShowConcatApply"
bind:refreshStatus="handleRefreshStatus"
bind:stop="handleStop"
/>
//js
// 监听当前房间连麦状态
// 弃用
handleShowConcatApply(data) {
// data.detail.status为open/close
// open: 当前房间已开启连麦
// close: 当前房间未开启连麦
},
// 监听当前用户连麦状态
// 只有在房间开启了连麦功能后,用户才能进行连麦
handleRefreshStatus(data) {
// data.detail: {
// show: true/false, // 是否能连麦
// type: 'apply'/'cancel'/'stop', // 当前连麦类型:未举手/已举手/连麦中
// txt: '' //对应连麦类型:申请连线/取消申请/挂断连线
//}
},
// 结束连麦
handleStop(data) {
console.info(data.detail, '---stop----');
}
Description of Methods Related to the Live Co-hosting Component
- apply
Note: The method for controlling co-hosting. Both "Raise Hand" and "Cancel Request" require calling this method. After calling this method, the component will determine the current co-hosting status and trigger the corresponding event.
stop
Note: Disconnect the call.
5. Chatroom Component
<chatroom bind:onTapBulletin="handleShowBulletin" />
handleShowBulletin() {
console.info('===点击聊天室公告按钮触发====');
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| showBulletin | Boolean | No | true | Whether to show the bulletin button |
| skin | String | No | black | Skin (black, white) |
6. Quiz Consultation Component
<quiz />
7. Playback History Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| playbackList | Array | No | [ ] | Playback list |
| nextVod | String | No | '' | Current playback videoPoolId |
| onTapPlayback | Event | No | null | Callback function for tapping playback |
<playback
playbackList="{{ playbackList }}"
nextVod="{{ currentVodId }}"
bind:onTapPlayback="handlePlayback"
/>
// 回放列表通过api.getPlayBackVideos(channelId)获取
// 播放下一个回放,nextVod传入当前的回放videoPoolId
// 点击某个回放时,通知player播放
handlePlayback(e) {
const { videoPoolId, videoId } = e.detail;
}
8. Chapter Section Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| chapterList | Array | Yes | [ ] | Chapter list |
| vodCurTime | Number | Yes | 0 | Current play time |
| onTapChapter | Event | No | Empty | Chapter tap callback |
<chapter
bind:onTapChapter="handleChangeChapter"
vodCurTime="{{ vodPlayerProgress }}"
chapterList="{{ chapterList }}"
/>
// 回放列表通过api.getChapterRecords(channelId)获取
// vodCurTime: 当前播放器的播放时间
// onTapChapter
handleChangeChapter(e) {
const chapter = e.detail.chapter;
}
9. menu-custom Custom Menu Component
Parameter Introduction
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| parseHtml | String | Yes | Empty | Rich text string |
10. Sign-in Interactive Feature Component
<sign bind:onSignShow="handleSignShow" />
handleSignShow() {
console.info('====收到签到开始事件,显示签到弹窗时触发====');
}
11. Question Interactive Function Questionnaire Component
<question zIndex="2000" />
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| zIndex | Number | No | - | Set popup layer |
12. answer-card Interactive Quiz Card Component
<answer-card
class="c-answer-card"
answerCardSize="{{ answerCardSize }}"
bind:onAnswerCardShow="handleShowAnswerCard" />
handleShowAnswerCard() {
console.info('=====收到答题事件,显示答题卡弹窗时触发====');
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| answerCardSize | Object | No | Empty | Set the answer card popup size { height: 400, width: 750 } |
| zIndex | Number | No | - | Set the popup z-index |
| class | String | No | Empty | Set the component style |
13. Lottery Interactive Drawing Component
<lottery zIndex="{{ lotteryIndex }}"/>
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| zIndex | Number | No | - | Set popup z-index |
14. Bulletin Interactive Feature Announcement Component
<bulletin
show="{{ true }}"
zIndex="2001"
bulletinStr="公告显示内容"
bind:onClose="handleHideBulletin"/>
handleHideBulletin() {
console.info('===点击关闭公告按钮触发===');
}
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| show | Boolean | Yes | false | Controls when to display the announcement |
| bulletinStr | String | Yes | '' | Announcement content |
Announcements can customize the displayed content. To display chat room announcement messages, you need to listen for the chat room's "BULLETIN" event to retrieve and show the announcement content.
Using the API
The core methods for using the feature are setApp, init, destroy, and api.
setApp
Set the appId and appSecret for Polyv Cloud Live Streaming. This is typically called in the onLaunch method of app.js.
init
On successful initialization, return 频道详情:detail, 聊天室:chat, 网络请求:api.
plv.init(options)
.then(r => {
// 初始化成功
const { detail, chat } = r;
})
.catch(err => {
// 初始化失败
console.error(err);
});
Custom Statistical Parameter Settings
If you need to pass custom interaction statistical data param4 and param5, add the parameters in options.
If you are directly referencing the Polyv component, simply add param4 and param5 in options. If you are referencing the player component separately, you need to set the videoOption parameter of the player component.
Directly reference the Polyv component example:
plv.init(options = {..., param4: 'param4', param5: 'param5'})
.then(r => {
// 初始化成功
const { detail, chat } = r;
})
.catch(err => {
// 初始化失败
console.error(err);
});
Example of Using the Player Component Alone
<player
videoOption="{{ videoOption }}"
/>
videoOption = {
statistics: {
param4: 'param4',
param5: 'param5'
}
}
plv.init(options = {..., param4: 'param4', param5: 'param5'})
.then(r => {
// 初始化成功
const { detail, chat } = r;
})
.catch(err => {
// 初始化失败
console.error(err);
});
Related Parameter Description
| Parameter | Type | Required | Description |
|---|---|---|---|
| appId | String | Yes | Polyv Cloud Live appId |
| appSecret | String | Yes | Polyv Cloud Live appSecret |
| channelId | String|Number | Yes | Channel ID |
| openId | String | Yes | Mini Program user openId |
| userName | String | Yes | User nickname |
| avatarUrl | String | Yes | User avatar |
Channel Details: detail
| Attribute | Description |
|---|---|
| channelMenus | Page menus set in the backend |
| scene | Current live type: ppt (cloud classroom) / alone (standard live) |
| status | Live status: Y (live) / N (not live) |
| name | Live name |
| desc | Live description |
| publisher | Host |
| userId | User ID |
| likes | Like count |
| pageView | View count |
| channelId | Channel ID |
| playbackEnabled | Whether playback is enabled |
| hasPlayback | Whether playback exists |
| playbackList | Playback list |
| recordFileSimpleModel | Live recording temp |
| warmUpImg | Warm-up image |
| warmUpFlv | Warm-up video |
| coverImage | Cover image |
| stream | Live stream name |
| startTime | Live start time |
| sessionId | Session ID |
| chatToken | Chat room authentication token |
Chat Feature: chat
- chat.events All Events
| Parameter | Description |
|---|---|
| CONNECT | Connect socket |
| DISCONNECT | Disconnect |
| ERROR | Socket error |
| RECONNECT_ATTEMPT | Reconnection attempt |
| CLOSE_ROOM | Room closed |
| OPEN_ROOM | Room opened |
| SYSTEM_MESSAGE | System message |
| SPEAK | User speaks |
| SPEAK_ERROR | Speak error |
| SPEAK_CENSOR | Speak review |
| FLOWERS | Send flowers |
| CHAT_IMG | Image |
| REWARD | Reward info |
| CUSTOMER_MESSAGE | Custom message |
| SERVER_ERROR | Server error |
| KICK_USER | User kicked |
| REMOVE_HISTORY | Clear chat history |
| REMOVE_CONTENT | Clear a specific message |
| HISTORY_MESSAGE | Get historical chat messages |
| SEND_MESSAGE | Message sent successfully |
| PROHIBIT_TO_SPEAK | Mute user |
| LOGIN | Login |
| LOGOUT | Logout |
| LOGIN_REFUSE | Login refused |
| SLICESTART | Cloud class starts |
| MICROPHONE | Microphone |
| ALLOW_MICROPHONE | Allow microphone |
| SUCCESS_MICROPHONE | Microphone connected |
| JOIN_CHANNEL_FAIL | Failed to join channel |
| BAN_USER_ROOM | Ban user from room |
| UPDATE_QUESTION_HISTROY | UPDATE_QUESTION_HISTROY |
| S_QUESTION | Student asks question |
| T_ANSWER | Teacher/TA/Admin answers question |
chat.socket Get socket object
chat.on listens to events
chat.off Uninstall events event
chat.trigger Trigger Event
chat.optionspassed in optionschat.roomClosed Whether the room is closed
chat.teacherData Teacher Information
Chat Room API Related
| api | params | desc |
|---|---|---|
| historyCount | None | Get the number of chat room history messages |
| getHistoryMessage() | start I int: Start row number end | int: End row number Request example: chat.getHistoryMessage(end, start, data => {console.log(data)}) |
Get chat room records return | Array |
| hasMoreHistory() | None | Check if there are more history records |
| getOnlineUserList() | Request example: chat.getOnlineUserList().then(res => {console.log(res)}) |
Get the online user list of the channel return | Object |
| sendFlower() | None | Send a flower |
| sendLike() | num | int: Number of likes Request example: chat.sendLike(1) |
Send a like |
| send() | msg | String: Text message Request example: chat.send('Hello,World') |
Send a message |
Consultation Room API Related
chat.getQuestionHistoryMessage()- Retrieve consultation historychat.sendQuestion()- Send a consultation messageCo-hosting
chat.checkCurrentStatus()Query the current co-hosting statuschat.cancelJoinChannel()Cancel joining the co-hosting channel
destory
Call this method in the page's onUnload lifecycle hook to reset the data.
api
api.getUserId(openId)– Retrieve the user IDapi.getChannelDetail(channelId)– Retrieve channel detailsapi.getOrdinaryLiveStatus(channelId)– Retrieve the status of a regular live streamapi.getPlayBackVideos(channelId)– Retrieve the list of playback videosapi.getChapterRecords(params)– Retrieve chapter informationparams.id– The file ID of a temporary file or the video ID of a playback video (whentypeisrecord,idis the file ID of the temporary file; whentypeisplayback,idis the video ID of the playback video)params.channelId– Channel IDparams.type– Type (record: temporary file type;playback: playback type)
api.getChannelKey(channelId)– Retrieve the co-hosting key
Error Message Description
| Error Code | Description |
|---|---|
| 31000 | Failed to retrieve on-demand video data |
| 31001 | vid cannot be empty |
| 31002 | On-demand video has expired |
| 31003 | Account has no traffic |