写给刚接触 RTMP/Flash 音视频协议的同学,用大白话讲清楚这些"神秘代码"到底是什么意思。
一、先讲个故事:什么是 NetStatusEvent?
想象你在使用一个视频直播 App:
- 你点击"开始观看" → 服务器说:"好,开始播放了"
- 网络卡了 → 服务器说:"缓冲区空了,等一下"
- 主播关播了 → 服务器说:"播放已结束"
- 输错了房间号 → 服务器说:"找不到这个流"
这些服务器给你的"通知消息",在 RTMP 协议里就叫做NetStatusEvent(网络状态事件)。
你的代码里定义的NetStatusEvent结构体,就是用来描述这些通知的:
type NetStatusEvent struct { Code string // 具体发生了什么事(比如"播放开始"、"连接失败") Level string // 严重程度(status=正常信息 / error=错误 / warning=警告) }就这么简单:一个事件 = 一件事 + 它的严重程度。
二、Level(严重程度)-- 三种"语气"
Level | 含义 | 打个比方 |
|---|---|---|
| 正常状态信息 | 快递员打电话说"你的包裹已送达" |
| 出错了 | 快递员说"地址不存在,送不了" |
| 警告,可能有问题 | 快递员说"天气不好,可能会晚到" |
三、Code(事件代码)详解 — 按"场景"分类
场景一:NetStream.Play.* — "我在看直播"
这是观众端最常遇到的一组事件:
代码 | Level | 大白话解释 |
|---|---|---|
| status | 播放正式开始!画面出来了 |
| status | 播放结束,主播关了或者你主动停了 |
| status | 播放列表重置了(比如切换清晰度) |
| error | 找不到这个流!房间号可能错了 |
| error | 播放失败,原因不明(可能是没权限) |
| status | 有人开始推流了(你正在看这个流) |
| status | 推流的人停了 |
| status | 切换了流(比如自适应码率切换) |
| status | 播放完成(点播文件播完了) |
实际开发中的典型处理:
func handlePlayEvent(e *NetStatusEvent) { switch e.Code { case NetStream_Play_Start: log.Println("开始播放!渲染画面吧") startRendering() case NetStream_Play_StreamNotFound: log.Println("房间不存在,提示用户") showError("该直播间不存在") case NetStream_Play_Stop: log.Println("播放结束") cleanupPlayer() } }场景二:NetStream.Buffer.* — "数据缓冲区"
直播就像用水管接水,缓冲区就是水桶:
代码 | Level | 大白话解释 |
|---|---|---|
| status | 水桶见底了!数据来得不够快,画面会卡 |
| status | 水桶满了!可以继续流畅播放了 |
| status | 剩余的水都倒出来了(流式处理结束) |
实际用途: 用Buffer.Empty和Buffer.Full来显示/隐藏"加载中"动画。
case NetStream_Buffer_Empty: showLoadingSpinner() // 显示转圈圈 case NetStream_Buffer_Full: hideLoadingSpinner() // 隐藏转圈圈,流畅播放场景三:NetStream.Publish.* — "我在推流"
这是主播端的事件:
代码 | Level | 大白话解释 |
|---|---|---|
| status | 推流成功!观众能看到你了 |
| error | 这个流名已经被别人用了 |
| status | 你连上了但没在推数据(空闲) |
| status | 成功停止推流 |
推流失败最常见的原因: 流名冲突(两个人同时推同一个房间)。
场景四:NetStream.Record.* --"我在录制"
代码 | Level | 大白话解释 |
|---|---|---|
| status | 录制开始 |
| status | 录制结束 |
| error | 录制失败 |
| error | 没有录制权限 |
场景五:NetStream.Seek.* -- "我要快进/拖动"
代码 | Level | 大白话解释 |
|---|---|---|
| status | 跳转成功,从第 5 分钟开始播了 |
| error | 跳转失败(流不支持快进) |
| error | 跳到了不存在的时间(比如视频只有 10 分钟,你跳到了第 20 分钟) |
场景六:NetStream.Pause/Unpause — "暂停/恢复"
代码 | Level | 大白话解释 |
|---|---|---|
| status | 已暂停 |
| status | 已恢复播放 |
场景七:NetConnection.* — "连接服务器"
这是最基础的一组事件,连接阶段就会触发:
代码 | Level | 大白话解释 |
|---|---|---|
| status | 连接成功!可以开始推/拉流了 |
| status | 连接已关闭 |
| error | 连接失败(服务器挂了?网络断了?) |
| error | 被拒绝(没权限、Token 不对) |
| error | 应用名无效(rtmp://server/appName 写错了) |
| error | 服务器正在关闭这个应用 |
连接阶段的典型流程:
func handleConnectionEvent(e *NetStatusEvent) { switch e.Code { case NetConnection_Connect_Success: log.Println("连上了!可以开始 createStream()") createStream() case NetConnection_Connect_Failed: log.Println("连接失败,3秒后重试...") retryAfter(3 * time.Second) case NetConnection_Connect_Rejected: log.Println("被拒绝,检查鉴权信息") showAuthError() } }场景八:NetConnection.Call.* — "调用远程方法"
代码 | Level | 大白话解释 |
|---|---|---|
| error | 调用服务器方法失败 |
| error | 数据包格式不对 |
| error | 跨域被阻止(安全策略) |
场景九:SharedObject.* — "共享对象"
SharedObject 是 RTMP 里的"共享数据"机制(类似聊天室的弹幕同步):
代码 | Level | 大白话解释 |
|---|---|---|
| status | 数据保存成功 |
| error | 数据保存失败 |
| error | 持久化标记冲突 |
| error | URI 不匹配 |
四、实际开发中的最佳实践
统一事件分发器
func DispatchNetStatusEvent(e *NetStatusEvent) { // 先按严重程度分类处理 switch e.Level { case Level_Error: log.Printf("错误: %s", e.Code) handleError(e) case Level_Warning: log.Printf("警告: %s", e.Code) handleWarning(e) case Level_Status: log.Printf("状态: %s", e.Code) handleStatus(e) } }常见业务场景的状态映射
业务场景 | 关注的关键事件 | 处理方式 |
|---|---|---|
观众进入直播间 |
| 显示播放器 |
观众遇到卡顿 |
| 显示/隐藏 loading |
主播开始推流 |
| 确认推流成功 |
主播被踢 |
| 提示重新登录 |
房间不存在 |
| 跳转错误页 |
网络断开 |
| 自动重连 |
func newNetStatusEvent(code, level string) (e *NetStatusEvent) { e.Code = code e.Level = level return e }使用时:
event := newNetStatusEvent(NetStream_Play_Start, Level_Status) DispatchNetStatusEvent(event)五、总结
记住三句话就够了:
Connect系列 → 管"连不连得上"Play/Publish系列 → 管"能不能看/能不能推"Buffer系列 → 管"卡不卡"