SearchActivity 七个小动作:让公开源也能“点了就播”
公开资源多到眼花缭乱,可只要入口糟糕,看片自由就停留在口号。LibreTV 想要解决的,是那种“抓到就播、不做调度”的粗暴模式:晚高峰大家一起挤,单源直接趴窝,用户还得手动反复换源。这篇文章从 SearchActivity 出发,串起我如何把折腾党关心的历史记录、切源、搜索速度等体验一一打磨,顺手分享一些实现细节。喜欢折腾、又不想被折磨的朋友,欢迎一起聊聊。
“我喜欢免费片源,作为白嫖党的骄傲~~ 傲傲傲~ ,但更喜欢在 SearchActivity 里感受到‘点了就有反应’的尊重。”掘金的朋友大多是工程师,光讲故事不过瘾,所以本文会把关键代码和思路都扔出来。
S|情景(Situation):资源很多,但入口体验一塌糊涂
公开资源站点像海边贝壳,随便捡;但播放器基本是“单源 + 轮询”,晚高峰直接挤爆。夸克式“抓到就播”白天尚且能用,晚上就是看运气。用户一边抱怨卡顿,一边被迫手动换源。你夜里最怕哪一类问题——搜索转圈、还是点击后完全没反馈?欢迎顺手评论告诉我。
让搜索体验对得起“折腾党”
给自己定了三个小目标:
- 搜索反馈得快,哪怕只是先露一部分结果;
- 命中单源时也要展现来源、剧集、清晰度,再决定是否播放;
- 历史、清除、空态这些细节要让人“手起刀落”,别让人怀疑播放器挂了。
背后就是一句话:让公开源在晚高峰也有“点了就有”的错觉(最好是真的)。
SearchActivity 的七个小手术
-
并发搜索流:
searchVideosStreaming让多个源同时出击,谁先返回谁先上屏,避免“等最后一名”。核心实现是channelFlow + async,下面是删减过的版本:fun searchVideosStreaming(query: String) = channelFlow { val aggregate = mutableListOf<VideoInfo>() val mutex = Mutex() coroutineScope { sources.map { src -> async { repository.searchFromSource(query, src).getOrElse { emptyList() }.also { list -> if (list.isNotEmpty()) mutex.withLock { aggregate += list send(SearchRankingUtils.rankSearchResults(aggregate, query)) } } } }.awaitAll() } } -
单源也展示来源列表:不再一股脑跳播放器,让用户确认来源和剧集再决定要不要播。
-
历史关键词一键再战:Chip 点击即复用并触发搜索,少敲几个字。
-
清空按钮即时显示:
TextWatcher负责显隐,空文本直接收起来。 -
空态与加载不要互扯:Loading 时隐藏 empty view。SearchActivity 里是
isLoading.observe+groupedSearchResults.observe两套观察一起协作,确保“还在加载”时不会误以为空结果。 -
进度条别妨碍输入:触发搜索后锁焦、收键盘,避免又点又跳。
-
失败就说一声:某个源挂了就 Toast,告诉用户“别的源还在路上”,别让他们以为整个 App 挂了。
在你看来,哪一个小细节最能让你信任一个播放器?或者你还想要什么反馈方式?
点了就播,至少有反应
- 速度可感知:通常 1–2 个源返回就能看到列表,减少盯圈圈的尴尬。
- 晚高峰容忍度高:某个源抽风,还有“其他源加载中”的提示。
- 交互一致:单源也照规矩走,用户不再在多个页面来回跳。
- 包体更轻:顺手砍掉 40+ 无用资源与模板,维护成本明显下降。
下一版你更想看到“更多片源”还是“搜索结果自带推荐/缓存策略”?评论区告诉我路线图。
说给一起折腾的人
- 别迷信单源:并发 + 流式更新才是真正的高峰解药。
- 交互就是尊重:按钮、空态、历史都要真实反馈,否则折腾党直接卸载。
- 定期清仓:无用 drawable、模板、Fragment 趁早删,别让未来的你背锅。
公开免费资源确实多,但没有一个能在晚上稳定播放的入口,自由永远是口号。欢迎分享你在 SearchActivity 里的妙招,或者直接甩你想看的新功能,我们一起把折腾进行到底。