需求本身很简单:孩子想看动画片,但不应该被推荐流、开屏广告和自动连播接管。 市面上有儿童模式,但大多数产品的目标仍然是停留时长。于是我把这件事拆成两部分: 一个自己能控制的视频库,和一个只做播放的客户端。

先划掉不做的

做家庭产品时,决定不做什么比决定做什么更重要。这个项目一开始就划掉了这些:

  • 不做小程序:平台审核、内容限制和播放能力都不可控,而且孩子使用的设备是平板。
  • 不做广告和推荐:没有信息流,没有「猜你喜欢」,没有自动播放下一个。
  • 不做社交:没有评论、点赞、分享,也没有任何上传入口。
  • 不做 iOS:家里只有 Android 平板,不为不存在的设备维护第二套壳。
  • 不追求内容多:宁可只有几十个经过筛选的视频,也不把平台上的「儿童」标签直接搬过来。

另外两条是硬规则:必须登录才能看,且所有内容先审核后上线。 前者是为了避免链接被转发后变成公开站点,后者是整条流水线存在的理由。

内容从哪来

内容来源是小红书、B站和央视。采集本身不难,难的是承认一件事: 平台上的「儿童」「早教」「动画」标签,不等于适合三岁孩子。 节奏、配乐、语言复杂度、价值引导,都需要单独判断。

所以入库不是采集完就结束,而是一条流水线:

采集 → 下载 / 转写(ASR) → AI 初审 → 人工抽检
     → 分类与分层 → 封面处理 → 入库 → 对象存储 → 上线

转写是为了让审核和分类有文本依据。AI 初审负责粗筛,人工抽检负责兜底。 我没有让模型直接决定「能不能上线」,因为这类判断一旦错了,代价不是一条坏推荐, 而是孩子看到不该看的内容。

分类不是「动画片」

最初我也用「动画 / 儿歌 / 科普」这种分类,很快就发现它没有用: 同样是动画片,有的适合睡前,有的会让孩子过度兴奋;有的语言密度高,有的几乎没有对话。

后来改成按三岁孩子的发展维度来组织,并为每个视频建立正向与反向清单。 正向的是希望反复出现的内容特征,反向的是需要排除的特征。 在此基础上做思维评分和配比,控制不同类别在一天里的比例。

内容还分成 A、B、C 三层,播放时用「1A + 1B」的组合队列: 先看一个主线内容,再配一个节奏更平缓的。这样既不会无限连播, 也不会在孩子正投入时突然切断。

每个内容还会带一条线下延伸建议。屏幕时间结束后,大人知道可以接着做什么, 而不是只能说「不能看了」。

客户端为什么是 uni-app + Capacitor

前端用 uni-app 的 H5 形态,Android 端用 Capacitor 套壳。选择它的原因不是跨平台, 而是迭代速度:页面改动可以直接部署,不需要每次发版。

但纯 WebView 播放并不总是可靠:全屏、硬解码、后台切换、不同厂商的浏览器内核, 都会在某个设备上出问题。所以最终加了一个原生播放页, 由原生层负责播放器,H5 只负责列表、详情和导航。

APK 的更新链路也做得很朴素:一个固定的下载地址始终指向最新包, 加一份 latest.json 版本清单给客户端读取。 没有应用商店,没有灰度发布,够用就行。

播放链路比想象中脆

平台视频的直链会过期,这是整个项目里最容易被低估的问题。今天能播的地址, 明天可能就 403。围绕这一点,最后做了几层处理:

  • 封面本地化:不直接引用平台图片,下载、压缩后保存到自己的地址。
  • 媒体代理:播放请求经过后端,统一处理 referer、过期和重定向。
  • 对象存储:把可长期保存的媒体上传到七牛,用队列和状态机管理上传进度。
  • 地址回写:数据库记录对象存储地址与原始地址,播放时按优先级回退。
  • 定时刷新:直链失效后重新解析,更新记录,而不是让用户看到一个坏掉的卡片。

这些工作都不「酷」,但它们决定了孩子点开一个视频时,看到的是内容还是转圈。

屏幕时间与「下一个」

儿童视频产品最容易失控的地方,是「下一个」按钮。推荐流把它做得太顺滑, 孩子不需要理解,只需要不停地点。

这个客户端做了两件事:限制单次和每日观看时长;用组合队列代替无限连播。 看完队列就结束,页面会提示今天的观看时间,而不是再推一个更刺激的视频。

一个人使用的产品,验收标准不是 DAU,而是孩子看完之后有没有情绪崩溃。

后端为什么这么轻

后端是 Hono 加内置的 node:sqlite,没有独立的数据库服务。 清单接口、媒体代理、采集与审核脚本都在一个仓库里。对单机部署来说, 这比拆成微服务省下的不是服务器成本,而是「周末还能不能快速改一个接口」的成本。

数据源是一份 JSON 清单加 SQLite。JSON 方便人工检查和版本对比,SQLite 负责查询和状态。 两者之间的导入脚本是显式的,不做隐式的双向同步。

一个私有产品的维护成本

这个项目没有用户增长,也没有社区。它只有一个用户,而且这个用户不会提 issue, 他只会哭。这反而让维护标准变得更直接:内容少一点没关系,审核不能放宽; 功能少一点没关系,播放不能坏。

真正的成本不在写代码,而在持续运营:采集脚本会随平台改版失效, 内容需要不断补充,审核需要人工抽检,直链需要定期刷新。 所以整个系统尽量把重复劳动脚本化,把需要判断的部分留给人。

技术栈并不复杂,难的是边界。想清楚哪些内容能进来、以什么节奏播放、 什么时候必须停下来,剩下的才是工程问题。