├── .gitignore
├── .idea
├── .gitignore
├── .name
├── compiler.xml
├── gradle.xml
├── migrations.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── 046279_arm64v8a_x5.tbs.apk
│ ├── 046914_armeabi_x5.tbs.apk
│ └── js
│ │ ├── backwardScript.js
│ │ ├── cctvOpen.js
│ │ ├── forwardScript.js
│ │ └── getEpgScript.js
│ ├── java
│ └── com
│ │ └── eanyatonic
│ │ └── cctvViewer
│ │ ├── BrowseErrorActivity.java
│ │ ├── CardPresenter.java
│ │ ├── ChannelAdapter.java
│ │ ├── DetailsActivity.java
│ │ ├── DetailsDescriptionPresenter.java
│ │ ├── ErrorFragment.java
│ │ ├── MainActivity.java
│ │ ├── MainFragment.java
│ │ ├── Movie.java
│ │ ├── MovieList.java
│ │ ├── PlaybackActivity.java
│ │ ├── PlaybackVideoFragment.java
│ │ ├── VideoDetailsFragment.java
│ │ ├── bean
│ │ └── EpgInfo.java
│ │ └── tools
│ │ ├── FileTool.java
│ │ ├── FileUtils.java
│ │ ├── RestartAppUtil.java
│ │ └── SysTool.java
│ └── res
│ ├── drawable
│ ├── app_icon_your_company.png
│ ├── channel_background_selected.xml
│ ├── channel_background_unselected.xml
│ ├── default_background.xml
│ ├── logo.png
│ ├── movie.png
│ └── rounded_background.xml
│ ├── layout
│ ├── activity_details.xml
│ ├── activity_main.xml
│ └── item_channel.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.webp
│ ├── mipmap-mdpi
│ └── ic_launcher.webp
│ ├── mipmap-xhdpi
│ └── ic_launcher.webp
│ ├── mipmap-xxhdpi
│ └── ic_launcher.webp
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.webp
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── img.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | .idea/deploymentTargetDropDown.xml
17 | *.apk
18 | app/release/output-metadata.json
19 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | 胖弟看电视
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
一个电视机顶盒及Android TV收看中国中央电视台直播的浏览器
2 | 3 | 4 | ## 下载安装包 5 | https://github.com/Zcodeoooo/CCTV_Viewer/releases/ 6 |  7 | 8 | ## 方案选择 9 | 感谢https://github.com/lizongying/my-tv项目的对于央视频vip接口的支持,作者本人的账号开通的vip造福大众,另外由于该项目本身缺少关键解密文件(其实已经有了扒代码调用解密算法的方式取得ckey参数),还有由于项目本身来自于逆向js算法导致的不稳定性(鉴于最近Pandora项目的归档的教训),本人并不借鉴于此 10 | 另外查看了sekiro方案(多设备注入js,提供负载均衡的服务器接口获取数据),可以达到各个方面的均衡,例如兼容性安卓版本 稳定性 速度,可以达到商业性的标准,但是涉及服务器相关,本人也爱莫能助,有心人可以参考下 11 | 12 | ## 注意 13 | 由于目前精力有限,无法支持对于指定播放地址的模块化协作脚本定制化开发,有其他问题请及时反馈 14 | 15 | ## 兼容性说明 16 | 受WebView 第三方页面限制目前只能在安卓4.4及以上系统正常上运行 17 | 已静态集成arm64和armeabi架构 x5内核 18 | (安卓7.0及以下已测试geckoview内核及Crosswalk内核都会播放失败),其他方案请考虑tvbox 19 | 安卓7.0以上系统无需添加x5内核,可以去除,以提升运行速度及占用大小 20 | ## 已更新功能 21 | 今日节目单切换(遥控器菜单键呼出) 22 | 直播进度后退前进(遥控器左右键) 23 | 央视频vip源添加(目前发现问题是cctv特定频道有版权限制,央视频则没有) 24 | 25 | 26 | 27 | ## 计划更新 28 | 下版本计划添加 29 | 频道选择 30 | 31 | 最后感谢chatgpt的大力支持,让我3天学会Android入门 32 | 33 | ## 目前可看频道 34 | "CCTV-1 综合", 35 | "CCTV-2 财经", 36 | "CCTV-3 综艺", 37 | "CCTV-4 中文国际", 38 | "CCTV-5 体育", 39 | "CCTV-6 电影", 40 | "CCTV-7 军事农业", 41 | "CCTV-8 电视剧", 42 | "CCTV-9 纪录", 43 | "CCTV-10 科教", 44 | "CCTV-11 戏曲", 45 | "CCTV-12 社会与法", 46 | "CCTV-13 新闻", 47 | "CCTV-14 少儿", 48 | "CCTV-15 音乐", 49 | "CCTV-16 奥林匹克", 50 | "CCTV-17 农业农村", 51 | "CCTV-5+ 体育赛事", 52 | "CCTV 欧洲", 53 | "CCTV 美国", 54 | 55 | 央视频 56 | "CCTV4K", 57 | "CCTV1", 58 | "CCTV2", 59 | "CCTV3", 60 | "CCTV4", 61 | "CCTV5", 62 | "CCTV5+", 63 | "CCTV6", 64 | "CCTV7", 65 | "CCTV8", 66 | "CCTV9", 67 | "CCTV10", 68 | "CCTV11", 69 | "CCTV12", 70 | "CCTV13", 71 | "CCTV14", 72 | "CCTV15", 73 | "CCTV16-HD", 74 | "CCTV16(4K)", 75 | "CCTV17", 76 | "CGTN", 77 | "CGTN法语频道", 78 | "CGTN俄语频道", 79 | "CGTN阿拉伯语频道", 80 | "CGTN西班牙语频道", 81 | "CGTN外语纪录频道", 82 | "CCTV风云剧场频道", 83 | "CCTV第一剧场频道", 84 | "CCTV怀旧剧场频道", 85 | "CCTV世界地理频道", 86 | "CCTV风云音乐频道", 87 | "CCTV兵器科技频道", 88 | "CCTV高尔夫·网球频道", 89 | "CCTV女性时尚频道", 90 | "CCTV央视文化精品频道", 91 | "CCTV央视台球频道", 92 | "CCTV电视指南频道", 93 | "CCTV卫生健康频道", 94 | 95 | "北京卫视", 96 | "江苏卫视", 97 | "东方卫视", 98 | "浙江卫视", 99 | "湖南卫视", 100 | "湖北卫视", 101 | "广东卫视", 102 | "广西卫视", 103 | "黑龙江卫视", 104 | "海南卫视", 105 | "重庆卫视", 106 | "深圳卫视", 107 | "四川卫视", 108 | "河南卫视", 109 | "福建东南卫视", 110 | "贵州卫视", 111 | "江西卫视", 112 | "辽宁卫视", 113 | "安徽卫视", 114 | "河北卫视", 115 | "山东卫视" 116 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.eanyatonic.cctvViewer' 7 | compileSdk 33 8 | sourceSets { 9 | main { 10 | assets.srcDirs = ['src/main/assets', 'src\\main\\assets'] 11 | } 12 | } 13 | defaultConfig { 14 | applicationId "com.eanyatonic.cctvViewer" 15 | minSdk 21 16 | targetSdk 33 17 | versionCode 1 18 | versionName "1.0" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_15 30 | targetCompatibility JavaVersion.VERSION_15 31 | } 32 | 33 | } 34 | 35 | dependencies { 36 | 37 | implementation 'androidx.leanback:leanback:1.0.0' 38 | implementation 'com.github.bumptech.glide:glide:4.16.0' 39 | implementation 'androidx.appcompat:appcompat:1.6.1' 40 | api 'com.tencent.tbs:tbssdk:44286' 41 | implementation 'androidx.recyclerview:recyclerview:1.3.2' 42 | // 添加 OkHttp 依赖 43 | implementation 'com.squareup.okhttp3:okhttp:4.11.0' 44 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontwarn dalvik.** 24 | -dontwarn com.tencent.smtt.** 25 | 26 | -keep class com.tencent.smtt.** { 27 | *; 28 | } 29 | 30 | -keep class com.tencent.tbs.** { 31 | *; 32 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 |
12 | * 将存储文件目录规范化,对应功能划分目录,后续操作缓存管理进行调用操作
13 | */
14 | public class FileUtils {
15 |
16 | /**
17 | * 保存文件预览的目录
18 | *
19 | * @param context 上下文对象
20 | */
21 | public static File getTBSFileDir(Context context) {
22 | String dirName = "TBSFile";
23 | return context.getExternalFilesDir(dirName);
24 | }
25 |
26 | /**
27 | * 把asset的文件转化为本地文件
28 | *
29 | * @param context 上下文对象
30 | * @param oldPath 旧的文件路径
31 | * @param newPath 新的文件路径
32 | */
33 | public static boolean copyAssets(Context context, String oldPath, String newPath) {
34 | try {
35 | String fileNames[] = context.getAssets().list(oldPath);// 获取assets目录下的所有文件及目录名
36 | if (fileNames.length > 0) {// 如果是目录
37 | File file = new File(newPath);
38 | file.mkdirs();// 如果文件夹不存在,则递归
39 | for (String fileName : fileNames) {
40 | copyAssets(context, oldPath + "/" + fileName, newPath + "/" + fileName);
41 | }
42 | } else {// 如果是文件
43 | InputStream is = context.getAssets().open(oldPath);
44 | FileOutputStream fos = new FileOutputStream(new File(newPath));
45 | byte[] buffer = new byte[1024];
46 | int byteCount;
47 | while ((byteCount = is.read(buffer)) != -1) {// 循环从输入流读取
48 | // buffer字节
49 | fos.write(buffer, 0, byteCount);// 将读取的输入流写入到输出流
50 | }
51 | fos.flush();// 刷新缓冲区
52 | is.close();
53 | fos.close();
54 | }
55 | } catch (Exception e) {
56 | e.printStackTrace();
57 | return false;
58 | }
59 | return true;
60 | }
61 |
62 | /**
63 | * 获取url文件后缀
64 | *
65 | * @param url 文件链接
66 | */
67 | public static String getSuffix(String url) {
68 | if ((url != null) && (url.length() > 0)) {
69 | int dot = url.lastIndexOf('.');
70 | if ((dot > -1) && (dot < (url.length() - 1))) {
71 | return url.substring(dot + 1);
72 | }
73 | }
74 | return "";
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/eanyatonic/cctvViewer/tools/RestartAppUtil.java:
--------------------------------------------------------------------------------
1 | package com.eanyatonic.cctvViewer.tools;
2 |
3 | import static android.content.Context.JOB_SCHEDULER_SERVICE;
4 | import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
5 |
6 | import android.annotation.TargetApi;
7 | import android.app.ActivityManager;
8 | import android.app.AlarmManager;
9 | import android.app.PendingIntent;
10 | import android.app.job.JobInfo;
11 | import android.app.job.JobParameters;
12 | import android.app.job.JobScheduler;
13 | import android.app.job.JobService;
14 | import android.content.ComponentName;
15 | import android.content.Context;
16 | import android.content.Intent;
17 | import android.os.Build;
18 |
19 | /**
20 | * 多种方式重启应用自身
21 | */
22 |
23 | public class RestartAppUtil
24 | {
25 |
26 | /**
27 | * 使用 AlarmManager 来帮助重启
28 | *
29 | * @param context
30 | * @param cls
31 | */
32 | public static void restartByAlarm(Context context, Class> cls)
33 | {
34 | Intent mStartActivity = new Intent(context, cls);
35 | int mPendingIntentId = 123456;
36 | PendingIntent pIntent = PendingIntent.getActivity(context, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
37 |
38 | AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
39 | alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 500, pIntent);
40 |
41 | System.exit(0);
42 | }
43 |
44 | /**
45 | * 使用 killProcess 杀死自身,系统会恢复应用
46 | *
47 | * @param context
48 | * @param cls
49 | */
50 | public static void restartByKillProcess(Context context, Class> cls)
51 | {
52 | Intent intent = new Intent(context, cls);
53 | intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
54 | context.startActivity(intent);
55 | android.os.Process.killProcess(android.os.Process.myPid());
56 | }
57 |
58 | /**
59 | * 通过清栈触发应用重启。但不会重启 application ,与应用相关的静态变量也会更重启前一样。
60 | *
61 | * @param context
62 | */
63 | public static void restartByClearTop(Context context)
64 | {
65 | Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
66 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
67 | context.startActivity(intent);
68 | }
69 |
70 | /**
71 | * 利用系统重启api触发应用重启
72 | *
73 | * @param context
74 | */
75 | public static void restartBySystemApi(Context context)
76 | {
77 | ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
78 | manager.restartPackage(context.getPackageName());
79 | }
80 |
81 | /**
82 | * 通过 Intent.makeRestartActivityTask 来触发应用重启,跟 restartByClearTop 类似。
83 | * 但不会重启 application ,与应用相关的静态变量也会更重启前一样。
84 | *
85 | * @param context
86 | */
87 | public static void restartByCompatApi(Context context, Class> cls)
88 | {
89 | Intent intent = new Intent(context, cls);
90 | Intent restartIntent = Intent.makeRestartActivityTask(intent.getComponent());
91 | context.startActivity(restartIntent);
92 | System.exit(0);
93 | }
94 |
95 | /**
96 | * 5.1 版本以后可以借助 JobScheduler 来重启应用
97 | *
98 | * @param context
99 | */
100 | public static void restartByJobScheduler(Context context, Class> cls)
101 | {
102 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
103 | {
104 | int delayTimeMin = 1000;
105 | int delayTimeMax = 2000;
106 |
107 | MyJobSchedulerService.setMainIntent(new Intent(context, cls));
108 |
109 | JobInfo.Builder jobInfoBuild = new JobInfo.Builder(0, new ComponentName(context, MyJobSchedulerService.class));
110 | jobInfoBuild.setMinimumLatency(delayTimeMin);
111 | jobInfoBuild.setOverrideDeadline(delayTimeMax);
112 | JobScheduler jobScheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE);
113 | jobScheduler.schedule(jobInfoBuild.build());
114 |
115 | System.exit(0);
116 | }
117 | }
118 |
119 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
120 | static class MyJobSchedulerService extends JobService
121 | {
122 | private static Intent mIntent;
123 |
124 | public static void setMainIntent(Intent intent)
125 | {
126 | mIntent = intent;
127 | }
128 |
129 | @Override
130 | public boolean onStartJob(JobParameters params)
131 | {
132 | startActivity(mIntent);
133 | jobFinished(params, false);
134 | return false;
135 | }
136 |
137 | @Override
138 | public boolean onStopJob(JobParameters params)
139 | {
140 | return false;
141 | }
142 | }
143 |
144 | }
145 |
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/eanyatonic/cctvViewer/tools/SysTool.java:
--------------------------------------------------------------------------------
1 | package com.eanyatonic.cctvViewer.tools;
2 |
3 | import android.os.Build;
4 | import android.util.Log;
5 |
6 | public class SysTool {
7 | public static String showSysAach(){
8 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
9 | return Build.SUPPORTED_ABIS[0];
10 | } else {
11 | return Build.CPU_ABI;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/app_icon_your_company.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zcodeoooo/CCTV_Viewer/2e99f56235e9beda50d7a6689b01ed610fe055b4/app/src/main/res/drawable/app_icon_your_company.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/channel_background_selected.xml:
--------------------------------------------------------------------------------
1 |
2 |