功能模块索引
单文件架构 (main.dart ~1,400行),按 14 个功能领域分段讲解
一份逐词解释的技术教程。不预设任何知识,从零开始讲清楚每一个术语。
单文件架构 (main.dart ~1,400行),按 14 个功能领域分段讲解
你手机上的每一个图标——微信、抖音、地图——点开之后运行的那个东西,就是一个 App(Application,应用程序)。App 的本质是一堆指令(告诉手机"在屏幕上画一个按钮"、"收到消息时响一声"),由程序员用一种叫编程语言的特殊文字写出来。手机里的芯片(处理器)能读懂这些指令并执行。
不同的手机需要不同的指令格式:Android 手机和 iPhone 用的不是同一种"语言",所以同一个 App 通常需要写两遍。但有一种技术可以只写一遍、自动翻译到多个平台,这就是 Flutter。
一句话定义:fplayer-ff-mobile 是一个手机/电脑上运行的 App,用来播放同一局域网内其他设备发出的实时视频画面。
当你把手机和电脑连到同一个 WiFi,或者用网线插到同一个路由器上时,这些设备就组成了一个局域网(Local Area Network,简称 LAN)。
局域网里的设备可以直接互相"对话"——数据只在本地这个小圈子里流动,所以速度特别快、延迟特别低(延迟就是从"A发出数据"到"B收到数据"之间的时间差)。本项目就是利用了这个特性——视频画面在局域网内直接传输,几乎感觉不到卡顿。局域网不需要互联网,即使你家断网了,局域网里的设备依然可以互相通信。
你在优酷或 B站 看视频时,视频文件存在网站的服务器上。你看的时候,服务器把数据一段一段地发给你,手机收到一段就播放一段——这就是流媒体。好处是不用等整个视频下载完才能看。
"拉流"就是从别人那里把视频流"拉"过来播放(你是接收方)。"推流"就是你把自己的视频"推"出去给别人看(你是发送方)。本 App 的角色是拉流——从别人(Desktop)那里拉视频过来播放。
| 维度 | 具体内容 |
|---|---|
| 编程语言 | Dart(Google 设计的一种语言,专门配合 Flutter 框架使用) |
| 使用的框架 | Flutter(Google 的跨平台开发框架——写一遍代码,安卓和 Windows 都能跑) |
| 界面规范 | Material Design 3(Google 发布的界面设计系统) |
| 播放核心 | media_kit(在 Flutter 和 libmpv 播放引擎之间搭桥的中间包) |
| 网络通信 | HTTP(网页浏览用的通信方式)、TCP(更底层的数据传输方式) |
| 本地存储 | SharedPreferences(一种简单的小数据存储方案,存用户设置) |
| 能跑在哪些设备上 | Android 手机、Windows 电脑(Web 平台代码已预留但待构建配置) |
本项目不是独立运行的——它需要另外两个程序配合,形成一条完整的"视频生产→分发→消费"链路。
局域网中每台设备都有一个IP 地址——就是这台设备在网络上的"门牌号"。比如 192.168.1.5 就代表某台设备。端口是一个数字编号(0到65535),标识这台设备上的具体网络程序。IP地址 + 端口 = 精确位置。比如 192.168.1.5:8080 就是"那台设备上的8080端口"。
运行在电脑上。采集画面(屏幕录制或摄像头),用数学算法压缩后发送到网络中。发送前会在 Service 上登记自己的存在。
一台常驻运行的服务程序。它维护一份"谁在播、播到哪"的清单。如果端口变化了它知道最新的,Mobile 不需要关心变化。
用户打开 App,输入 Service 地址 + 流名。App 自动从 Service 查出流的实际播放地址,然后开始拉数据、解压缩、显示到屏幕上。
从点击"播放"到画面出现,经历了5个步骤。
App 内部按"分层"组织——每层只解决一类问题,只和上下邻居交互。好处是:想换播放器?只改播放器层,其他层不动。
这些都是编程语言。不同语言适合干不同的事:Kotlin 是 Android 系统的最佳搭档;C++ 性能极高,适合视频解码这种重体力活;C++ 性能极高,适合视频解码这种重体力活。Flutter 通过平台通道与原生层通信,你不需要单独学它们。
一段1分钟、1080p清晰度的原始(未压缩)视频,大小大约是 10GB。如果你家网速是100Mbps,下载10GB要花约14分钟——而你只想看1分钟的视频。所以视频在传输前必须被压缩(用数学算法删掉人眼不太敏感的细节),10GB压到几十MB。即使压缩后还是很大,没法等全下载完再播——这就引出了"怎么传"的问题。不同的传法,就是不同的协议。
做法:把视频切成小片段(每个2-10秒,存为 .ts 文件),同时写一份"播放列表"(.m3u8),告诉播放器各片段的网址顺序。
过程:先下载列表 → 按序逐个下载片段 → 播完一个下下一个。
优点:所有浏览器和手机天生支持。
缺点:延迟2-5秒,因为要等片段完整生成才能下载。
✅ Web 和 Android 首选
做法:不切片。建立连接后,数据连续不断地推过来,收到一段立刻解码播放一段。
过程:连上 → 数据持续到达 → 边收边播,不等。
优点:延迟极低(不到1秒),不需等片段。
缺点:浏览器不原生支持 FLV 解码。
✅ Windows 桌面首选
做法:用专用端口1935,建立一条持续的双向通道,数据能同时往两个方向流动。
过程:连接 → 双方握手 → 沿通道持续推送。
优点:延迟极低,支持双向。
缺点:2020年后浏览器不再支持。
⚠️ 仅特殊场景
| 平台 | 默认推荐 | FLV/RTMP 行为 |
|---|---|---|
| Windows | HTTP-FLV | ✅ 正常播放 |
| Android | HLS | ⚠️ 弹出警告,但不阻止 |
| Web | HLS(仅此选项) | ❌ 直接拦截,提示切换 HLS |
编码(压缩):发送端拿到原始画面,用数学算法分析"哪些信息可以丢掉而不被人眼察觉"。比如天空区域相邻像素颜色差不多就只存一个代表色;连续两帧之间背景没动就只存变化的部分。10GB → 30MB。
解码(解压缩):接收端把压缩数据还原为可显示的像素。这不是简单的"反向运行"——需要大量数学运算来推算被删掉的细节。这个过程极复杂、极消耗算力,必须用专门的程序处理——这就是播放器引擎的核心工作。
在编程中,库就是别人写好的一堆功能,打包好给你直接用。你不需要从零写"怎么解压视频"——已经有人写好了 libmpv,你直接调用就行。libmpv 来自 mpv(一个著名的开源播放器软件),被提取出来做成独立的库,任何程序都可以调用它来获得视频播放能力。
负责 Dart ↔ C 之间的通信翻译。把 Flutter 的命令(打开、播放、暂停、停止)转发给 libmpv,把 libmpv 的输出传回 Flutter。
负责把 libmpv 解码出的像素数据画到屏幕上的指定区域。同时提供播放控件界面,并自动适配不同平台。
包含 libmpv 在各平台上的预编译文件。Android 用 .so 格式,Windows 用 .dll 格式(不同操作系统的库文件格式)。
HTTP:互联网上最基础的通信规则——你每次打开网页就在用它。一方发请求("给我某某数据"),另一方回响应("给,这是数据")。GET 是最常用的请求类型——纯查询,不修改任何东西。参数直接附在网址后面:用 ? 开始,& 分隔。
JSON:一种用纯文本表示数据的格式。花括号 {} 包住"对象"(名字:值的配对),方括号 [] 包住"列表"。几乎所有编程语言都能直接读懂,所以互联网上程序间传数据绝大多数都用它。
| 用户选了 | App 优先取哪个字段 | 如果为空,回退取哪个 |
|---|---|---|
| HLS | playHls | playUrls.hls |
| HTTP-FLV | playHttpFlv | (无回退,为空就报错) |
| RTMP | playUrls.rtmp | (无回退,为空就报错) |
| 服务模式 | 直接模式 | |
|---|---|---|
| 地址怎么来的 | 通过上面讲的 API 自动查 | 用户自己手打完整网址 |
| 用户需要填什么 | Service地址 + App名 + 流名 | 完整的播放网址 |
| 端口变了怎么办 | ✅ 自动跟上 | ❌ 得手动去改网址 |
UI(用户界面)就是你能看到和触碰到的所有界面元素——按钮、输入框、颜色、字体。UI 的好坏决定了你用 App 时觉得"舒服"还是"别扭"。设计系统是一套事先定好的规则——"按钮用这个色、卡片用这个圆角"。有了规则,程序员不用每做一个新页面都重新想一遍,直接按规则来。换整套规则就换整套外观。
Material Design 是 Google 发布的界面设计规范——"App 长相说明书"。2021年的第三版(Material 3)引入了设计令牌:给颜色/字体/形状起名字——"主色"叫 primary、"表面色"叫 surface。开发者只给这些名字赋值一次,整个 App 的所有组件就自动统一换色。比每个按钮单独设色高效得多。
| 用途 | 色值 | 出现位置 |
|---|---|---|
| 主色 | #17171C | 按钮、标题文字、重要图标 |
| 辅色 | #1863DC | 链接文字、播放中蓝色高亮、INFO 标签 |
| 强调色 | #FF7759 | LIVE 直播徽标、WARN 警告标签 |
| AppBar 背景 | #003C33 | 顶栏底色 |
| 输入框焦点 | #9B60AA | 点输入框时,边框变这个颜色 |
| 卡片圆角 | 22px | 所有卡片的统一圆角 |
| 按钮圆角 | 32px | 所有按钮的统一药丸形 |
内存(RAM):运行中的程序数据存在这里。极快(纳秒级读写),但断电就没了。App 一关,内存被回收,数据全清。硬盘/闪存(Disk/Flash):永久存储。写起来比内存慢,但关机关 App 都不丢。持久化就是把数据从"一关就丢"的内存搬到"关了还在"的硬盘上。
最简单的数据存储方式。每条数据由名字(键)和内容(值)组成。存:把键"gateway_url"对应的值存为"http://192.168.1.100:9000"。取:给我键"gateway_url"对应的值。极快、极简单,适合少量配置信息。数据量大应该用数据库。SharedPreferences 就是 Flutter 和 Android 内置的键值对存储。
自动保存:每次用户打字,Flutter 监听机制自动触发保存。启动恢复:App 启动时读出所有值,填回界面。存储7个数据:input_mode、gateway_url、app、stream、protocol、direct_url、theme_mode。
日志就是程序在运行过程中写的流水账——"14:32:05 用户点击了播放"、"14:32:07 连接失败,原因是网络不通"。核心作用是事后排查——出了问题回头看日志就知道"到底发生了什么、在哪一步出的事"。没有日志只能瞎猜。飞机上的"黑匣子"就是飞行日志——出事后调查员靠它还原事发经过。
日志面板位于 App 底部。每条日志含:时间戳、级别(INFO/WARN/ERROR)、消息内容。三个来源:用户操作、播放器状态变化、错误事件。管理:上限200条、自动滚动、一键复制、一键清空、实时级别统计。
点播(VOD):视频是一个已经录制好的完整文件,有明确的总时长(比如10分30秒),你可以拖进度条跳到第5分钟——因为第5分钟的内容已经存在了。直播(Live Streaming):画面正在实时产生。第5分钟的内容现在还不存在(还没发生),你当然没法跳过去。本项目是直播——进度条完全没用,只会误导用户。所以关了进度条、拖拽手势、双击快进。
| 平台 | 按钮 | 为什么 |
|---|---|---|
| Windows 桌面 | 播放/暂停 + 音量 + 全屏 | 屏幕大,鼠标精确,多放几个不拥挤 |
| Android 手机 | 仅全屏按钮 | 屏幕小,手指粗,多了容易误触 |
项目覆盖了7种可能出错的场景——每种都在出问题之前检查,或出问题之后捕获,给用户清晰的提示。
| 什么错了 | 怎么发现的 | 告诉用户什么 |
|---|---|---|
| 填的信息不完整 | 发请求前检查 | "请填写服务地址、App、Stream" |
| 网址为空 | 播放前检查 | "请输入播放地址" |
| Service 返回错误(如404) | 看 HTTP 状态码 | "gateway 响应 404: ..." |
| Service 回了乱码 | 解析时验证格式 | "响应格式无效" |
| 想要的协议没有地址 | 检查字段是否空 | "响应中缺少 playHttpFlv" |
| Web 环境用了 FLV/RTMP | 检测网址特征 | "Web环境不支持,请切换 HLS" |
| 播放引擎启动失败 | try-catch 抓异常 | "播放失败: 具体原因" |
一部 Android 手机和一台 Windows 电脑,底层是完全不同的操作系统——管理硬件的最底层软件。不同操作系统有不同的"规矩":Android 用 Linux 内核+Kotlin,Windows 用 NT 内核+C++,浏览器用 JavaScript 引擎。传统做法是一个 App 写三遍,Flutter 的做法是写一遍 Dart,编译工具分别翻译成各平台的原生机器指令——这就是跨平台的核心价值。
| 平台 | 支持度 | 怎么编译的 | 限制 |
|---|---|---|---|
| Windows | ✅ | Dart→原生x64 + C++用CMake | 无 |
| Android | ✅ | Dart→ARM64 + Gradle打包 | 需允许明文HTTP |
| Web | ⚠️ 待配置 | 代码已预留(kIsWeb 分支),尚未 flutter create web | 仅支持 HLS |
| iOS | ❌ | — | 未配置iOS编译 |
android:usesCleartextTraffic="true"——仅本 App 豁免,不影响其他 App。程序员写的代码(Dart、C++、Kotlin)是人类可读的文本。但手机芯片只懂机器码(0和1组成的硬件指令)。构建就是通过编译器把人类可读代码翻译成机器码。Flutter 的构建分两步:先编译 Dart → 各平台原生机器指令,再调用各平台打包工具压成安装包。
APK:传统格式,包含所有资源,能直接传到手机上安装(侧载),不需要商店。坏处是包很大——包含了用不上的资源。AAB:2018年 Google 推出,是一个"零件清单"——从 Play 商店下载时,Google 根据你的具体手机型号动态组装最优包。AAB 不能直接安装,必须通过商店。
哈希算法:一种数学函数——输入任意数据,输出固定长度的"指纹"。同一文件指纹始终相同;文件哪怕只改一个字节,指纹就完全不同。SHA256 是一种广泛使用的哈希算法,输出64个十六进制字符。发布软件时附带 SHA256 指纹,下载者可验证文件是否完好无损。
Android 要求安装包必须经过数字签名——用开发者的私钥加密标记,证明"这个包确实是我做的,中途没被掉包"。有正式密钥则正式签名(能上架商店);没有则用 Debug 签名(仅内部测试,不能上架)。
Android 设计了权限机制:每个 App 安装前必须声明自己要用哪些敏感功能(联网、摄像头、定位……)。用户在安装时就能看到并判断是否合理。INTERNET 权限是最基础的——允许 App 发起网络连接。在配置文件中写 <uses-permission android:name="android.permission.INTERNET"/>。
路由器给每台设备分一个私有 IP(如 192.168.x.x),只在局域网内有效——出了路由器没人认识。路由器对外只暴露一个公网 IP。这个机制叫 NAT,天然形成安全隔离——外部攻击者无法主动连接你局域网里的设备,因为不知道地址。本项目的所有视频数据都在局域网内传输,外部接触不到。
不要把流媒体端口直接暴露到公网——全世界都能扫到。安全做法是用 VPN(虚拟专用网络)——在公网上建立一条加密隧道,让远程设备"虚拟地"接入你的局域网,获得局域网内的虚拟 IP,就可以像在本地一样访问所有内网资源。