用 Claude Code 追查电视广告,再给孩子放助眠音乐

上周我写了怎么用 ADB 把 Claude Code 连上我的索尼电视。那篇讲的是准备工作:找到电视,打开调试,改好地区,装上一个应用。如果你还没做这一步,先从那篇开始。

这篇讲的是连接建好以后,我拿它做了什么。不到一天里我用了两次。昨晚是去对付一个我本不该看到的广告,今天早上是给我女儿放音乐。其中一件已经搞定了,另一件还在进行中。我觉得没做完的那件反而更值得一读。

付了钱的应用里还有广告

我订阅了一个体育流媒体应用。可每次打开回放,它还是先放一段贴片广告,角落里有个小标签写着 Ad 1 of 1。花了钱还要看广告,这件事让我烦的程度连我自己都有点意外。于是我让 Claude 把广告去掉。

最直接的想法是改应用。动手之前,Claude 先确认这条路到底走不走得通。它通过 ADB 从电视里把 APK 拉出来,在 dex 文件里搜常见的视频广告技术留下的痕迹:Google IMA、FreeWheel、Yospace、动态广告插入、VAST 和 VMAP。

adb shell pm path <package>
adb pull <path-from-above> app.apk
unzip -o app.apk -d app/

答案是走不通。这个应用原来只是一个 Cordova 外壳。原生部分只是薄薄一层包装,播放器和广告都是应用启动时从服务方的服务器上以 JavaScript 的形式下载下来的。你就算把这个 APK 改上一整个星期,广告照样会出来,因为它们根本不在 APK 里。

这个答案我很满意。几分钟就弄清楚了,省掉了一个晚上在一件注定没用的事情上。

在 DNS 这一层拦广告

改不了应用,就改电视能连到哪里。Android TV 有一个“私人 DNS”设置,和 Android 手机上的一样。把它指向一个带过滤的 DNS 服务器,电视上所有应用查地址时都会去问这台服务器。应用要找已知的广告服务器时,过滤器什么都不返回。

可以在电视的网络设置里改,也可以用 ADB:

adb shell settings put global private_dns_mode hostname
adb shell settings put global private_dns_specifier dns.adguard-dns.com

这个主机名是 AdGuard 的公共过滤器。Claude 设好之后,确认一个已知的广告域名已经解析不到任何地方,然后打开一段回放。贴片广告照放不误。

所以下一步是个人版的 AdGuard DNS 服务器。免费账户就能拿到一台自己的服务器,给电视建一个设备条目,就有了这台设备专属的私人 DNS 主机名,还有查询日志和自定义规则。把电视切过去只要一行:

adb shell settings put global private_dns_specifier <your-id>.d.adguard-dns.com

折腾这一步,图的就是查询日志。公共过滤器是个黑箱。自己的服务器会把电视发出的每一次查询按顺序、实时地摆在你面前。

只影响电视

我问的第一个问题是,这会不会影响我的笔记本或者家里的 Wi-Fi。不会。私人 DNS 是单台设备上的设置。电视通过加密连接把查询发到那个主机名,主机名里的 ID 告诉 AdGuard 查询来自哪台设备。我的 Mac 还是用它原来的 DNS,路由器也是,家里其他设备也都一样。

所以撤销也很简单。下面这行会让电视回到网络默认的 DNS:

adb shell settings put global private_dns_mode opportunistic

两次没广告不算证据

切换之后我看了两段回放,两次都没有广告。这时候宣布胜利很容易。

Claude 没有这么做,它是对的。我的新服务器用的屏蔽列表和之前失败的公共过滤器一模一样,拦截这件事本身什么都没变。广告没出现,更可能的原因是频次上限:同一个人看过几次之后,广告服务器会暂时不再给他放。折腾了一晚上之后连着两次安静的回放,看起来正是这个样子。

真正的测试更慢,也更枯燥:

  1. 打开电视的查询日志。
  2. 播放一段新的回放,一直放到广告真的出现。
  3. 看广告出现前那几秒的查询,找到投放广告的那个主机。
  4. 为它加一条自定义规则,比如 ||ads.example.com^。
  5. 再放一段回放,确认两件事:没有广告,而且比赛本身还能正常播放。

最后这一步很重要。拦错了主机,视频可能会跟着广告一起挂掉。

还有一个该停手的地方。有些服务会在自己的服务器上把广告直接缝进和正片同一条视频流里,这叫服务端广告插入。如果广告和比赛来自同一个主机,DNS 就没有东西可以分开。拦掉那个主机,两样一起没了。

这个应用属于哪一种,我还不知道,下一次回放就能看出来。我现在手里有的,是一套方法,和一个能亲眼看着、不用靠猜的过滤器。

把电视当成哄睡遥控器

今天早上的事简单得多。我在 Claude Code 里用英文打了一行:

open youtube, piano sleeping disney songs for my daughter

我没说是哪台电视、哪个应用、怎么做。Claude 先确认 ADB 连接还活着,在电视上找到了 YouTube 的包名。然后它先试最直接的办法:带搜索词的 SEARCH intent。电视上的应用直接无视了它。

中间有个小坑:第一次尝试时,搜索词在空格处被拆开了,因为电视上的 shell 把每个词都当成了单独的参数。解决办法是把整条 am start 命令放进 adb shell "..." 的引号里,再在里面给搜索词加一层引号。

接着跳出了 YouTube 的账号选择界面。Claude 选了开通 Premium 的那个,这样摇篮曲放到一半就不会插进广告。

最后管用的是 VIEW intent,配一个普通的 YouTube 搜索链接。电视上的应用会直接打开搜索结果页:

adb shell "am start -a android.intent.action.VIEW \
  -d 'https://www.youtube.com/results?search_query=piano+sleeping+disney+songs' \
  com.google.android.youtube.tv"

焦点默认落在第一个结果上,按一下确定键就开始播放:

adb shell input keyevent KEYCODE_DPAD_CENTER

排在第一的是一个 6 小时的迪士尼歌曲钢琴合集,专门用来助眠。够用了。

确认到底在放什么

这才是我最在意的部分。按下确定键不等于事情办完了,所以 Claude 用两种方式核对了结果。

先读电视的媒体会话,也就是 Android 用来告诉系统“现在在放什么”的机制:

adb shell dumpsys media_session | grep -E "state=|description="

从这里能看到播放状态、标题和播放位置。state 3 表示正在播放。然后它截了一张图,看屏幕上实际显示的是什么:

adb exec-out screencap -p > tv.png

两边对得上。而且播放位置还说明了一件我自己肯定会漏掉的事:视频不是从头开始的,而是从 6 小时 14 分里的 4 小时 13 分处接着放。Claude 告诉了我这一点,还问要不要从头重新播放。

看起来是小事,但我敢在哄睡时交给它,靠的就是这个。一个只会按确定键的脚本会告诉我“成功了”。Claude 告诉我的是电视上实际在放什么,连我自己不会去查的部分也包括在内。

同一个连接,两件事

这两件事用的都只是上周建好的 ADB 连接,别的什么都没有。没有智能家居网关,电视上也没多装应用。

回头看,这两件事其实是一个样子。处理广告时,Claude 不肯把两次安静的回放当成证据。放 YouTube 时,它不肯把按下按钮当成已经在播放。两次它都是先自己去看,再来告诉我。既然要让它在我女儿入睡时操作电视,这正是我想要它做的。

← 全部文章