ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零搭建家庭影音中心:Jellyfin+Flutter打造电视端私人媒体库

从零搭建家庭影音中心:Jellyfin+Flutter打造电视端私人媒体库 1. 项目概述LunaTV 到底解决什么问题先说一下 LunaTV 这个项目的来历。我家里有一台吃灰多年的旧笔记本外挂了一块 4TB 硬盘硬盘里塞着这几年拍的孩子的视频、旅行素材、老电影还有从旧手机和相机里导出的几千张照片。问题很典型这些东西电脑上能看手机上能翻但一到客厅那台电视上就彻底没法看。要么插 U 盘一个个文件打开要么用手机投屏投两分钟就断要么干脆因为电视系统太老、解码格式不对直接黑屏。LunaTV 就是冲着这个痛点去的。我把它定义成一个自己家里的私人影音数据中心 电视端极简播放器一台常开的小主机负责把散落在硬盘里的视频文件整理成带海报、带简介、带字幕轨的媒体库一台挂在客厅电视上的 Android TV 盒子跑一个自己写的客户端用遥控器上下左右就能浏览、点播、续播。项目折腾了大概两个月踩了不少坑今天把完整的思路、选型、部署过程和问题排查都整理出来供想在家里搭类似系统的人参考。这个项目适合谁简单说适合手里有 NAS 或旧电脑、电视是 Android TV/盒子、家里有大量本地视频文件、并且不想每个月买会员或者不想被各家 App 的播放限制绑死的人。如果你是纯小白完全没接触过 Docker 和 Linux也别怕后面每一步都会讲到。LunaTV 不是什么大型平台它的核心目标只有一个让家里所有人都能在电视上像用视频 App 一样看硬盘里的东西。1.1 起点家里的电视、NAS 和一堆落灰的硬盘很多人家里其实已经有条件做这件事只是没有一套顺手的管理工具。我的原始设备是这样的一台 2016 年的联想小新笔记本i5-6200U 处理器、8GB 内存硬盘换过一块 512GB 的 SSD外接一个绿联的硬盘底座里面放一块 4TB 东芝企业盘电视是客厅的 Sony 65 寸系统是 Android TV另外接了一个小米盒子作为备用。这套配置放在今天几乎不值钱但用来跑媒体服务完全够。最初我试过几种方案。一种是直接在电视上装小白播放器、MX Player然后通过 SMB 共享打开笔记本里的文件夹。这种方式的缺点是没有目录索引几千个视频文件靠文件夹一层层翻找一部片子能把遥控器按出火星子没有海报和介绍孩子认不出哪部是哪部看了一半退出下次还要从头拖进度条。另一种是买了个某品牌的 NAS自带视频管理但系统内部 UI 太臃肿旧电视装它的客户端经常闪退。折腾到最后我意识到问题不在于设备而在于缺少一个服务端负责整理、电视端负责体验的完整链路。LunaTV 名字的由来也很随意当时晚上十点多在客厅布线电视屏幕刚好自动切到了月球壁纸我随手在项目文件夹里敲了 LunaTV。没想到这个名字一直用到了现在。中文名我有时候叫它月下电视听起来还挺顺耳。1.2 定位与边界一个够用的家庭媒体中心做这个项目之前我给自己定了几条边界避免越做越失控。第一不做下载器不碰任何来源审查相关的事情只负责管理自己手里已有的合法文件。第二不做多用户复杂的权限系统家里一共就三个人统一能看就行。第三不追求外观炫酷电视端界面以大封面、大字体、易聚焦为准毕竟遥控器操作和鼠标点击完全不是一回事。第四优先保证局域网内体验公网访问和后期的远程协作排在很后面的优先级。这些边界非常重要。很多类似项目最后烂尾就是因为想把所有功能都塞进去今天想加个漫画阅读器明天想加个音乐播放后天想加个下载任务管理结果半年过去核心的视频播放还是一团糟。LunaTV 始终锁定电视上看视频这一件事其余功能宁可不做。最后的技术选型也因为这个定位变得非常清晰服务端用 Jellyfin 做媒体库管理和流媒体服务电视端自己用 Flutter 写一个轻量客户端通过 Jellyfin 的 HTTP API 拉取媒体列表、播放地址和进度信息。这样我不用从零去写视频扫描、封面刮削、字幕解析那一堆麻烦事电视端又可以完全按自己的想法做交互。1.3 技术选型一句话总结如果你只想记住这段架构一句话就够了Jellyfin 负责把所有视频文件变成有海报墙、有简介、有统一播放接口的媒体库LunaTV 客户端只干一件事把 Jellyfin 的数据用一种适合遥控器操作的方式呈现在电视上。存储层靠 SMB/NFS 或者直接把硬盘挂载给 Jellyfin网络层走局域网 HTTP播放层直接用 Jellyfin 已经把转码、直通、字幕都处理好的流媒体接口。这个分工把复杂度压到了最低是我做过最舒服的一个私人项目。2. 整体架构与关键技术拆解2.1 三层架构存储层、服务端、电视端LunaTV 在物理上分三层。最底下是存储层就是那块 4TB 硬盘文件系统用 ext4目录结构做了严格规定。中间是服务端运行在一台旧笔记本的 Docker 环境里Jellyfin 容器负责扫描硬盘目录、生成元数据、生成缩略图、响应播放请求。最上面是电视端一个小体积的 Android APK跑在小米盒子上开机自启动进入桌面后就是一个全屏的媒体库界面。这三层我在一开始就用不同的颜色贴纸在物理设备上做了标记硬盘贴绿色笔记本贴黄色盒子贴红色。听着有点幼稚但排查问题的时候非常有用。电视端一卡我基本能迅速判断是红色设备的问题还是黄色设备的问题不会一头扎进日志里。存储层的目录规范我后面会专门讲。这里只强调一个原则所有视频文件必须先按规范整理好再让服务端扫描。否则 Jellyfin 会把原本是一部电影的两段视频拆成两条会把一季电视剧识别成二十个独立电影整理起来比手动翻文件夹还痛苦。2.2 为什么站在 Jellyfin 肩膀上而不是从零造后端一开始我其实动了从零写后端的念头用 Python 扫描目录、读到文件名后用 TMDB 的 API 拉元数据、用 Flask 写一个返回 JSON 的接口、再加上一个简单的播放页。写到一半我发现这个轮子已经有人造得很完美了而且我造的那个轮子大概率是三角形的。Jellyfin 本身是一个开源媒体服务器支持扫描电影、电视剧、音乐、照片能自动刮削海报、简介、演员表、评分支持外挂字幕、多音轨、转码、DLNA 投屏还提供一套非常完整的 REST API。最关键的是它对硬件转码的支持很好Intel 核显的 Quick Sync、AMD 的 AMF、NVIDIA 的 NVENC 都能用。我用那台 i5-6200U 的旧笔记本测试硬解 1080p H.264 几乎没有压力4K 遇到不支持的编码才会走转码。这套 API 是 LunaTV 电视客户端的基础。我只需要让电视端登录后调用几个接口获取首页视图、获取某个分类下的条目、获取某个条目的详情和播放信息、上报播放进度。剩下那些视频扫描、封面生成、转码切片全部交给 Jellyfin。自研服务端听起来很酷但真正维护起来会占用掉大量周末时间对一个家庭媒体库项目来说完全不划算。2.3 电视端用 Flutter 的理由电视端最开始我试过原生 Android 开发用 Kotlin 配合 Leanback 库。Leanback 是谷歌官方的 TV 组件库功能很全但问题是它默认的界面风格太Google TV想改成适合家里老人和小孩的大卡片风格需要覆写大量样式开发效率很低。后来我试了 Flutter发现它对自定义 UI 更友好而且自带焦点管理机制遥控器的上下左右和确认键可以统一通过Focus系统来处理。Flutter 在电视端的坑也很明显比如视频播放插件不统一、不同盒子上的解码能力差异大。我的解决方案是LunaTV 客户端本身不直接解码视频而是调用 Jellyfin 提供的播放地址再交给系统播放器或者 exo_player 插件去渲染。客户端只负责把媒体列表、详情、进度条这些 UI 画好。这样即使遇到某台盒子解码能力弱也可以让 Jellyfin 那边转码成兼容格式再播客户端的代码完全不用动。最终 APK 的体积控制在了 35MB 左右只包含 ARM64 架构的 so 文件。这对小米盒子、当贝盒子这一类 Android TV 设备来说很友好安装速度快运行内存占用也不高。电视端应用最忌讳的就是拿手机应用的思路直接搬过去界面元素太小、不支持遥控器焦点、启动页加载半天这三点 LunaTV 在开发过程中都专门做了优化。2.4 带宽与硬件参数估算很多人纠结旧电脑能不能带动 4K其实问题要先拆成网络带宽和解码能力两件事。局域网里千兆有线网络的理论带宽大约是 1000 Mbps也就是 125 MB/s。一部 4K H.265 电影码率通常在 40 到 80 Mbps 之间换算下来是 5 到 10 MB/s。所以哪怕是普通的千兆局域网传输 4K 原盘也完全够瓶颈基本不在网络。解码能力才是关键。电视端盒子如果本身支持播放 4K H.265Jellyfin 会直接走直通模式服务端只负责读文件、推流不做转码。这种情况下旧笔记本的 CPU 占用很低几乎只看硬盘读取速度。但如果你在电视端遇到视频编码不兼容比如老电视不支持 HEVCJellyfin 就必须做实时转码。1080p H.264 转码大约需要 i3-6100 级别的 CPU 就能流畅处理4K H.265 转 1080p 就需要有核显 Quick Sync 辅助否则 CPU 会占满还掉帧。我实测下来的数据供参考i5-6200U 开启 Intel Quick Sync 之后可以同时处理 3 路 1080p H.264 转码或者 1 路 4K H.265 转 1080p H.264。对家庭两三台设备同时观看的场景完全够了。如果你用的是更老的平台建议打开 Jellyfin 后台的硬件加速选项之前先确认核显驱动装好了否则系统会自动回退到纯 CPU 软解那才是真正的灾难。3. 从零搭建 LunaTV 的实操记录3.1 服务端部署Docker Compose 与目录规划我在那台旧笔记本上装的是 Ubuntu Server 22.04 LTS没有桌面环境纯粹当一个家庭服务器用。装好系统后第一件事是安装 Docker 和 Docker Compose 插件然后写了一个docker-compose.yml内容大概是这样的services: jellyfin: image: jellyfin/jellyfin:latest container_name: luna-jellyfin restart: unless-stopped ports: - 8096:8096 - 8920:8920 volumes: - /opt/luna/config:/config - /opt/luna/cache:/cache - /mnt/media:/media devices: - /dev/dri:/dev/dri environment: - TZAsia/Shanghai - JELLYFIN_PublishedServerUrlhttp://192.168.1.20:8096这里有几个关键点。第一/mnt/media是我那块 4TB 硬盘的挂载点Jellyfin 容器通过这个路径扫描文件。第二/dev/dri是 Intel 核显设备映射进去之后 Jellyfin 才能用 Quick Sync 硬解如果你的机器没有核显或者驱动没装好这一行可以删掉否则容器会启动失败。第三JELLYFIN_PublishedServerUrl这个环境变量很关键我一开始没设导致电视端有时拿到的是容器内部的地址http://172.17.0.3:8096电视当然连不上。指定成局域网 IP 之后这个问题立刻消失。写好后执行docker compose up -d等一两分钟浏览器打开http://笔记本IP:8096完成管理员账号初始化服务端就活了。这里我强烈建议把笔记本的 IP 在路由器里设成静态地址或者给设备一个 DHCP 保留地址否则哪一天它变成了另一个 IP电视端、手机端全部都会失联。3.2 媒体库规范化命名、元数据与封面这是整个项目里最需要耐心、最不值得偷懒的一步。Jellyfin 的刮削能力再强它也只能根据文件名去识别内容。我之前硬盘里的文件命名五花八门比如2019-1-1 北京旅游.MP4、爸爸生日快乐.mp4、新电影(1).mkv这种文件放进去基本等于扔给 Jellyfin 一个盲盒。我后来整理的规则很简单分三类电影电影名 (年份).扩展名比如The Matrix (1999).mkv电视剧剧名 SxxExx.扩展名比如Breaking Bad S01E01.mkv家庭视频和照片单独建一个目录不参与海报墙刮削只看文件列表和时间目录结构如下/mnt/media/ ├── movies/ │ └── The Matrix (1999).mkv ├── tvshows/ │ └── Breaking Bad/ │ └── Season 01/ │ └── Breaking Bad S01E01.mkv └── family/ ├── 2024-10-01_厦门旅行.mp4 └── 2024-12-25_圣诞晚餐.mov整理完文件后回到 Jellyfin 控制台添加媒体库电影目录选 movies电视剧目录选 tvshows家庭视频目录可以单独建一个类型为混合视频的媒体库。扫描模式选实时监控元数据语言我直接设成zh-CN这样新加入的文件会自动抓取中文海报和简介。这里有个小经验先添加一个只有一个电影的测试目录确认刮削正常后再扫描全量不然一上来就是几千个文件出错了很难定位。我第一次就是全量扫描后一半文件没匹配上后面只能靠文件名手动改那晚的心情一言难尽。3.3 电视端接入APK 编译与局域网连通电视端 LunaTV 的 Flutter 工程编译成 APK 后我通过 U 盘拷贝到小米盒子上安装。Android TV 默认不允许安装未知来源应用需要在设置里允许这个每个人电视系统不一样就不赘述了。打开 LunaTV第一屏是服务器设置页。用户输入 Jellyfin 的局域网 IP、端口、用户名和密码客户端会把这份配置保存在本地。保存后客户端调用 Jellyfin 的接口进行登录和拉取媒体库列表。为了验证是不是 TV 端的问题我通常在电脑上先跑几条命令curl http://192.168.1.20:8096/health curl -X POST http://192.168.1.20:8096/Users/AuthenticateByName \ -H Content-Type: application/json \ -d {Username:luna,Pw:你的密码}第一条命令返回Healthy说明服务端在线第二条返回一个 AccessToken说明账号认证链路没问题。如果这两条在电脑上能通但电视上不行那问题大概率出在网络隔离或者 Android 应用的网络权限上。小米盒子有些会在内置安全拦截里杀掉后台网络请求需要在权限设置里允许 LunaTV 访问本地网络。电视端一旦连上服务端首页就会显示几个大的分类卡片继续观看、电影、电视剧、家庭视频、最近添加。每个分类横向滚动遥控器左右键切换确认键进入详情页详情页可以直接播放或者从上一次进度继续播。交互逻辑我参考的是主流电视视频 App 的布局长辈不需要学习成本上手就会。3.4 家庭网络与存储优化网络部分我没有折腾 Mesh 和万兆家里目前还是光猫加一台普通的千兆 AC 路由器。实测在 5GHz Wi-Fi 下小米盒子连接 Jellyfin 播放 1080p 完全流畅播放 4K 原盘偶尔会有缓冲于是我用一根六类网线直接把盒子接到了路由器上问题消失。如果你家电视和盒子都支持有线尽量用有线Wi-Fi 在长时间高码率播放下偶尔的不稳定真的非常烦人。存储方面那块 4TB 企业盘我保持 7200 转常转没有做休眠策略。媒体服务这东西每次唤醒硬盘要等两三秒虽然看起来小事但放在电视这种即时交互场景里体验很减分。我把笔记本的 BIOS 也设置了来电自启动这样家里万一停电再来电Jellyfin 能自己起来不用再搬个小板凳去按电源键。整个 LunaTV 的日常维护量基本降到了每个月看一次日志、检查一下硬盘温度的水平。4. 核心实现细节那些文档里不会写的坑4.1 遥控器交互电视端 UI 不能按手机思路做这是 LunaTV 项目里我学到最深的一课。手机端的应用可以点击任意位置而电视端唯一可靠的操作方式是遥控器的方向键和确认键。Flutter 默认的焦点系统在 ListView 嵌套、GridView 混排的时候会出现焦点丢失、焦点漂移的问题。我的解决办法是给每一个可以聚焦的卡片指定FocusNode手动监听onKeyEvent并维护一个当前选中索引的全局状态。焦点移动后立刻触发对应区域的滚动操作保证永远有卡片处于高亮状态。另外一个经验是电视端的字号。我家电视是 65 寸看起来很大但坐在三米外的沙发上原本在电脑上觉得很清晰的 14 号字根本看不清。LunaTV 里最小标题字号我设定为 28正文字号 24海报卡片之间的间距也加大到了 16。界面元素宁大勿小这是电视应用和手机应用最本质的区别。如果你也想做 TV 客户端建议一开始就用一个沙发距离的真实场景来测试而不是坐在电脑前盯着模拟器看。4.2 元数据刮削的语言与图片问题Jellyfin 默认的元数据语言是英语我不小心在初始配置时选错了导致很多国产电影的海报和简介都是英文甚至干脆不匹配。后来在控制台的媒体库里把元数据语言改成中文并勾选覆盖已有元数据重新扫描后才恢复。虽然扫描过程确实慢但它会自动把原来错误的英文标题覆盖掉总比手动改几百个文件强。图片加载失败也是一个高频问题。Jellyfin 的图片代理需要访问 TMDB 等元数据服务如果你所在网络访问这些站点不稳定海报墙就会变成一排灰色的占位符非常难看。我的做法是第一次扫描时找一个网络比较顺的时间段把元数据都抓取好并缓存到本地平时用的时候就完全走本地缓存不再请求外部站点。如果你连首次抓取都不顺利可以先手动下载好 nfo 和图片文件放到对应电影目录下Jellyfin 会优先采用本地文件。4.3 播放链路的直通与转码取舍播放环节的核心问题是到底让谁干活。如果电视端盒子支持的编码格式和服务端的视频文件完全一致Jellyfin 就走直通服务端只承担文件读取和推流CPU 占用低、画质无损。如果格式不支持服务端就必须转码。我一开始让 LunaTV 客户端一律请求 arz 路径结果有些视频播放会卡顿后来发现是码率太高而电视盒子的 Wi-Fi 太差。于是我在客户端设置里加了一个选项默认用直通优先但用户可以在播放界面手动切换为兼容模式。兼容模式下客户端请求 Jellyfin 的转码接口并把目标码率限制在 20 Mbps。这个设计很笨但非常实用。有一次家里老人说电影转圈不出来我远程切到兼容模式问题立刻解决。对非技术用户来说他们不需要理解什么是转码只需要知道切换到这个模式后能看就够了。5. 常见问题与排查技巧实录5.1 电视端一打开就转圈这个现象大多数发生在 Jellyfin 服务器刚重启或者电视端长时间待机之后。排查思路很简单先在电脑上ping一下服务器的 IP如果 ping 不通说明服务端没起来如果 ping 得通就在浏览器里打开http://IP:8096确认 Web 管理界面能正常登录。如果浏览器也打开慢多半是 Jellyfin 正在做后台任务比如扫描媒体库或者生成缩略图等几分钟再试。我在 LunaTV 客户端里加了一个服务器连接测试按钮点击后会先检查网络连通性再试着调用一次/System/Info接口。这样问题发生在网络层还是服务端层屏幕上会直接给出一句人类能看懂的话而不是一串红色错误码。这个功能我强烈建议所有做 TV 客户端的人加上能省掉大量的家庭售后咨询。5.2 海报墙有标题没封面如果媒体库里的条目能被识别说明文件名和刮削匹配没问题纯粹是图片没加载下来。先检查 Jellyfin 的日志里有没有联网失败的记录然后到控制台的元数据里手动刷新单条记录。如果手动刷新能更新封面说明问题只是偶发网络抖动如果不行就要考虑抓取源的问题。我的实测经验是多刷新几次一般能成功因为元数据服务偶尔会拒绝来自某些数据中心的请求并不是 Jellyfin 本身的问题。实在不行就下载一张本地图片命名成poster.jpg放在电影目录里Jellyfin 会优先使用本地图片。5.3 播放 4K 卡成 PPT这种情况基本都是转发/解码链路中的某一个环节掉链子导致的。我按出现频率给原因排个序电视盒子无线网络不稳定、服务端硬解没开启、视频文件码率过高、字幕插件冲突。如果电视端用的是百兆网口插了网线也可能只有 100 Mbps播放 80 Mbps 码率的原盘再加上转码数据会直接卡死。解决办法是把转码码率限制在 40 Mbps 以下或者放弃 4K 原盘直通改看 1080p 版本。家里用电视看视频流畅永远比原盘画质重要这是我折腾坏了第二块移动硬盘后的结论。5.4 音画不同步与字幕乱码音画不同步大概率出在音频直通上。电视盒子把 TrueHD 或 DTS 音频直通给功放时如果功放对格式支持不完整就会出现延迟。我的做法是把 LunaTV 的默认音频模式设置为兼容强制 Jellyfin 在必要的时候把音频转码成 AC3 或 AAC。虽然少了些无损体验但至少声音和画面是同步的。字幕乱码最常见的原因是字幕文件编码不是 UTF-8。老电影的字幕大多是 GBK 编码电视端解析经常乱码。解决方法是下载字幕时优先选 UTF-8 编码的或者在 Jellyfin 后台设置默认字幕编码。另一个更省事的做法是让 Jellyfin 在转码时把字幕烧录进画面这样电视端就完全不用处理字幕文件了缺点是字幕不能关闭。我在 LunaTV 里把它做成了一个开关默认关闭遇到乱码时手动打开烧录字幕实测非常管用。为了方便排查我把常见问题汇总成一个表贴在了笔记本旁边的墙上现象可能原因快速处置办法电视端转圈服务器没起来或网络不通电脑浏览器打开 8096 端口测试有标题没封面元数据图片抓取失败控制台手动刷新元数据4K 播放卡顿网络带宽或硬解未开启切兼容模式限制码率音画不同步音频直通不兼容强制音频转码为 AC3/AAC字幕乱码字幕文件不是 UTF-8开启字幕烧录功能续播进度丢失客户端上报进度异常检查 Jellyfin 播放进度时间戳6. 最后说几句个人体会LunaTV 做下来我最深的感触是技术难点从来不在某一个环节而是整个链路里到处都藏着细节。硬盘命名不规范后面刮削就全是问题局域网 IP 不固定客户端就会失联硬件加速没配置好播放就会卡顿。每一个点单独拿出来都不难但串在一起就需要有人能站在全局视角去处理。这也是为什么我建议你在动手前先像我一样把架构图画清楚存储层在哪、服务端在哪、电视端在哪、各自负责什么边界分明之后再逐个击破。如果你也想复刻一个类似的东西我给你的第一条建议不是先去买设备、装软件而是先把硬盘里的文件命名整理好。命名规范了整个项目就成功了一半。另一半就是尽量用有线网络把电视和服务器之间的路修稳。硬件差一点没关系Jellyfin 的转码能力足够帮你兜底但物理链路如果不稳定什么软件优化都补不回来。LunaTV 这个项目到现在已经稳定跑了半年多除了偶尔家里老人说怎么没有新电影需要我往硬盘里丢点新资源之外基本做到了零维护。这种放在自己家里、完全可控、给家人用的系统带来的成就感确实比写一堆没人用的接口强太多了。
返回列表