开始使用
Orbit 账户的组织方式、控制台各处的功能,以及从登录到让我们的网络开始应答一个域名的最短路径。
直播通过 RTMPS 推送到一座城市,从所有城市被观看。本页讲创建频道、开播、观看地址,以及修改密钥时会发生什么。
频道是拥有地址的那个东西,直播是它上面的一次会话。频道只需创建一次,可以反复使用 —— 观看地址在两次直播之间不会改变。
curl -X POST https://dash.yunzheng.space/api/v1/stream/streams \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sunday service"}'应答中包含推流密钥。它只出现这一次,之后无法再读取 —— API 读不到,控制台看不到,我们也拿不到。如果丢失,只能重新签发。
| 字段 | 含义 |
|---|---|
name | 频道名称,最多 60 个字符。 |
visibility | unlisted(默认)或 public。平台没有目录页,两者都只能通过地址访问。 |
city | 推流落在哪座城市。不填则由还有余量的城市接收。 |
placement | 频道如何被访问:city(该城市自己的地址,走公网)或 anycast(我们自己的网络,就近接入)。anycast 自 Pro 套餐起包含。 |
renditions | 在推送画质之下再生成几档,0 到 4。默认取套餐允许的上限;超过上限会被拒绝,而不是被悄悄下调。 |
unlisted 的含义是地址不可被猜到,它不是访问控制:拿到地址的人都能观看。任何支持 RTMP 的推流软件都只需要填两项:服务器与密钥。连接是 RTMPS,每个频道拿到的都是这一个地址:密钥就在推流指令里,不加密的连接意味着软件每次重连都把它送上线路。
Server: rtmps://ingest-<city>.edge.yunzheng.space:1936/live
Key: osk_…频道在创建时选定两种地址中的一种,只选一次:
| 地址 | 含义 |
|---|---|
某一座城市的ingest-<city>.edge.yunzheng.space | 城市由你选定,每次直播都落在那里;其他任何地方都不会应答这个地址。 |
全球唯一的ingest.edge.yunzheng.space | 从任何地方都用同一个地址。连接会落到还有余量的最近城市,不同场次可能不同。Pro 套餐起包含。 |
两者都能在控制台以及创建频道的应答里找到。在命令行下,同样的事情是这样:
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -g 60 -c:a aac -ar 48000 \
-f flv rtmps://ingest-<city>.edge.yunzheng.space:1936/live/$STREAM_KEY| 接受的内容 | 说明 |
|---|---|
| 视频 | H.264。 |
| 分辨率 | 最高 2K(2560×1440)。 |
| 音频 | AAC。 |
| 关键帧 | 每两秒一个或更密。分片在关键帧处切分,间隔越长,观众开播等待越久。 |
renditions 增加的是它之下的档位,由我们这边生成,给连接承载不了原画的观众。有些编码器与硬件根本无法建立加密连接。频道可以被切换为在 RTMPS 之外同时接受明文 RTMP —— 逐个频道设置,默认关闭,加密地址在任何情况下都照常可用。
Server (RTMPS): rtmps://ingest-<city>.edge.yunzheng.space:1936/live
Server (RTMP): rtmp://ingest-<city>.edge.yunzheng.space:1935/live
Key: osk_…curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allow_rtmp":true}'打开后,明文地址会随频道一起返回,字段是 ingest_url_rtmp,关闭时则没有这个地址。若频道没有打开该开关,却用明文推流过来,推流会被拒绝,原因是 rtmps-only,频道事后也会写明这一点。
每个频道默认都以 HLS 能做到的最接近直播的方式分发,新建频道就是这一档。画面被切成 1 秒一片,观众大约落后你 3 到 5 秒。
| 设置 | 观众看到的 |
|---|---|
low默认 | 落后约 3–5 秒,分片 1 秒。前提是编码器每 1 到 2 秒一个关键帧。 |
standard | 落后约 15 秒,分片 4 秒。对关键帧间隔没有要求,网络差时也更稳。 |
在此之上,你还可以手动把观众往后压。delay_s 是 0 到 600 的整秒数,默认 0,也就是不加任何延迟。
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"latency_mode":"low","delay_s":30}'延迟是在分发端生效的,不在播放器里:分片其实已经写好,但播放器读到的列表在分片够「老」之前不会提到它。因此观众换一个播放器也跳不过去,也不会有人用别的方式看而抢在你前面。
两项都在下一场直播生效,并且都能在控制台里、服务器地址旁边改。
低延迟已经是 HLS 能做到的极限,而这个下限大约是 5 秒:播放器开播前要先攒下整片,而清单里公布最后一片时,下一片还在写。频道可以在它旁边再提供一条完全没有分片的通道 —— WebRTC,和视频通话用的是同一套 —— 在这条通道上,观众大约只落后你一秒。
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"realtime_play":true}'频道返回时,会在 playback_url 旁边多出一个 whep_url。我们的播放器两个都要:给了前者它就先试 WebRTC,一旦出问题 —— 被拒绝、网络走不通、八秒内没有画面、连上之后又断 —— 它会自己切到 HLS 地址,不会向观众提示任何东西。HLS 那条路径从不关闭,所以并不存在「无路可退」。
| 你得到什么 | 代价是什么 |
|---|---|
| 大约落后一秒,而且就是你自己的画面 —— 你的分辨率、你的帧率,原样转发,不经过解码。 | 编码器必须不发 B 帧。有 B 帧时,我们只为这条通道重新编码:推流城市多占一个编码器,这些观众收到的画面也不再与你推上来的逐字节相同。开了水印的频道总会为这条通道重新编码,这样这些观众看到的画面里同样有水印。无论哪种情况,HLS 都不受影响。 |
bf=0;或者把 tune 设为 zerolatency,效果相同,还顺便去掉了编码器自身的前瞻延迟。硬件编码器里这一项叫 B 帧 或 连续 B 帧 —— 设为 0。其余设置都不用动,同一路推流照旧服务 HLS 通道。在两个关键帧之间打开页面的观众,要等到下一个关键帧才会出现画面 —— 最多一个关键帧间隔,因为这条通道上的关键帧是你的,观众端的任何请求都无法让它提前出现。按低延迟本来就要求的关键帧间隔(1 到 2 秒),这个等待就是一两秒;如果编码器每 8 秒才一个关键帧,就可能等满八秒,这里没有任何办法把它缩短。
这条通道上没有回看:WebRTC 传的是数据包,不是分片清单,因此进度条会隐藏,画质菜单只有一项 —— 你推上来的那一路画面,也是唯一的一路。播放器其余部分完全一样;页面若想知道最终走的是哪条通道,监听 mode 事件即可。
频道提供推送时的画质,以及可选的若干更低档位。观众的播放器会挑连接能承载的那一档,并随网络变化在档位间切换 —— 对连接较差的观众来说,没有低档位的后果不是画面变小,而是卡住不动。
| 额外档位 | 生成的内容 |
|---|---|
0 | 只有推送的那一路。我们这边完全不解码。 |
1 … 4 | 1080p、720p、480p、360p,从高到低,且永不超过推送画质 —— 720p 的直播即使要四档,也只会得到 480p 与 360p 两档,因为放大不会凭空产生细节。 |
清单里会列出全部档位,由播放器自行选择;没有需要手动切换的地方,也不需要另外发一个地址。
频道可以在画面的一角带上版权水印:灰色半透明的 “Orbit Stream”,下面用更小的字写着 “Powered by YunZheng.Space” 和你的 UID。默认在右下角,画面右下有内容时可以改到左上角;不打开就不会有。
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"watermark":"br"}'水印是在转码时烧进画面的,不是播放器画上去的。因此无论观众用什么播放,收到的画面里都有它,每一个画质档位里都有它,录制下来的那一场里也有它 —— 录像保留的就是播出时的样子。没有任何播放器开关能把它关掉。
文案与 UID 由我们生成,不可自定义:水印的作用就是标明这一场直播属于哪个账号。两个控制台都把这个选项放在服务器地址旁边。
频道可以在没有人推流时播放自己的内容:循环播放若干文件、循环播放自己过往的直播,或者转播你自己的另一个频道。它不是发给每位观众各自播放的视频 —— 它与直播走同一条分发路径,因此所有打开地址的人都处在同一个位置,一小时后进来的人就是从一小时的位置开始看。
curl -X PUT https://dash.yunzheng.space/api/v1/stream/streams/$ID/programme \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"mode": "loop",
"items": [
{ "kind": "recording", "ref": "orc_…", "title": "Last Sunday" },
{ "kind": "recording", "ref": "orc_…" },
{ "kind": "url", "ref": "https://files.example/interval.mp4" }
]
}'| 条目 | 指向什么 |
|---|---|
recording | 本账户的某一次录制,用它的 orc_… 标识。必须已完成且保存在我们的存储中 —— 存在你自己桶里的录制通过带签名、会过期的地址回放,因此不能用在这里。 |
url | 一个 https 地址,指向 MP4 或 HLS 播放列表。明文 http 会被拒绝。 |
channel | 你自己的另一个频道,用它的 ops_… 播放标识,以本频道的地址转播出去。转播别人的频道需要对方授权,目前还没有授权的入口。 |
| 模式 | 作用 |
|---|---|
loop | 按给定顺序播放,播完从头再来。最多 50 条。 |
relay | 把一路直播源以本频道的地址重新分发。只接受一条。 |
一旦有人向频道推流,频道立刻回到直播:节目停止,直播开始,中断不会超过一个分片的长度。推流结束后,频道会像对待任何一次掉线那样先等 15 秒 —— 以防对方马上回来 —— 然后节目继续。
频道用 on 字段报告自己处在三种状态中的哪一种:live、programme 或 off。托管页把中间这种称作「重播」而不是直播;播放器按直播处理,因为它就是一条时间线,地址始终指向它的最前沿。
两种形式,都来自播放标识 —— 它与推流密钥是两个不同的字符串。密钥不会出现在浏览器发起的任何请求里。
https://live.yunzheng.space/p/<playback id> a page that plays it
https://live.yunzheng.space/s/<playback id>/index.m3u8 HLS, for your own player两个地址都随频道一起返回,不需要自己拼;它们在两次直播之间都不会改变。
观众实际连到哪里,由创建频道时的 placement 决定 —— 推流与观看两端是同一个选择:
| 访问方式 | 对观众意味着什么 |
|---|---|
| 你指定的一座城市 | 无论观众在哪里,都连到那一座城市,走公网。一座城市、一个地址,每次都一样。 |
| 我们自己的网络 | 观众连到离自己最近的城市,剩下的路程由网络内部承担。自 Pro 套餐起包含。 |
| 内容 | 边缘保留时长 |
|---|---|
| 画质清单 | 10 秒 —— 它标识本场直播,每场只变一次。 |
| 分片列表 | low 档 1 秒,standard 档 2 秒 —— 半个分片,因为它随直播推进而变化。 |
| 单个分片 | 2 分钟。分片一旦写出就不再改变。 |
托管页用的播放器你也可以直接用。它是一个无依赖的脚本:绘制控件、提供画质选择、显示观众落后直播多少秒,浏览器能原生播放时走原生、不能时加载组件。
<div id="player"></div>
<script src="https://live.yunzheng.space/v1.js"></script>
<script>
OrbitPlayer.mount(document.getElementById("player"), {
src: "<the channel's playback_url>",
hlsjs: "https://live.yunzheng.space/static/player.js",
lang: "en",
latency: "low" // match the channel: "low" (default) or "standard"
});
</script>latency,与频道自身的设置保持一致。不填就是 "low",频道不改也是 "low";在低延迟频道上传 "standard" 会把短窗口白白丢掉,在标准频道上传 "low" 则会多缓冲。Safari 自己播放 HLS,没有可配置的项,这个选项在那里不起作用。再传入 whep,播放器就会先尝试准实时播放,失败时自行退回 src。你的页面其余部分不用改:控件、事件、句柄都一样。
OrbitPlayer.mount(document.getElementById("player"), {
src: "<the channel's playback_url>",
whep: "<the channel's whep_url>", // near-real-time; omit it and nothing changes
hlsjs: "https://live.yunzheng.space/static/player.js",
latency: "low"
}).on("mode", function (m) {
// "realtime" or "hls" - which engine ended up playing
});seekLive() 与后退按键不起作用,latency() 返回 null,levels() 只有你推上来的那一路画面。handle.mode() 随时给出当前使用的引擎,mode 事件在切换时触发 —— 包括准实时尝试失败、由 HLS 接手的那一刻;这是页面唯一能拿到的信号,因为对观众我们刻意什么都不说。三种改造方式,由轻到重:
| 程度 | 做法 |
|---|---|
| 改样式 | 在挂载元素上设置自定义属性:--op-fg、--op-frame、--op-panel、--op-hover、--op-live、--op-warn、--op-line、--op-muted。任何文案可用 text: { live: "直播中" } 覆盖。 |
| 扩展 | 用 controls: { stats: false, pip: false } 关掉单个控件;用 handle.addButton({ icon, aria, onClick }) 加自己的按钮,它和我们的按钮同样构造、落在同一条控件栏里;用 handle.on("state" | "levels" | "level" | "latency" | "mode", fn) 监听。 |
| 完全自建 | controls: false 只挂载引擎:视频元素、档位(levels()、setLevel()、currentLevel())、latency()、seekLive() 和事件,什么都不画。所有控件由你自己绘制。 |
| 按键 | 作用 |
|---|---|
| 空格、K | 播放 / 暂停 |
| J、左方向键 | 往回退十(五)秒,进入直播边缘之后的窗口 |
| L、右方向键 | 回到直播 |
| M | 静音 |
| F | 全屏 |
| 上、下方向键 | 音量 |
直播就是直播:只要没人往回拉,进度条一直是满的、徽标一直显示直播中,不管实测落后多少;因浏览器节流等原因自己掉队的画面会被悄悄追回 —— 低延迟频道超过 8 秒时,标准频道超过 30 秒时。传入 title 可以让锁屏显示这场直播的名字,播放、暂停和媒体键在那里都能用。
任何时候都可以签发新密钥。默认情况下,正在播出的直播会继续使用旧密钥直到结束,新密钥从下一次开播起生效 —— 因此可以在活动进行中轮换密钥而不中断播出。
curl -X POST https://dash.yunzheng.space/api/v1/stream/streams/<id>/key \
-H "Authorization: Bearer $ORBIT_TOKEN"传入 {"revoke_now": true} 可以同时结束正在播出的直播。密钥外泄时应当使用这一种。
频道会显示它是否在播、从何时开始、在哪座城市。历史直播会留存记录:每一次的时间、时长、推送量与分发量,以及结束的方式。
curl https://dash.yunzheng.space/api/v1/stream/streams/<id>/sessions \
-H "Authorization: Bearer $ORBIT_TOKEN"无法开播的频道会在自己身上写明原因,是一句话:套餐额度用尽,或密钥已被吊销。推流软件只会告诉你连接失败,原因在这里。