轻松在电视上观看直播,操作简单,适合家中老人使用,使用 WebView JavaScript 注入方式实现
- 方向上: 上一个频道
- 方向下: 下一个频道
- 左键: 声音减
- 右键: 声音加
- 确定键: 播放/暂停
- 数字键: 换台
- 菜单键:选择换台
- 返回键:返回/退出
- #号键:强制刷新当前频道(有问题时使用)
使用老式的闭路电视操作,适合老人使用!
- USB数据线连接电视和手机(或U盘),打开手机的通知,将
仅充电改为文件传输,然后把安装包复制到电视,电视找到相关USB存储,安装apk - 通过无线方式安装,需要 android 11 以上,打开电视
adb调试(如何开启请百度),使用甲壳虫adb助手连接电视后安装
具体请百度或问AI,这里不进行详细描述
有些电视限制安装第三方App,需要使用 adb 禁用软件包安装程序
tcl电视举例(其他品牌电视请问AI或百度):需要电视开启adb,进入 设置 -> 关于 -> 依次按下 上下左右 ,之后会显示扩展设置,
找到开启adb开关,然后使用 https://www.jiakechong.net/ 甲壳虫adb助手 连接到电视,
连接之前需要获取电视在网络中的ip,打开电视网络信息会有ipv4 地址,这个就是。然后用甲壳虫找到应用管理,
禁用软件安装包程序就可以安装app了。 安装完成之后再把软件安装包程序启用。
- 查找电视的一些设置、App,看看有没有应用自启动相关的设置,对本应用开启自启动。可百度或 Ai 搜索自启动相关文章
- 确保 App 已经打开过,并允许了悬浮窗权限
- 设置默认的桌面,一般按 Home键(主页键) 会提示设置默认桌面,可以设置为本App并选择始终,或查找电视相关设置和App设置默认桌面
不保证所有电视都支持,仅在原生系统虚拟机 Android 12 测试通过
如果上面的方法不奏效,可以尝试以下方法:
# 无线连接 adb connect ip:端口
adb devices # 查看设备是否连接成功
adb shell # 进入管理员模式
cmd package set-home-activity "xyz.jdynb.tv/.MainActivity" # 设置默认的启动项
reboot # 重启设备注意:定制系统可能无效
部分电视系统内核的版本低,所以需要使用X5内核,这个需要自己自行编译此项目,需要一定的android基础。
- 在腾讯浏览服务 登录账号,之后创建应用,上传打包后的 apk
- 进行实名认证
- 在应用列表点击
查看应用,打开版本配置列表 - 获取配置文件,将下载的配置文件,替换
app/src/x5/assets/tbs/config.tbs文件 - 修改
app/src/main/build.gradle文件,编辑signingConfigs签名配置, 选择构建分支为x5,重新进行打包构建 apk - 运行安装完成后,查看是否成功
频道完整配置文件 main/assets/live-3.jsonc live-3.jsonc
最新调整:
- 去除
number频道号码 - 变更
live.jsonc为live-3.jsonc
修改 channel 频道列表
{
// ...
"channel": [
// ...
{
"channelType": "xxx", // 频道类型(分类名称)
"player": "common", // 播放器 id,这里写上,下方的 channelList 中没有设置的就默认为这个值
"channelList": [
{
"channelName": "香港卫视", // 频道名称 (需要唯一)
"tvLogo": "", // logo 目前没用到,可以不写
"number": 6, // 填写唯一的频道号码,用于数字换台 (废弃)
"player": "common", // 播放器 id,这个上方已经内置了,只需要写 common
"args": { // 播放参数,固定 liveUrl 即可
"liveUrl": "https://wwww.tv.com/playlist.m3u8?"
}
}
]
}
]
}注意上方的
liveUrl只能是普通的 m3u8地址,并且没有带时间戳等相关的参数
有些直播地址是有验证参数的,需要在脚本中进行处理生成
增加自定义频道,修改 channel 配置
// 省略其他
{
// ...
"channel": [
// ...
{
"channelType": "xx电视分类",
"player": "custom", // 设置自定义播放 id
"channelList": [
// ...
{
"channelName": "xx电视",
"tvLogo": "https://web.cmc.hebtv.com/cms/rmt0336/upload/Image/9/9tpklm/2025/07/18/30aaea69a1de46a881cef5d759d30b3e.png",
"number": 23
}
// ...
]
}
]
}{
"player": {
"id": "custom", // 播放器 id,和上面定义的频道 player 参数一致
"name": "simple", // 内置的播放器名称,做了特殊处理,默认写这个
"script": { // 需要执行的 js 脚本
"init": [ // 在网页初始化完成时候执行的 js 脚本
"https://gitee.com/jdy2002/DongYuTvWeb/raw/master/app/src/main/assets/js/hebei/init.js"
],
"play": [ // 响应播放事件执行的 js 脚本
"https://gitee.com/jdy2002/DongYuTvWeb/raw/master/app/src/main/assets/js/hebei/play.js"
],
"resume_pause": [] // 暂停恢复执行的 js 脚本,这里留空,因为内部已经做了处理了
}
}
}不需要云服务器,在 gitee 上建一个仓库,上面脚本地址自己写上仓库中实际的地址即可,具体格式参照上面的
simple 播放器已经内置了 CryptoJS,所以这里就不需要引入了,直接使用即可
脚本中,
{{channelName}}会被替换成用户选择的频道名称,当然其他 args 参数也会被这样替换进入
内部已经关闭了跨域限制,可以直接请求
playLive是内置的方法,可以直接调用传入地址进行播放
注意:使用fetch请求时,有些会检测Referer请求头,请设置X-Referer请求头,内部会进行处理, 请求头数据请使用
X-Body添加到请求头中,这里的数据将作为 Body 发送,
这种方式有限制,有些请求会检测 Referer 请求头,请设置自定义请求头 X-Referer,
推荐使用内置的 HttpUtil 发送请求
(function() {
// 这里在播放地址上面加上需要的参数
window.addLiveUrlQuery = function (obj) {
let ts = parseInt(new Date().getTime() / (1000)) + (7200);
// CryptoJS 已经是内置的,可以直接使用
return obj.liveVideo[0].formats[0].url + "?t=" + ts + '&k=' + CryptoJS.MD5(obj.appCustomParams.movie.liveUri + obj.appCustomParams.movie.liveKey + ts);;
}
fetch("https://api.cmc.hebtv.com/scms/api/com/article/getArticleList?catalogId=32557&siteId=1", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"sec-ch-ua": "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
// 如果不检测 Referer的话,可以正常写
"X-Referer": "https://www.hebtv.com/" // X-Referer 会自动在请求头添加 Referer,防止被检测
// 必须在设置了 X-Referer 之后,X-Body 才有效
"X-Body": "" // 这里设置请求体数据
},
"body": null,
"method": "GET"
}).then(res => {
return res.json()
}).then(res => {
const news = res.returnData.news
window.channelList = news.map(item => {
return {
title: item.title,
liveVideo: item.liveVideo,
appCustomParams: item.appCustomParams
}
})
// {{channelName}} 会被替换成频道名称,上面的 channel中的属性都会被传入到脚本中,这里可以进行自定义处理
const channelItem = window.channelList.find(item => item.title === '{{channelName}}')
const playUrl = window.addLiveUrlQuery(channelItem)
// 初始化时机执行的,所以这里需要对当前播放的频道进行播放,play.js 不会在初始化时候调用生效
playLive(playUrl)
})
})();内置在 simple 播放器中的,可以直接使用,推荐使用这种方式发送请求
const response = await HttpUtil.post(`https://feiying.litenews.cn/api/v1/auth/exchange?t=${now}&s=${s}`, body, {
headers: {
"accept": "*",
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"content-type": "text/plain",
"priority": "u=1, i",
"sec-ch-ua": "\"Google Chrome\";v=\"143\", \"Chromium\";v=\"143\", \"Not A(Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"Referer": "https://v.iqilu.com/"
},
responseType: 'text'
})
const result = JSON.parse(decrypt(response.data))
playLive(result.data)HttpUtil 的定义参见 HttpUtil
(function() {
const channelItem = window.channelList.find(item => item.title === '{{channelName}}')
const playUrl = window.addLiveUrlQuery(channelItem)
playLive(playUrl)
})();对于加密的直播地址,需要使用 Base 播放器,直接通过浏览器加载来实现播放,去除播放器以外的节点,实现全屏播放。
init.js实现 全屏并去除其他无用样式进行初始化
play.js实现 直接使用 window.location.href 进行跳转
resume_pause.js实现 查找按钮并点击
配置参考
url参数支持动态参数,使用 {{}} 方式传入
// player 配置
{
"id": "guangxi",
"name": "base", // 指定为 base
"url": "https://tv.gxtv.cn/channel/channelivePlay_{{id}}.html", // url 支持动态参数
// 设置指定的脚本
"script": {
"init": ["https://gitee.com/jdy2002/DongYuTvWeb/raw/master/app/src/main/assets/js/guangxi/init.js"],
"play": ["https://gitee.com/jdy2002/DongYuTvWeb/raw/master/app/src/main/assets/js/guangxi/play.js"],
"resume_pause": ["https://gitee.com/jdy2002/DongYuTvWeb/raw/master/app/src/main/assets/js/guangxi/resume_pause.js"]
}
}id 参数会注入到上面 url 的 {{id}} 中
// channel 配置
{
"channelType": "广西",
"player": "guangxi",
"channelList": [
{
"channelName": "广西卫视",
"args": {
"id": "e7a7ab7df9fe11e88bcfe41f13b60c62"
}
},
{
"channelName": "综艺旅游",
"args": {
"id": "f3335975f9fe11e88bcfe41f13b60c62"
}
}
]
}/main/assets/html/simple_player.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
<style>
body {
background: #000000;
}
#video {
width: 100vw;
height: 100vh;
}
#loading {
position: fixed;
left: 0;
top: 0;
z-index: 99;
width: 100vw;
height: 100vh;
font-size: 30px;
font-weight: bold;
color: white;
display:flex;
align-items: center;
justify-content: center;
}
</style>
<!-- 这里内部做了处理 -->
<script src="../js/lib/dy-http-util.js"></script>
<script src="../js/lib/dy-crypto-js.min.js"></script>
<script src="../js/lib/dy-hls.min.js"></script>
</head>
<body>
<video autoplay id="video"></video>
<div class="loading-overlay" id="loading">
加载中...请稍后
</div>
</body>
<script>
const video = document.querySelector("#video")
video.addEventListener('loadeddata', function() {
hideLoading()
})
function hideLoading() {
const loading = document.getElementById('loading');
loading.style.display = 'none';
}
function resumeOrPause() {
if (video.paused) {
video.play();
} else {
video.pause();
}
}
let hls = null;
function playLive(url, headers) {
video.volume = 1
// 检查是否支持 HLS.js 且为 m3u8 文件
if (Hls.isSupported() && url.includes('.m3u8')) {
if (hls) {
hls.destroy();
}
hls = new Hls({
debug: false,
xhrSetup: function(xhr, url) {
// xhr.setRequestHeader('X-Referer', headers['Referer'])
// 这里设置请求头
if (headers) {
for (const key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
});
hls.loadSource(url);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
hls.on(Hls.Events.ERROR, function(event, data) {
console.error('HLS Error:' + JSON.stringify(data));
if (data.fatal) {
switch(data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log('Network error, trying to recover');
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('Media error, trying to recover');
hls.recoverMediaError();
break;
default:
console.log('Fatal error, destroying HLS instance');
hls.destroy();
break;
}
}
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// iOS Safari 原生支持
video.src = url;
video.play();
} else {
// 其他格式直接播放
video.src = url;
video.play();
}
}
</script>
</html>其他使用方法,请查看项目的 /app/src/main/assets 中的代码
仅供学习交流,请勿用于商业用途


