一按遥控器就能换台:my-tv 电视直播应用装与用要点
【免费下载链接】my-tv我的电视 电视直播软件,安装即可使用项目地址: https://gitcode.com/GitHub_Trending/my/my-tv
家里的智能电视只带一个遥控器,你想让它在沙发上像机顶盒一样直接看央视、卫视的直播,my-tv 就是干这个的——一个「安装即可使用」的 Android TV 直播应用,内置央视/地方/港澳台/国际四组频道,靠遥控器的上下键换台。本文带你把它装进电视、看懂它是怎么把遥控器按键变成换台动作的,以及改代码时最容易踩的几个坑;不讲后端协议细节和加密算法的内部实现。
全局地图:先看结论再抠细节
| 要解决的问题 | 项目里的做法 | 对应文件 |
|---|---|---|
| 电视上没有鼠标,怎么启动应用 | 用 Leanback 的LEANBACK_LAUNCHER让 TV 端识别为电视应用 | app/src/main/AndroidManifest.xml |
| 遥控器按键怎么变成换台动作 | 在onKeyDown里把CHANNEL_UP/DOWN映射到切台方法 | app/src/main/java/com/lizongying/mytv/MainActivity.kt |
| 频道从哪来、怎么分组 | 用Map<String, List<TV>>静态内置,mustToken过滤 | app/src/main/java/com/lizongying/mytv/TVList.kt |
| 换完台记得住上次看到哪个 | 用SharedPreferences存itemPosition | app/src/main/java/com/lizongying/mytv/SP.kt |
| 一个频道有多个片源、断了要能恢复 | videoUrl存成列表,onStart里自动prepare重播 | app/src/main/java/com/lizongying/mytv/PlayerFragment.kt |
上图是作者标注的小米遥控器按键位:中间方向盘负责「上一频道/下一频道」和「打开菜单/确认频道」,下方三个小键负责关闭菜单和帮助。理解了这张图,下面看代码就知道每个键对应哪段逻辑了。
把应用装进电视:先让 TV 端认得它
结论先行:电视直播应用和普通手机应用最大的区别是「没有鼠标」,所以启动方式必须声明成电视应用,否则有些盒子根本装不上或点不开。
看清单文件里的两段声明,这是 TV 应用的「身份证」:
<uses-feature android:name="android.software.leanback" android:required="true" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>LEANBACK_LAUNCHER是电视端的启动器类别,加了它 TV 主界面才会把它当成「电视应用」展示出来;同时touchscreen被声明成required="false",意思是「没触屏也能跑」,这正契合电视盒子。
反例提示:很多坑出在这一步。如果你只留LAUNCHER类别、漏掉LEANBACK_LAUNCHER,应用在部分电视上会以「手机应用」身份出现,甚至被系统判定为不兼容而拒绝安装。另外别把largeHeap="true"这种针对直播长播放的内存配置随手删掉,长会话容易因为内存不足闪退。
安装本身有三种路子:能开 ADB 的盒子直接adb install my-tv.apk;小米电视可以走「小米电视助手」;其它就 U 盘拷进电视里装。想改代码再打包,仓库地址是 https://gitcode.com/GitHub_Trending/my/my-tv ,git clone下来即可。
配置遥控器按键的 5 个动作
结论先行:换台、返回、开设置这些手感好不好,全看onKeyDown里按键码映射得对不对,这里是整个遥控器交互的心脏。
MainActivity把物理键码翻译成具体动作,核心就这五个分支:
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { when (keyCode) { KeyEvent.KEYCODE_CHANNEL_UP -> channelUp() KeyEvent.KEYCODE_CHANNEL_DOWN -> channelDown() KeyEvent.KEYCODE_DPAD_CENTER -> switchMainFragment() KeyEvent.KEYCODE_MENU -> showSetting() KeyEvent.KEYCODE_BACK -> back() } return super.onKeyDown(keyCode, event) }为什么这么写:CHANNEL_UP/DOWN是机顶盒遥控器上的专用换台键,很多 TV 盒子的「上下方向键」并不会触发它,所以这里同时接了方向键和换台键两套来源,避免「有的盒子好使、有的不使」。BACK键被拦截成自定义back(),实现「连按两次才退出」,防止用户看球时一个误触就把直播关掉了。
反例提示:新手常犯的错误是只处理KEYCODE_DPAD_UP、忘了KEYCODE_CHANNEL_UP,结果换台键在不少品牌盒子上完全失灵。另一个坑是back()里用doubleBackToExitPressedOnce做二次确认时,2 秒后没把标志位复位,下次按返回键直接退出——这段延迟复位逻辑别删。
组织频道列表并记住上次停在哪个台
结论先行:频道不是从服务器拉的,而是内置成一个「分组名 → 频道数组」的 Map,换台时靠一个下标在拍平的列表里前后移,最后把下标存进本地就能「断点续看」。
频道数据集中在 TVList.kt,结构一目了然:
var list = mapOf( "央视" to listOf(TV(0, "CCTV1 综合", "CCTV1", listOf(), "央视", ..., ProgramType.Y_PROTO, false, mustToken = false), ...), "地方" to listOf(TV(0, "东方卫视", "东方卫视", listOf(), "地方", ..., ProgramType.Y_PROTO, true, mustToken = false), ...), "港澳台" to listOf(TV(0, "凤凰卫视资讯台", "", listOf(), "港澳台", ..., ProgramType.F, false, mustToken = false), ...) )注意mustToken = true的频道(比如某些综艺频道)会在setup()里被直接过滤掉,不放进最终列表——这是「暂时播不了的就不显示」的取舍。每条TV还带programType,标记走央视 proto 协议、JCE 协议还是凤凰的 F 协议,后面拉取 EPG 时按这个分发。
换台动作在 MainFragment.kt 里就是一个下标的循环移动,退出时把下标存进 SP.kt:
fun next() { view?.post { itemPosition++ if (itemPosition == tvListViewModel.size()) itemPosition = 0 // 到末尾回到开头 tvListViewModel.setItemPosition(itemPosition) tvListViewModel.getTVViewModel(itemPosition)?.changed() } } // 在 onStop() 里: SP.itemPosition = itemPosition为什么这样写:把「换台」抽象成对itemPosition的自增和回绕,配合SP的itemPosition存取,应用一冷启动就停在用户上次看到的台,体验上就像传统电视。
反例提示:如果给每个频道各发一个网络请求去切台,既慢又容易撞并发。这里统一由MainFragment持有「当前下标」这一个真源,其它 Fragment 只读不写。千万别在PlayerFragment里再维护一份「当前台号」,两边一不一致,换台就会跳到错的台。
处理多片源切换和播放失败恢复
结论先行:直播最让人抓狂的是「切到某台黑屏」,项目靠「一个频道挂多个片源 + 生命周期里自动重播」两招来兜底。
每个频道的videoUrl是一个列表而不是单条,TV.kt 里是List<String>,PlayerFragment.kt 只负责取当前下标那条去播:
fun play(tvViewModel: TVViewModel) { playerView?.player?.run { setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent())) prepare() } } override fun onStart() { if (playerView?.player?.isPlaying == false) { playerView?.player?.prepare() // 恢复播放 playerView?.player?.play() } }为什么这样写:getVideoUrlCurrent()按videoIndex取当前片源,一旦某条源失效,可以把videoIndex切到列表里的下一条;而onStart里的prepare()重播,专门处理「切后台再切回来」时播放被挂起的情况——更新日志里的「恢复播放」就是指它。
反例提示:典型错误是把播放地址存成一个String,源一旦失效就彻底黑屏且无法自愈。正确姿势是像这里一样存成List<String>,把「当前用第几路」和「一共有哪些路」分开管理。另外onPause里要记得stop()释放解码器,否则从设置页返回时新旧两路音频叠在一起、内存也跟着涨。
📋 上手自检清单
- 清单里同时声明了
LEANBACK_LAUNCHER和LAUNCHER两个类别,电视端能正确识别为 TV 应用 onKeyDown同时接了CHANNEL_UP/DOWN和方向键,换台在不同盒子都不失灵BACK键走二次确认退出,且 2 秒后复位了doubleBackToExitPressedOnce标志- 频道按「央视/地方/港澳台/国际」分组,
mustToken=true的已被过滤不显示 - 换台只改
itemPosition一个下标,退出时写进SP.itemPosition实现断点续看 - 每个频道
videoUrl是列表而非单条,失效时能切到下一条源 onStart里有prepare()恢复播放,onPause里有stop()释放解码器- 用
adb install或 U 盘把 apk 装进实际要用的电视盒子,真机验证过换台
想真正搞明白它怎么把遥控器变成一台「能换台、记得住、断网能自愈」的电视,最该先读的就是 MainActivity.kt 的按键分发和 MainFragment.kt 的下标换台。建议你先把 apk 装到一台真电视上换几个台试试手感,再回头对照代码找对应分支,比干读文档快得多。
【免费下载链接】my-tv我的电视 电视直播软件,安装即可使用项目地址: https://gitcode.com/GitHub_Trending/my/my-tv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考