Compare commits

..
104 Commits
Author SHA1 Message Date
Li ZongYing d65ac508eb readme 2024-05-31 12:27:23 +08:00
Li ZongYing b372d52510 add license 2024-04-26 20:44:07 +08:00
Li ZongYing 608a7e3fef readme 2024-04-19 18:01:01 +08:00
Li ZongYing b980729224 readme 2024-04-19 17:57:58 +08:00
Li ZongYing 641f8a9108 add time 2024-04-19 15:22:05 +08:00
Li ZongYing 74b62870c6 some channel epg 2024-04-03 18:46:49 +08:00
Li ZongYing a3a9bdc762 dpad-right to enter the menu & dpad-left for channel list 2024-04-03 08:50:00 +08:00
Li ZongYing 019c5a8e66 dpad-right to enter the menu 2024-04-03 08:33:21 +08:00
Li ZongYing fd897f68c0 fix crash on setting 2024-03-29 16:19:27 +08:00
Li ZongYing 65c4b0cf52 channel filter 2024-03-26 13:36:26 +08:00
Li ZongYing 8290a86909 readme 2024-03-18 19:39:04 +08:00
Li ZongYing 4e05fe91d7 readme 2024-03-18 19:36:46 +08:00
Li ZongYing d5de8a8d2a readme 2024-03-18 19:35:39 +08:00
Li ZongYing c232099d29 readme 2024-03-18 19:32:53 +08:00
Li ZongYing 3fb4b1e585 readme 2024-03-18 18:34:58 +08:00
Li ZongYing 7b0e144cb1 fix update manager 2024-03-18 18:29:56 +08:00
Li ZongYing 06b8df5f82 request optimize 2024-03-15 21:51:27 +08:00
Li ZongYing 7bed18ee6d phoenix EPG 2024-03-15 17:16:12 +08:00
Li ZongYing 0c2beb936a phoenix retry 2024-03-15 17:16:12 +08:00
WoooKong 51ab79b5ca Optimize SettingFragment (#536) 2024-03-15 17:04:50 +08:00
Li ZongYing c8de039d05 logo 2024-03-11 10:26:19 +08:00
Li ZongYing c9494f7bf3 cetv1 logo 2024-03-09 21:12:56 +08:00
Sky 79b6cfcd11 fix typo (#541) 2024-03-09 18:59:04 +08:00
Li ZongYing 31988d0d86 fh fhd 2024-03-09 18:08:53 +08:00
Li ZongYing e55e8ceb18 delayHideMain 10s 2024-03-09 08:50:27 +08:00
Li ZongYing 0b62742350 token-v2 2024-03-08 22:50:47 +08:00
Li ZongYing 558fe7e4e7 cetv1 2024-03-08 21:17:13 +08:00
Li ZongYing e2145f8d0d bootStartup->false 2024-03-06 01:27:13 +08:00
Li ZongYing 46b4df1306 readme 2024-03-06 01:25:19 +08:00
李宗英 358539f9c3 Merge pull request #455 from WoooKong/main
创建 SP 工具类,管理所有(除 ChannelUtils 以外)用户偏好设置
2024-03-06 01:13:10 +08:00
WoooKong 17d8d4cb46 还原 VersionCode 2024-03-04 18:07:15 +08:00
WoooKong dc81112a3d Merge branch 'main' into main 2024-03-04 17:52:02 +08:00
Li ZongYing 95ea83e45c add phoenix tv 2024-02-25 16:08:49 +08:00
Li ZongYing a6078e75e1 got 2024-02-25 11:16:09 +08:00
Li ZongYing 8a3720049f update mannager 2024-02-25 10:19:35 +08:00
WoooKong ede420a190 调整注释 2024-02-19 16:55:34 +08:00
WoooKong f94d136a03 创建 SP 工具类,管理所有(除 ChannelUtils 以外)用户偏好设置 2024-02-19 12:23:05 +08:00
李宗英 e7780434d7 Merge pull request #442 from JohnsonRan/main
fix: LibChecker -> my-tv
2024-02-18 18:30:28 +08:00
旋律旋律你在干什么 67edb2830c fix: LibChecker -> my-tv 2024-02-18 13:06:12 +08:00
Li ZongYing 2869164368 readme 2024-02-17 09:15:16 +08:00
Li ZongYing 83a70291d4 fix tianmao-mohe 2024-02-16 22:14:11 +08:00
李宗英 872f668c59 Merge pull request #427 from DavidDengHui/patch-3
修复远程服务地址
2024-02-16 22:02:37 +08:00
李宗英 426839c747 Merge pull request #425 from DavidDengHui/patch-1
Fix MainActivity.kt
2024-02-16 21:59:33 +08:00
邓晖 3e513d5adf Fix server.xml
原远程服务CDN地址`https://cdn.jsdelivr.net/gh/LeGend-wLw/my-tv-json-utils@raw/main/channels.json`有误已修复。
另外GitHub镜像服务`fastly.jsdelivr.net`要比`cdn.jsdelivr.net`快一些已修改。
2024-02-14 16:06:07 +08:00
邓晖 86d4081d03 Fix MainActivity.kt
不知道理解的对不对,第244行好像是打错了?
2024-02-14 14:37:07 +08:00
李宗英 f76aa37169 Merge pull request #402 from fanmingming/patch-1
Resolve the issue of inaccessible resources in mainland China.
2024-02-11 11:00:15 +08:00
李宗英 d944777fc6 Merge pull request #400 from tuuzed/main
feat: 新增开机自启配置项,默认启用
2024-02-11 10:55:22 +08:00
范明明 a45244c552 Resolve the issue of inaccessible resources in mainland China. 2024-02-11 10:52:54 +08:00
Li ZongYing 809ec920a5 get time online 2024-02-11 10:42:00 +08:00
Li ZongYing 8e4f7df8d4 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-02-11 10:30:30 +08:00
Li ZongYing fe209183ea fix token bug 2024-02-11 10:26:59 +08:00
Li ZongYing b7ec58ac5b readme 2024-02-11 10:20:58 +08:00
Li ZongYing 096acdf55e readme 2024-02-11 10:17:15 +08:00
liuyonghui c8d59d5aab feat: 新增开机自启配置项,默认启用 2024-02-11 09:58:50 +08:00
李宗英 2b4b823946 Merge pull request #304 from 2926295173/patch-1
[GitHub] 创建 issue 模板,分类 issue
2024-02-08 19:01:16 +08:00
AOIAN 127ceeb0b0 Create fr.yml 2024-02-07 13:01:09 +08:00
AOIAN 9dd6a2155f Create bug.yml 2024-02-07 12:58:05 +08:00
Li ZongYing 526a7c5c40 fix ysp 2024-02-07 02:57:36 +08:00
Li ZongYing 960f890ddd readme 2024-02-06 23:03:36 +08:00
李宗英 0a7837c577 Merge pull request #223 from LeGend-wLw/main
完成服务器有关所有功能,并优化相关代码
2024-02-06 15:59:12 +08:00
LeGend-wLw d8342e8c6a Add:从淘宝服务器获取当前时间 2024-02-05 14:25:18 +08:00
LeGend-wLw 6387f48409 Add:淘宝服务器返回时间戳格式 2024-02-05 14:24:46 +08:00
LeGend-wLw 807300918c Fix:网络请求超时时间修改为500ms 2024-02-05 14:15:52 +08:00
LeGend-wLw 36eacb7a57 Fix:添加1s超时 2024-02-05 14:14:37 +08:00
LeGend-wLw b4abe0ccc6 Merge remote-tracking branch 'origin/main' 2024-02-05 13:24:25 +08:00
LeGend-wLw fd1dc8d3c7 Fix:更改服务器地址为Github地址 2024-02-05 13:21:44 +08:00
LeGend-wLw ea56bf1b80 Merge branch 'main' into main 2024-02-05 13:10:07 +08:00
Li ZongYing 4fb27ba502 fix channel number did not disappear 2024-02-05 06:12:01 +08:00
LeGend-wLw a746fe6c65 Add:添加log信息 2024-02-04 23:58:11 +08:00
LeGend-wLw 594d5e3499 Merge branch 'main' of https://github.com/LeGend-wLw/my-tv 2024-02-04 23:52:44 +08:00
LeGend-wLw 6061187bc8 Merge branch 'lizongying:main' into main 2024-02-04 23:52:04 +08:00
LeGend-wLw cd8b37e63c Fix:优化从服务器获取逻辑 2024-02-04 23:49:19 +08:00
LeGend-wLw aa3ba1d41f Fix:移除ChannelUtils有关代码 2024-02-04 23:48:58 +08:00
LeGend-wLw c778ac07ba Add:添加本地文件版本,服务器版本地址 2024-02-04 23:48:39 +08:00
LeGend-wLw 700c8b4c04 Fix:移除TVList的初始化 2024-02-04 23:48:03 +08:00
LeGend-wLw 825a872a09 Fix:移除TVList的初始化 2024-02-04 23:47:54 +08:00
LeGend-wLw 54b43664ec add:注释 2024-02-04 23:47:10 +08:00
LeGend-wLw a93cfb455c 在应用初始化时初始化TVlist 2024-02-04 23:46:57 +08:00
LeGend-wLw 8535689e95 完成专门的ChannelUtils 2024-02-04 23:41:51 +08:00
李宗英 1714633cb7 Merge pull request #205 from LeGend-wLw/main
移除硬编码,更改为json格式,并支持从服务器上动态更新channels。
2024-02-04 22:46:08 +08:00
LeGend-wLw f7b024f6da 防止多次初始化 2024-02-04 16:42:56 +08:00
LeGend-wLw dd8df63b01 添加初始化TVList行为 2024-02-04 16:42:38 +08:00
LeGend-wLw a4c95efd71 添加从服务器上获取channel.json逻辑,但未启用 2024-02-04 16:37:59 +08:00
LeGend-wLw 9f32fa3673 移除MustToken和needToken,移动到TV类中 2024-02-04 16:37:20 +08:00
LeGend-wLw b3d0093ab0 修改为从json文件中读取channel信息 2024-02-04 16:36:34 +08:00
LeGend-wLw 5798d3a5fe 更改TV类中成员,使能和json对应 2024-02-04 16:36:05 +08:00
LeGend-wLw 4eb90de79d 当前转换成json的channel信息 2024-02-04 16:35:38 +08:00
LeGend-wLw b8232971e2 服务器地址,未更新 2024-02-04 16:35:20 +08:00
LeGend-wLw bc3681b0a6 将从tvModel中获取needToken和MustToken改成从TV类中获取needToken和MustToken 2024-02-04 16:34:59 +08:00
Li ZongYing 088808c3fd fix retry bug 2024-02-03 20:07:08 +08:00
Li ZongYing 73239a6677 replay 2024-02-01 17:04:02 +08:00
Li ZongYing cc2514a81a performance optimization 2024-01-28 16:00:33 +08:00
Li ZongYing bcf9bab25f add channel 2024-01-27 19:52:36 +08:00
Li ZongYing f7eebb3f7a channel allow one num 2024-01-25 12:21:16 +08:00
Li ZongYing 0f4a33a3fc change channel order 2024-01-25 11:37:06 +08:00
Li ZongYing c17d62c698 rename to setting 2024-01-19 13:49:43 +08:00
Li ZongYing 33d3da97e5 fix 13 2024-01-18 22:19:04 +08:00
Li ZongYing ba95ce5374 number selection 2024-01-18 21:42:06 +08:00
Li ZongYing e1ee909071 optimize memu hiding 2024-01-17 10:19:50 +08:00
Li ZongYing 1fd0951f92 channel reversal 2024-01-16 19:04:43 +08:00
Li ZongYing 42b072f5ec support KEYCODE_CHANNEL_UP & KEYCODE_CHANNEL_DOWN 2024-01-14 12:09:12 +08:00
Li ZongYing a23b1e11ce fix the menu did not appear 2024-01-14 10:15:45 +08:00
Li ZongYing 1ac6d87c3c clean log 2024-01-12 10:13:29 +08:00
Li ZongYing b075a7db81 trust all certs 2024-01-11 19:33:31 +08:00
83 changed files with 1061 additions and 2687 deletions
-8
View File
@@ -1,13 +1,5 @@
## 更新日誌 ## 更新日誌
### v2.1.2
*
### v2.1.1
* 平衡聲音
### v2.1.0 ### v2.1.0
* 恢復播放 * 恢復播放
+10 -1
View File
@@ -26,7 +26,7 @@
## TODO ## TODO
* 音量不同 * 音量不同
* 大湾区卫视、广东4k超高清、广东珠江、三沙卫视、翡翠 * 大湾区卫视、广东4k超高清、广东珠江、三沙卫视
* CHC高清三个电影频道 * CHC高清三个电影频道
* 地方频道 * 地方频道
* 收藏夹 * 收藏夹
@@ -35,9 +35,18 @@
* 亮度调节 * 亮度调节
* 音量调节 * 音量调节
* 軟解 * 軟解
* 自動更新
無法自啟的設備:
斐讯N1盒子,[Phicomm] Phicomm p230 (Android 7.1.2)
閃退:
中国移动盒子(新魔百和M302A) 4.4.2
## 版权说明 ## 版权说明
[LICENSE](./LICENSE)
本项目仅供学习研究,禁止用于商业用途,请于下载二十四小时内删除。 本项目仅供学习研究,禁止用于商业用途,请于下载二十四小时内删除。
本项目可能随时终止,请大家谨慎使用,建议使用官方渠道进行观看。 本项目可能随时终止,请大家谨慎使用,建议使用官方渠道进行观看。
-1
View File
@@ -4,4 +4,3 @@
/CMakeFiles/ /CMakeFiles/
/Makefile /Makefile
/src/main/cpp/native.c /src/main/cpp/native.c
/src/main/cpp/aes.c
+1 -3
View File
@@ -21,9 +21,7 @@ if (IS_SO_BUILD)
SHARED SHARED
# Provides a relative path to your source file(s). # Provides a relative path to your source file(s).
src/main/cpp/native.c src/main/cpp/native.c)
src/main/cpp/aes.c
)
# 设置编译输出路径 # 设置编译输出路径
set_target_properties( set_target_properties(
+16 -55
View File
@@ -13,7 +13,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.lizongying.mytv" applicationId "com.lizongying.mytv"
minSdk 17 minSdk 21
targetSdk 33 targetSdk 33
versionCode VersionCode() versionCode VersionCode()
versionName VersionName() versionName VersionName()
@@ -26,7 +26,6 @@ android {
cmake { cmake {
arguments "-DIS_SO_BUILD=${project.hasProperty('IS_SO_BUILD') ? project.IS_SO_BUILD : true}" arguments "-DIS_SO_BUILD=${project.hasProperty('IS_SO_BUILD') ? project.IS_SO_BUILD : true}"
// abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"
} }
} }
@@ -78,9 +77,10 @@ static def VersionCode() {
def replace = [v: "", ".": " ", "-": " "] def replace = [v: "", ".": " ", "-": " "]
def arr = (process.text.trim().replace(replace) + " 0").split(" ") def arr = (process.text.trim().replace(replace) + " 0").split(" ")
def versionCode = arr[0].toInteger() * 16777216 + arr[1].toInteger() * 65536 + arr[2].toInteger() * 256 + arr[3].toInteger() def versionCode = arr[0].toInteger() * 16777216 + arr[1].toInteger() * 65536 + arr[2].toInteger() * 256 + arr[3].toInteger()
println("VersionCode $versionCode")
return versionCode return versionCode
} catch (ignored) { } catch (ignored) {
return 1 return 0
} }
} }
@@ -88,81 +88,42 @@ static def VersionName() {
try { try {
def process = "git describe --tags --always".execute() def process = "git describe --tags --always".execute()
process.waitFor() process.waitFor()
def versionName = process.text.trim() - "v" return process.text.trim() - "v"
if (versionName.isEmpty()) {
versionName = "1.0.0"
}
return versionName
} catch (ignored) { } catch (ignored) {
return "1.0.0" return "1.0.0"
} }
} }
def gson_version = "2.10.1"
def failureaccess_version = "1.0.2"
def guava_version = "33.2.1-android"
dependencies { dependencies {
def media3_version = "1.3.1"
// minSdk17
def media3_version = "1.2.1"
implementation "androidx.media3:media3-ui:$media3_version" implementation "androidx.media3:media3-ui:$media3_version"
// For media playback using ExoPlayer // For media playback using ExoPlayer
implementation("com.google.guava:failureaccess:$failureaccess_version") implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation("com.google.guava:guava:$guava_version")
implementation("androidx.media3:media3-common:$media3_version") {
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.guava', module: 'failureaccess'
}
implementation("androidx.media3:media3-exoplayer:$media3_version")
// For HLS playback support with ExoPlayer // For HLS playback support with ExoPlayer
implementation "androidx.media3:media3-exoplayer-hls:$media3_version" implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
// 21:2.9.0 17:2.6.4 // 21:2.11.0 17:2.6.4
def retrofit2_version = "2.6.4" def retrofit2_version = "2.11.0"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1' implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
// Gson 2.10.1 and older: API level 19 implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
implementation("com.google.code.gson:gson:$gson_version")
implementation("com.squareup.retrofit2:converter-gson:$retrofit2_version") {
exclude group: 'com.google.code.gson', module: 'gson'
}
implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version" implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version"
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
implementation 'androidx.leanback:leanback:1.2.0-alpha04'
implementation 'com.github.bumptech.glide:glide:4.11.0' implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation("androidx.work:work-runtime-ktx:2.9.0") implementation 'androidx.core:core-ktx:1.13.0'
implementation 'androidx.core:core-ktx:1.11.0-beta02' implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.leanback:leanback:1.2.0-alpha02'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation 'com.google.android.exoplayer:exoplayer-ui:2.13.3'
} implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
configurations.configureEach {
resolutionStrategy {
force("com.google.code.gson:gson:$gson_version")
force "com.google.guava:failureaccess:$failureaccess_version"
force "com.google.guava:guava:$guava_version"
eachDependency { details ->
if (details.requested.group == 'com.google.code.gson') {
details.useVersion "$gson_version"
}
if (details.requested.group == 'com.google.guava' && details.requested.name == 'failureaccess') {
details.useVersion "$failureaccess_version"
}
if (details.requested.group == 'com.google.guava' && details.requested.name == 'guava') {
details.useVersion "$guava_version"
}
}
}
} }
+1 -7
View File
@@ -18,10 +18,4 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class com.google.gson.** { *; }
-dontwarn com.google.gson.**
-keep class com.google.guava.** { *; }
-dontwarn com.google.guava.**
+1 -17
View File
@@ -10,13 +10,11 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application <application
android:name=".MyTVApplication" android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:banner="@drawable/banner" android:banner="@drawable/banner"
android:icon="@drawable/logo" android:icon="@drawable/logo"
@@ -38,9 +36,6 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<meta-data
android:name="android.windowLayoutInDisplayCutoutMode"
android:value="shortEdges" />
</activity> </activity>
<receiver <receiver
@@ -63,16 +58,5 @@
android:name=".InitializerProvider" android:name=".InitializerProvider"
android:authorities="${applicationId}.InitializerProvider" android:authorities="${applicationId}.InitializerProvider"
android:exported="false" /> android:exported="false" />
<meta-data android:name="android.max_aspect" android:value="2.5" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>
Binary file not shown.
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
#ifndef AES_H
#define AES_H
#include <stddef.h>
void init();
void encode(const char *in, size_t size, char *out);
#endif
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,217 +0,0 @@
package com.lizongying.mytv
import android.util.Log
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.animation.ScaleAnimation
import androidx.core.view.marginTop
import androidx.core.view.setPadding
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.CardBinding
import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlin.math.abs
class CardAdapter(
private val recyclerView: RecyclerView,
private val mainFragment: MainFragment,
private var tvListViewModel: TVListViewModel,
) :
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
private var listener: ItemListener? = null
private var focused: View? = null
var focusable = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = CardBinding.inflate(inflater, parent, false)
val application = mainFragment.requireActivity().applicationContext as MyTVApplication
binding.card.layoutParams.width = application.px2Px(binding.card.layoutParams.width)
binding.card.layoutParams.height = application.px2Px(binding.card.layoutParams.height)
binding.icon.layoutParams.height = application.px2Px(binding.icon.layoutParams.height)
binding.icon.setPadding(application.px2Px(binding.icon.paddingTop))
binding.main.layoutParams.height = application.px2Px(binding.main.layoutParams.height)
binding.main.setPadding(application.px2Px(binding.main.paddingTop))
binding.title.textSize = application.px2PxFont(binding.title.textSize)
binding.desc.textSize = application.px2PxFont(binding.desc.textSize)
val layoutParams = binding.desc.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.desc.marginTop)
binding.desc.layoutParams = layoutParams
val cardView = binding.root
cardView.isClickable = true
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView, binding)
}
fun clear() {
focused?.clearFocus()
recyclerView.invalidate()
}
private fun startScaleAnimation(view: View) {
val scaleAnimation = ScaleAnimation(
0.9f, 1.0f,
0.9f, 1.0f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f
)
scaleAnimation.duration = 200
scaleAnimation.fillAfter = false
view.startAnimation(scaleAnimation)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val item = tvListViewModel.getTVViewModel(position)
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view
cardView.tag = tvViewModel
val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus ->
if (hasFocus) {
listener?.onItemHasFocus(item)
focused = cardView
if (focusable) {
startScaleAnimation(cardView)
viewHolder.focus(true)
}
} else {
viewHolder.focus(false)
}
}
cardView.onFocusChangeListener = onFocusChangeListener
cardView.setOnClickListener { _ ->
listener?.onItemClicked(item)
}
var downX = 0f
var downY = 0f
val touchSlop = ViewConfiguration.get(cardView.context).scaledTouchSlop
cardView.setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
true
}
MotionEvent.ACTION_UP -> {
val upX = event.x
val upY = event.y
val deltaX = abs(upX - downX)
val deltaY = abs(upY - downY)
if (deltaX < touchSlop && deltaY < touchSlop) {
// 如果位移量小于阈值,则执行点击操作
cardView.performClick()
}
true
}
else -> false
}
}
cardView.setOnKeyListener { _, keyCode, event: KeyEvent? ->
if (event?.action == KeyEvent.ACTION_DOWN) {
return@setOnKeyListener listener?.onKey(keyCode) ?: false
}
false
}
viewHolder.binding.title.text = tvViewModel.getTV().title
viewHolder.binding.icon.let {
Glide.with(viewHolder.view.context)
.load(tvViewModel.getTV().logo)
.centerInside()
.into(it)
}
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
viewHolder.binding.desc.text = epg.last().title
} else {
viewHolder.binding.desc.text = ""
}
}
override fun getItemCount() = tvListViewModel.size()
class ViewHolder(itemView: View, var binding: CardBinding) : RecyclerView.ViewHolder(itemView) {
val view = itemView
fun focus(hasFocus: Boolean) {
if (hasFocus) {
binding.main.setBackgroundResource(R.drawable.rounded_light_bottom)
} else {
binding.main.setBackgroundResource(R.drawable.rounded_dark_bottom)
}
}
}
fun updateEPG() {
for (i in 0 until recyclerView.childCount) {
val childView = recyclerView.getChildAt(i)
val viewHolder = recyclerView.getChildViewHolder(childView) as ViewHolder
if (viewHolder.view.tag != null && viewHolder.view.tag is TVViewModel) {
val tvViewModel = viewHolder.view.tag as TVViewModel
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
val title = epg.last().title
if (viewHolder.binding.desc.text != title) {
viewHolder.binding.desc.text = title
Log.i(TAG, "updateEPG $title")
}
} else {
if (viewHolder.binding.desc.text != "") {
viewHolder.binding.desc.text = ""
Log.i(TAG, "updateEPG")
}
}
}
}
}
fun toPosition(position: Int) {
recyclerView.post {
recyclerView.scrollToPosition(position)
recyclerView.getChildAt(position)?.isSelected
recyclerView.getChildAt(position)?.requestFocus()
}
}
interface ItemListener {
fun onItemHasFocus(tvViewModel: TVViewModel)
fun onItemClicked(tvViewModel: TVViewModel)
fun onKey(keyCode: Int): Boolean
}
fun setItemListener(listener: ItemListener) {
this.listener = listener
}
companion object {
private const val TAG = "CardAdapter"
}
}
@@ -0,0 +1,76 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Color
import android.view.ContextThemeWrapper
import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter
import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel
class CardPresenter(
private val context: Context,
) : Presenter() {
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {}
cardView.isFocusable = true
cardView.isFocusableInTouchMode = true
return ViewHolder(cardView)
}
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any?) {
val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView
cardView.titleText = tvViewModel.getTV().title
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value
cardView.mainImageView?.let {
Glide.with(viewHolder.view.context)
.load(tvViewModel.getTV().logo)
.centerInside()
.into(it)
}
cardView.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
// cardView.setOnFocusChangeListener { v, hasFocus ->
// run {
// if (hasFocus) {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.focus))
// }
// } else {
// if (v != null) {
// (v as ImageCardView).setInfoAreaBackgroundColor(context.resources.getColor(R.color.ic_launcher_background))
// }
// }
// }
// }
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) {
cardView.contentText = epg.last().title
} else {
cardView.contentText = ""
}
}
override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView
cardView.mainImage = null
}
companion object {
private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 300
private const val CARD_HEIGHT = 101
}
}
@@ -1,13 +1,11 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ChannelBinding import com.lizongying.mytv.databinding.ChannelBinding
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
@@ -17,7 +15,7 @@ class ChannelFragment : Fragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private val handler = Handler() private val handler = Handler()
private val delay: Long = 5000 private val delay: Long = 3000
private var channel = 0 private var channel = 0
private var channelCount = 0 private var channelCount = 0
@@ -28,30 +26,45 @@ class ChannelFragment : Fragment() {
_binding = ChannelBinding.inflate(inflater, container, false) _binding = ChannelBinding.inflate(inflater, container, false)
_binding!!.root.visibility = View.GONE _binding!!.root.visibility = View.GONE
val application = requireActivity().applicationContext as MyTVApplication val activity = requireActivity()
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
binding.channel.layoutParams.width = application.px2Px(binding.channel.layoutParams.width) displayMetrics.density
binding.channel.layoutParams.height = application.px2Px(binding.channel.layoutParams.height)
val layoutParams = binding.channel.layoutParams as ViewGroup.MarginLayoutParams var screenWidth = displayMetrics.widthPixels
layoutParams.topMargin = application.px2Px(binding.channel.marginTop) var screenHeight = displayMetrics.heightPixels
layoutParams.marginEnd = application.px2Px(binding.channel.marginEnd) if (screenHeight > screenWidth) {
binding.channel.layoutParams = layoutParams screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
}
binding.content.textSize = application.px2PxFont(binding.content.textSize) val ratio = 16f / 9f
binding.time.textSize = application.px2PxFont(binding.time.textSize)
binding.main.layoutParams.width = application.shouldWidthPx() if (screenWidth / screenHeight > ratio) {
binding.main.layoutParams.height = application.shouldHeightPx() val x = ((screenWidth - screenHeight * ratio) / 2).toInt()
val originalLayoutParams =
binding.channelFragment.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.rightMargin += x
binding.channelFragment.layoutParams = originalLayoutParams
}
(activity as MainActivity).fragmentReady(TAG) if (screenWidth / screenHeight < ratio) {
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
val originalLayoutParams =
binding.channelFragment.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
binding.channelFragment.layoutParams = originalLayoutParams
}
(activity as MainActivity).fragmentReady("ChannelFragment")
return binding.root return binding.root
} }
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
handler.removeCallbacks(hideRunnable) handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable) handler.removeCallbacks(playRunnable)
binding.content.text = (tvViewModel.getTV().id.plus(1)).toString() binding.channelContent.text = (tvViewModel.getTV().id.plus(1)).toString()
view?.visibility = View.VISIBLE view?.visibility = View.VISIBLE
handler.postDelayed(hideRunnable, delay) handler.postDelayed(hideRunnable, delay)
} }
@@ -61,11 +74,13 @@ class ChannelFragment : Fragment() {
return return
} }
channelCount++ channelCount++
Log.i(TAG, "channelCount ${channelCount}")
this.channel = "${this.channel}$channel".toInt() this.channel = "${this.channel}$channel".toInt()
Log.i(TAG, "this.channel ${this.channel}")
handler.removeCallbacks(hideRunnable) handler.removeCallbacks(hideRunnable)
handler.removeCallbacks(playRunnable) handler.removeCallbacks(playRunnable)
if (channelCount < 2) { if (channelCount < 2) {
binding.content.text = "${this.channel}" binding.channelContent.text = "${this.channel}"
view?.visibility = View.VISIBLE view?.visibility = View.VISIBLE
handler.postDelayed(playRunnable, delay) handler.postDelayed(playRunnable, delay)
} else { } else {
@@ -87,18 +102,20 @@ class ChannelFragment : Fragment() {
} }
private val hideRunnable = Runnable { private val hideRunnable = Runnable {
binding.content.text = "" binding.channelContent.text = ""
view?.visibility = View.GONE view?.visibility = View.GONE
channel = 0 channel = 0
channelCount = 0 channelCount = 0
Log.i(TAG, "hideRunnable")
} }
private val playRunnable = Runnable { private val playRunnable = Runnable {
(activity as MainActivity).play(channel - 1) (activity as MainActivity).play(channel - 1)
binding.content.text = "" binding.channelContent.text = ""
view?.visibility = View.GONE view?.visibility = View.GONE
channel = 0 channel = 0
channelCount = 0 channelCount = 0
Log.i(TAG, "playRunnable")
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -3,61 +3,28 @@ package com.lizongying.mytv
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Dialog import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.databinding.ConfirmationBinding
class ConfirmationFragment( class ConfirmationFragment(
private val listener: ConfirmationListener, private val listener: ConfirmationListener,
private val message: String, private val message: String
private val update: Boolean
) : DialogFragment() { ) : DialogFragment() {
//
// private var _binding: ConfirmationBinding? = null
// private val binding get() = _binding!!
//
// override fun onCreateView(
// inflater: LayoutInflater, container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View {
// _binding = ConfirmationBinding.inflate(inflater, container, false)
// return binding.root
// }
//
// fun updateProgress(progress: Int) {
// binding.progressBar.progress = progress
// }
//
// override fun onDestroyView() {
// super.onDestroyView()
// _binding = null
// }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let { return activity?.let {
val builder = AlertDialog.Builder(it) val builder = AlertDialog.Builder(it)
builder.setTitle(message) builder.setTitle("确定更新吗?")
if (update) { .setMessage(message)
builder.setMessage("确定更新吗?") .setPositiveButton(
.setPositiveButton( "确定"
"确定" ) { _, _ ->
) { _, _ -> listener.onConfirm()
listener.onConfirm() }
} .setNegativeButton(
.setNegativeButton( "取消"
"取消" ) { _, _ ->
) { _, _ -> listener.onCancel()
listener.onCancel() }
}
} else {
builder.setMessage("")
.setNegativeButton(
"好的"
) { _, _ ->
}
}
builder.create() builder.create()
} ?: throw IllegalStateException("Activity cannot be null") } ?: throw IllegalStateException("Activity cannot be null")
} }
@@ -1,4 +1,4 @@
package com.lizongying.mytv.api package com.lizongying.mytv
import android.content.Context import android.content.Context
@@ -7,8 +7,6 @@ class Encryptor {
external fun encrypt(t: String, e: String, r: String, n: String, i: String): String external fun encrypt(t: String, e: String, r: String, n: String, i: String): String
external fun encrypt2(t: String): String
external fun hash(data: ByteArray): ByteArray? external fun hash(data: ByteArray): ByteArray?
external fun hash2(data: ByteArray): ByteArray? external fun hash2(data: ByteArray): ByteArray?
@@ -1,49 +1,28 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import androidx.core.content.ContextCompat
import androidx.core.view.marginStart import androidx.leanback.app.ErrorSupportFragment
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ErrorBinding
class ErrorFragment : Fragment() { class ErrorFragment : ErrorSupportFragment() {
private var _binding: ErrorBinding? = null
private val binding get() = _binding!!
override fun onCreateView( internal fun setErrorContent(message: String) {
inflater: LayoutInflater, container: ViewGroup?, imageDrawable =
savedInstanceState: Bundle? ContextCompat.getDrawable(context!!, androidx.leanback.R.drawable.lb_ic_sad_cloud)
): View { this.message = message
_binding = ErrorBinding.inflate(inflater, container, false) setDefaultBackground(TRANSLUCENT)
backgroundDrawable = ContextCompat.getDrawable(
context!!,
R.color.black
)
val application = requireActivity().applicationContext as MyTVApplication buttonText = resources.getString(R.string.dismiss_error)
buttonClickListener = View.OnClickListener {
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width) fragmentManager!!.beginTransaction().remove(this@ErrorFragment).commit()
binding.logo.layoutParams.height = application.px2Px(binding.logo.layoutParams.height) }
val layoutParams = binding.msg.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.px2Px(binding.msg.marginTop)
binding.msg.layoutParams = layoutParams
binding.msg.textSize = application.px2PxFont(binding.msg.textSize)
(activity as MainActivity).fragmentReady(TAG)
return binding.root
}
fun show(msg: String) {
binding.msg.text = msg
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
companion object { companion object {
private const val TAG = "ErrorFragment" private const val TRANSLUCENT = false
} }
} }
@@ -1,16 +1,11 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.marginBottom
import androidx.core.view.marginStart
import androidx.core.view.marginTop
import androidx.core.view.setPadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.InfoBinding import com.lizongying.mytv.databinding.InfoBinding
@@ -21,64 +16,31 @@ class InfoFragment : Fragment() {
private val binding get() = _binding!! private val binding get() = _binding!!
private val handler = Handler() private val handler = Handler()
private val delay: Long = 5000 private val delay: Long = 3000
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = InfoBinding.inflate(inflater, container, false) _binding = InfoBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTVApplication
binding.info.layoutParams.width = application.px2Px(binding.info.layoutParams.width)
binding.info.layoutParams.height = application.px2Px(binding.info.layoutParams.height)
val layoutParams = binding.info.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.bottomMargin = application.px2Px(binding.info.marginBottom)
binding.info.layoutParams = layoutParams
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width)
binding.logo.setPadding(application.px2Px(binding.logo.paddingTop))
binding.main.layoutParams.width = application.px2Px(binding.main.layoutParams.width)
binding.main.setPadding(application.px2Px(binding.main.paddingTop))
val layoutParamsMain = binding.main.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsMain.marginStart = application.px2Px(binding.main.marginStart)
binding.main.layoutParams = layoutParamsMain
val layoutParamsDesc = binding.desc.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsDesc.topMargin = application.px2Px(binding.desc.marginTop)
binding.desc.layoutParams = layoutParamsDesc
binding.title.textSize = application.px2PxFont(binding.title.textSize)
binding.desc.textSize = application.px2PxFont(binding.desc.textSize)
binding.container.layoutParams.width = application.shouldWidthPx()
binding.container.layoutParams.height = application.shouldHeightPx()
_binding!!.root.visibility = View.GONE _binding!!.root.visibility = View.GONE
(activity as MainActivity).fragmentReady("InfoFragment")
(activity as MainActivity).fragmentReady(TAG)
return binding.root return binding.root
} }
fun show(tvViewModel: TVViewModel) { fun show(tvViewModel: TVViewModel) {
if (_binding == null) { binding.textView.text = tvViewModel.getTV().title
return
}
binding.title.text = tvViewModel.getTV().title
Glide.with(this) Glide.with(this)
.load(tvViewModel.getTV().logo) .load(tvViewModel.getTV().logo)
.into(binding.logo) .into(binding.infoLogo)
Log.i(TAG, "${tvViewModel.getTV().title} ${tvViewModel.epg.value}") Log.i(TAG, "${tvViewModel.getTV().title} ${tvViewModel.epg.value}")
val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() } val epg = tvViewModel.epg.value?.filter { it.beginTime < Utils.getDateTimestamp() }
if (!epg.isNullOrEmpty()) { if (!epg.isNullOrEmpty()) {
binding.desc.text = epg.last().title binding.infoDesc.text = epg.last().title
} else { } else {
binding.desc.text = "" binding.infoDesc.text = ""
} }
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(removeRunnable)
@@ -1,29 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class ItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
private var application: MyTVApplication
init {
application = context.applicationContext as MyTVApplication
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val space = application.dp2Px(5)
outRect.top = space
outRect.bottom = space
outRect.left = space
outRect.right = space
}
}
@@ -1,37 +0,0 @@
package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.LoadingBinding
class LoadingFragment : Fragment() {
private var _binding: LoadingBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = LoadingBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTVApplication
binding.bar.layoutParams.width = application.px2Px(binding.bar.layoutParams.width)
binding.bar.layoutParams.height = application.px2Px(binding.bar.layoutParams.height)
(activity as MainActivity).fragmentReady(TAG)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
private const val TAG = "LoadingFragment"
}
}
@@ -1,5 +1,8 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@@ -9,35 +12,28 @@ import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
import android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPreferenceChangeListener { class MainActivity : FragmentActivity(), Request.RequestListener {
private var ready = 0 private var ready = 0
private val playerFragment = PlayerFragment() private val playerFragment = PlayerFragment()
private val errorFragment = ErrorFragment()
private val loadingFragment = LoadingFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private val channelFragment = ChannelFragment() private val channelFragment = ChannelFragment()
private var timeFragment = TimeFragment() private var timeFragment = TimeFragment()
private val settingFragment = SettingFragment() private val settingFragment = SettingFragment()
private val errorFragment = ErrorFragment()
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
@@ -45,98 +41,67 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
private val handler = Handler() private val handler = Handler()
private val delayHideMain: Long = 10000 private val delayHideMain: Long = 10000
private val delayHideSetting: Long = 15000 private val delayHideSetting: Long = 10000
init { init {
Utils.setRequestListener(this)
}
fun syncTime() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val utilsJob = async(start = CoroutineStart.LAZY) { Utils.init() } val utilsJob = async(start = CoroutineStart.LAZY) { Utils.init() }
utilsJob.start() utilsJob.start()
// utilsJob.await()
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val lp = window.attributes
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.setAttributes(lp)
}
window.decorView.apply {
systemUiVisibility =
SYSTEM_UI_FLAG_FULLSCREEN or
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
SYSTEM_UI_FLAG_HIDE_NAVIGATION or
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE
}
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.decorView.systemUiVisibility =
SYSTEM_UI_FLAG_FULLSCREEN or
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or SYSTEM_UI_FLAG_HIDE_NAVIGATION or
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
// window.statusBarColor = Color.TRANSPARENT
// window.navigationBarColor = Color.TRANSPARENT
// }
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
Request.setRequestListener(this) Request.setRequestListener(this)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
window.decorView.systemUiVisibility = SYSTEM_UI_FLAG_HIDE_NAVIGATION
if (savedInstanceState == null) { if (savedInstanceState == null) {
val transaction = supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment) .add(R.id.main_browse_fragment, playerFragment)
.add(R.id.main_browse_fragment, errorFragment)
// .add(R.id.main_browse_fragment, loadingFragment)
.add(R.id.main_browse_fragment, timeFragment) .add(R.id.main_browse_fragment, timeFragment)
.add(R.id.main_browse_fragment, infoFragment) .add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, channelFragment) .add(R.id.main_browse_fragment, channelFragment)
.add(R.id.main_browse_fragment, mainFragment) .add(R.id.main_browse_fragment, mainFragment)
.hide(mainFragment) .hide(mainFragment)
.hide(errorFragment) .commit()
if (!SP.time) {
transaction.hide(timeFragment)
} else {
transaction.show(timeFragment)
}
transaction.commitNow()
} }
gestureDetector = GestureDetector(this, GestureListener()) gestureDetector = GestureDetector(this, GestureListener())
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { errorFragment.buttonClickListener = View.OnClickListener {
// val connectivityManager = supportFragmentManager.beginTransaction()
// getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager .remove(errorFragment)
// connectivityManager.registerDefaultNetworkCallback(object : .commit()
// ConnectivityManager.NetworkCallback() { }
// override fun onAvailable(network: Network) {
// super.onAvailable(network) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// Log.i(TAG, "net ${Build.VERSION.SDK_INT}") val connectivityManager =
// if (this@MainActivity.isNetworkConnected) { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
// Log.i(TAG, "net isNetworkConnected") connectivityManager.registerDefaultNetworkCallback(object :
// ready++ ConnectivityManager.NetworkCallback() {
// } override fun onAvailable(network: Network) {
// } super.onAvailable(network)
// }) Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
// } else { if (this@MainActivity.isNetworkConnected) {
// Log.i(TAG, "net ${Build.VERSION.SDK_INT}") Log.i(TAG, "net isNetworkConnected")
// ready++ ready++
// } }
}
})
} else {
Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
ready++
}
SP.setOnSharedPreferenceChangeListener(this)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -146,52 +111,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
} }
fun showErrorFragment(msg: String) {
errorFragment.show(msg)
if (errorFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.show(errorFragment)
.commit()
}
fun hideErrorFragment() {
errorFragment.show("")
if (!errorFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.hide(errorFragment)
.commit()
}
fun showLoadingFragment() {
showFragment(loadingFragment)
}
fun hideLoadingFragment() {
hideFragment(loadingFragment)
}
fun showPlayerFragment() {
if (playerFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.show(playerFragment)
.commit()
}
fun hidePlayerFragment() {
if (!playerFragment.isVisible) {
return
}
supportFragmentManager.beginTransaction()
.hide(playerFragment)
.commit()
}
private fun showChannel(channel: String) { private fun showChannel(channel: String) {
if (!mainFragment.isHidden) { if (!mainFragment.isHidden) {
return return
@@ -208,6 +127,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
fun play(tvViewModel: TVViewModel) { fun play(tvViewModel: TVViewModel) {
playerFragment.play(tvViewModel) playerFragment.play(tvViewModel)
mainFragment.view?.requestFocus()
} }
fun play(itemPosition: Int) { fun play(itemPosition: Int) {
@@ -222,11 +142,18 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
mainFragment.next() mainFragment.next()
} }
private fun prevSource() {
// mainFragment.prevSource()
}
private fun nextSource() {
// mainFragment.nextSource()
}
fun switchMainFragment() { fun switchMainFragment() {
val transaction = supportFragmentManager.beginTransaction() val transaction = supportFragmentManager.beginTransaction()
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
mainFragment.setPosition()
transaction.show(mainFragment) transaction.show(mainFragment)
mainActive() mainActive()
} else { } else {
@@ -244,6 +171,12 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
fun settingDelayHide() { fun settingDelayHide() {
handler.removeCallbacks(hideSetting) handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting) handler.postDelayed(hideSetting, delayHideSetting)
showTime()
}
fun settingHideNow() {
handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, 0)
} }
fun settingNeverHide() { fun settingNeverHide() {
@@ -271,37 +204,27 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
fun fragmentReady(tag: String) { fun fragmentReady(tag: String) {
ready++ ready++
Log.i(TAG, "ready $tag $ready ") Log.i(TAG, "ready $tag $ready ")
if (ready == 7) { if (ready == 6) {
mainFragment.fragmentReady() mainFragment.fragmentReady()
showTime()
} }
} }
private fun showTime() { private fun showTime() {
Log.i(TAG, "showTime ${SP.time}")
if (SP.time) { if (SP.time) {
showFragment(timeFragment) timeFragment.show()
} else { } else {
hideFragment(timeFragment) timeFragment.hide()
} }
} }
private fun showFragment(fragment: Fragment) { fun isPlaying() {
if (!fragment.isHidden) { if (errorFragment.isVisible) {
return supportFragmentManager.beginTransaction()
.remove(errorFragment)
.commit()
} }
supportFragmentManager.beginTransaction()
.show(fragment)
.commitNow()
}
private fun hideFragment(fragment: Fragment) {
if (fragment.isHidden) {
return
}
supportFragmentManager.beginTransaction()
.hide(fragment)
.commitNow()
} }
override fun onTouchEvent(event: MotionEvent?): Boolean { override fun onTouchEvent(event: MotionEvent?): Boolean {
@@ -314,7 +237,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean { override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
switchMainFragment() switchMainFragment()
return true return true
} }
@@ -362,12 +284,14 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
return return
} }
if (settingFragment.isVisible) { Log.i(TAG, "settingFragment ${settingFragment.isVisible}")
return if (!settingFragment.isVisible) {
settingFragment.show(supportFragmentManager, "setting")
settingDelayHide()
} else {
handler.removeCallbacks(hideSetting)
settingFragment.dismiss()
} }
settingFragment.show(supportFragmentManager, TAG)
settingDelayHide()
} }
private val hideSetting = Runnable { private val hideSetting = Runnable {
@@ -376,10 +300,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
} }
private fun hideSettingFragment() {
handler.postDelayed(hideSetting, 0)
}
private fun channelUp() { private fun channelUp() {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
if (SP.channelReversal) { if (SP.channelReversal) {
@@ -419,11 +339,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
return return
} }
if (settingFragment.isVisible) {
hideSettingFragment()
return
}
if (doubleBackToExitPressedOnce) { if (doubleBackToExitPressedOnce) {
super.onBackPressed() super.onBackPressed()
return return
@@ -437,14 +352,8 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
}, 2000) }, 2000)
} }
private fun active() { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (settingFragment.isVisible) { Log.i(TAG, "keyCode $keyCode, event $event")
settingDelayHide()
}
}
fun onKey(keyCode: Int): Boolean {
Log.i(TAG, "keyCode $keyCode")
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_0 -> { KeyEvent.KEYCODE_0 -> {
showChannel("0") showChannel("0")
@@ -512,7 +421,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
KeyEvent.KEYCODE_UNKNOWN -> { KeyEvent.KEYCODE_UNKNOWN -> {
// showSetting() showSetting()
return true return true
} }
@@ -532,7 +441,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
KeyEvent.KEYCODE_ENTER -> { KeyEvent.KEYCODE_ENTER -> {
active()
switchMainFragment() switchMainFragment()
} }
@@ -541,7 +449,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
active()
channelUp() channelUp()
} }
@@ -550,7 +457,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> {
active()
channelDown() channelDown()
} }
@@ -559,28 +465,19 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
active() if (!mainFragment.isVisible && !settingFragment.isVisible) {
if (mainFragment.isHidden && !settingFragment.isVisible) {
switchMainFragment() switchMainFragment()
return true return true
} }
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
active() if (!mainFragment.isVisible && !settingFragment.isVisible) {
if (mainFragment.isHidden && !settingFragment.isVisible) {
showSetting() showSetting()
return true return true
} }
} }
} }
return false
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (onKey(keyCode)) {
return true
}
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
@@ -588,20 +485,13 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
private fun getAppSignature() = this.appSignature private fun getAppSignature() = this.appSignature
override fun onStart() { override fun onStart() {
Log.i(TAG, "onStart MainActivity") Log.i(TAG, "onStart")
syncTime()
super.onStart() super.onStart()
} }
override fun onResume() { override fun onResume() {
Log.i(TAG, "onResume") Log.i(TAG, "onResume")
super.onResume() super.onResume()
}
override fun onResumeFragments() {
super.onResumeFragments()
mainFragment.changeMenu()
if (!mainFragment.isHidden) { if (!mainFragment.isHidden) {
handler.postDelayed(hideMain, delayHideMain) handler.postDelayed(hideMain, delayHideMain)
} }
@@ -619,18 +509,16 @@ class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPrefer
} }
override fun onRequestFinished(message: String?) { override fun onRequestFinished(message: String?) {
fragmentReady("request") if (message != null && !errorFragment.isVisible) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, errorFragment)
.commitNow()
errorFragment.setErrorContent(message)
}
fragmentReady("Request")
} }
private companion object { private companion object {
const val TAG = "MainActivity" const val TAG = "MainActivity"
} }
override fun onSharedPreferenceChanged(key: String) {
Log.i(TAG, "$key changed")
when (key) {
SP.KEY_GRID -> mainFragment.changeMenu()
SP.KEY_TIME -> showTime()
}
}
} }
@@ -2,83 +2,71 @@ package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.marginBottom import androidx.leanback.app.BrowseSupportFragment
import androidx.core.view.marginStart import androidx.leanback.widget.ArrayObjectAdapter
import androidx.core.view.marginTop import androidx.leanback.widget.HeaderItem
import androidx.fragment.app.Fragment import androidx.leanback.widget.ListRow
import androidx.leanback.widget.ListRowPresenter
import androidx.leanback.widget.ListRowPresenter.SelectItemViewHolderTask
import androidx.leanback.widget.OnItemViewClickedListener
import androidx.leanback.widget.OnItemViewSelectedListener
import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.api.YSP import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.MenuBinding
import com.lizongying.mytv.databinding.RowBinding
import com.lizongying.mytv.models.ProgramType import com.lizongying.mytv.models.ProgramType
import com.lizongying.mytv.models.TVList
import com.lizongying.mytv.models.TVListViewModel import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() {
class MainFragment : Fragment(), CardAdapter.ItemListener {
private var itemPosition = 0 private var itemPosition = 0
private var rowList: MutableList<View> = mutableListOf() private var rowsAdapter: ArrayObjectAdapter? = null
private var _binding: MenuBinding? = null
private val binding get() = _binding!!
var tvListViewModel = TVListViewModel() var tvListViewModel = TVListViewModel()
private var lastVideoUrl = "" private var lastVideoUrl = ""
private lateinit var application: MyTVApplication override fun onCreate(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
private lateinit var gestureDetector: GestureDetector super.onCreate(savedInstanceState)
headersState = HEADERS_DISABLED
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val context = requireContext()
_binding = MenuBinding.inflate(inflater, container, false)
application = requireActivity().applicationContext as MyTVApplication
binding.menu.layoutParams.width = application.shouldWidthPx()
binding.menu.layoutParams.height = application.shouldHeightPx()
binding.container.setOnClickListener {
hideSelf()
}
gestureDetector = GestureDetector(context, GestureListener())
return binding.root
} }
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { // override fun onCreateView(
override fun onSingleTapConfirmed(e: MotionEvent): Boolean { // inflater: LayoutInflater,
Log.i(TAG, "onSingleTapConfirmed") // container: ViewGroup?,
hideSelf() // savedInstanceState: Bundle?
return true // ): View? {
} // val rootView = super.onCreateView(inflater, container, savedInstanceState)
} // rootView?.setOnClickListener {
// Log.i(TAG, "main on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// mainFragment.view?.setOnClickListener {
// Log.i(TAG, "mainFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
// getRowsSupportFragment().view?.setOnClickListener {
// Log.i(TAG, "getRowsSupportFragment on click")
// fragmentManager!!.beginTransaction().hide(this).commit()
// }
//
//
// return rootView
// }
private fun hideSelf() { override fun onStart() {
requireActivity().supportFragmentManager.beginTransaction() Log.i(TAG, "onStart")
.hide(this) super.onStart()
.commit()
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
@@ -86,247 +74,168 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
activity?.let { YSP.init(it) } activity?.let { YSP.init(it) }
itemPosition = SP.itemPosition loadRows()
view?.post { setupEventListeners()
val content = binding.content
var idx: Long = 0 tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
for ((k, v) in TVList.list) { tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
val itemBinding: RowBinding = if (tvViewModel.errInfo.value != null
RowBinding.inflate(layoutInflater, content, false) && tvViewModel.getTV().id == itemPosition
) {
val tvListViewModelCurrent = TVListViewModel() Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT).show()
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModelCurrent.addTVViewModel(tvViewModel)
tvListViewModel.addTVViewModel(tvViewModel)
} }
tvListViewModel.maxNum.add(v.size)
val adapter =
CardAdapter(
itemBinding.items,
this,
tvListViewModelCurrent,
)
rowList.add(itemBinding.items)
adapter.setItemListener(this)
itemBinding.header.text = k
itemBinding.items.tag = idx.toInt()
itemBinding.items.adapter = adapter
itemBinding.items.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
(activity as MainActivity).mainActive()
}
})
val itemDecoration = context?.let { ItemDecoration(it) }
if (itemDecoration != null) {
itemBinding.items.addItemDecoration(itemDecoration)
}
if (SP.grid) {
itemBinding.items.layoutManager =
GridLayoutManager(context, 6)
itemBinding.items.layoutParams.height =
application.dp2Px(110 * ((tvListViewModelCurrent.size() + 6 - 1) / 6) + 5)
} else {
itemBinding.items.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = application.dp2Px(11)
itemBinding.row.layoutParams = layoutParams
itemBinding.row.setOnClickListener {
hideSelf()
}
itemBinding.items.addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
gestureDetector.onTouchEvent(e)
return false
}
override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
TODO("Not yet implemented")
}
override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
TODO("Not yet implemented")
}
})
val layoutParamsHeader =
itemBinding.header.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsHeader.topMargin = application.px2Px(itemBinding.header.marginTop)
layoutParamsHeader.bottomMargin = application.px2Px(itemBinding.header.marginBottom)
layoutParamsHeader.marginStart = application.px2Px(itemBinding.header.marginStart)
itemBinding.header.layoutParams = layoutParamsHeader
itemBinding.header.textSize = application.px2PxFont(itemBinding.header.textSize)
content.addView(itemBinding.row)
idx++
} }
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
if (itemPosition >= tvListViewModel.size()) { // not first time && channel not change
itemPosition = 0 if (tvViewModel.ready.value != null
&& tvViewModel.getTV().id == itemPosition
&& check(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.getTV().title}")
(activity as? MainActivity)?.play(tvViewModel)
}
} }
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
tvListViewModel.setItemPosition(itemPosition) if (tvViewModel.change.value != null) {
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel -> val title = tvViewModel.getTV().title
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ -> Log.i(TAG, "switch $title")
if (tvViewModel.errInfo.value != null if (tvViewModel.getTV().pid != "") {
&& tvViewModel.getTV().id == itemPosition Log.i(TAG, "request $title")
) { lifecycleScope.launch(Dispatchers.IO) {
if (tvViewModel.errInfo.value == "") { tvViewModel.let { Request.fetchData(it) }
(activity as? MainActivity)?.showPlayerFragment()
(activity as? MainActivity)?.hideErrorFragment()
(activity as? MainActivity)?.hideLoadingFragment()
} else {
(activity as? MainActivity)?.hidePlayerFragment()
(activity as? MainActivity)?.hideLoadingFragment()
(activity as? MainActivity)?.showErrorFragment(tvViewModel.errInfo.value.toString())
} }
} (activity as? MainActivity)?.showInfoFragment(tvViewModel)
} setSelectedPosition(
tvViewModel.ready.observe(viewLifecycleOwner) { _ -> tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
// not first time && channel not change )
if (tvViewModel.ready.value != null } else {
&& tvViewModel.getTV().id == itemPosition if (check(tvViewModel)) {
&& check(tvViewModel) (activity as? MainActivity)?.play(tvViewModel)
) {
Log.i(TAG, "ready ${tvViewModel.getTV().title}")
(activity as? MainActivity)?.play(tvViewModel)
}
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.getTV().title
Log.i(TAG, "switch $title")
if (tvViewModel.getTV().pid != "") {
Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { Request.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel) (activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition( setSelectedPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition() tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
) )
} else {
if (check(tvViewModel)) {
// TODO lastVideoUrl
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setPosition(
tvViewModel.getRowPosition(), tvViewModel.getItemPosition()
)
}
} }
} }
} }
} }
(activity as MainActivity).fragmentReady(TAG)
} }
(activity as MainActivity).fragmentReady("MainFragment")
} }
fun changeMenu() { fun toLastPosition() {
if (SP.grid) { setSelectedPosition(
for (i in rowList) { selectedPosition, false,
if (i is RecyclerView) { SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1)
i.layoutManager = GridLayoutManager(context, 6) )
i.layoutParams.height = }
application.dp2Px(110 * (((i.adapter as CardAdapter).getItemCount() + 6 - 1) / 6) + 5)
} fun toFirstPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(0)
)
}
override fun startHeadersTransition(withHeaders: Boolean) {
}
private fun loadRows() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(context!!)
var idx: Long = 0
for ((k, v) in TVList.list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) {
val tvViewModel = TVViewModel(v1)
tvViewModel.setRowPosition(idx.toInt())
tvViewModel.setItemPosition(idx2)
tvListViewModel.addTVViewModel(tvViewModel)
listRowAdapter.add(tvViewModel)
} }
} else { tvListViewModel.maxNum.add(v.size)
for (i in rowList) { val header = HeaderItem(idx, k)
if (i is RecyclerView) { rowsAdapter!!.add(ListRow(header, listRowAdapter))
i.layoutManager = idx++
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) }
i.layoutParams.height = application.dp2Px(110 + 5)
adapter = rowsAdapter
itemPosition = SP.itemPosition
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
}
tvListViewModel.setItemPosition(itemPosition)
}
fun prevSource() {
view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.minus(1)
if (videoIndex == -1) {
tvViewModel.setVideoIndex(tvViewModel.videoUrl.value!!.size - 1)
}
tvViewModel.changed()
} }
} }
} }
} }
override fun onKey(keyCode: Int): Boolean { fun nextSource() {
if (this.isHidden) { view?.post {
when (keyCode) { val tvViewModel = tvListViewModel.getTVViewModel(itemPosition)
KeyEvent.KEYCODE_DPAD_UP -> { if (tvViewModel != null) {
(activity as MainActivity).onKey(keyCode) if (tvViewModel.videoUrl.value!!.size > 1) {
return true val videoIndex = tvViewModel.videoIndex.value?.plus(1)
} if (videoIndex == tvViewModel.videoUrl.value!!.size) {
tvViewModel.setVideoIndex(0)
KeyEvent.KEYCODE_DPAD_DOWN -> { }
(activity as MainActivity).onKey(keyCode) tvViewModel.changed()
return true
} }
} }
} }
return false
} }
override fun onItemHasFocus(tvViewModel: TVViewModel) { private fun setupEventListeners() {
val row = tvViewModel.getRowPosition() onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener()
}
for (i in rowList) { private inner class ItemViewClickedListener : OnItemViewClickedListener {
if (i.tag as Int != row) { override fun onItemClicked(
((i as RecyclerView).adapter as CardAdapter).focusable = false itemViewHolder: Presenter.ViewHolder,
(i.adapter as CardAdapter).clear() item: Any,
} else { rowViewHolder: RowPresenter.ViewHolder,
((i as RecyclerView).adapter as CardAdapter).focusable = true row: Row
) {
if (item is TVViewModel) {
if (itemPosition != item.getTV().id) {
itemPosition = item.getTV().id
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed()
}
(activity as? MainActivity)?.switchMainFragment()
} }
} }
(activity as MainActivity).mainActive()
} }
override fun onItemClicked(tvViewModel: TVViewModel) { private inner class ItemViewSelectedListener : OnItemViewSelectedListener {
if (this.isHidden) { override fun onItemSelected(
(activity as? MainActivity)?.switchMainFragment() itemViewHolder: Presenter.ViewHolder?, item: Any?,
return rowViewHolder: RowPresenter.ViewHolder, row: Row
} ) {
if (item is TVViewModel) {
if (itemPosition != tvViewModel.getTV().id) { tvListViewModel.setItemPositionCurrent(item.getTV().id)
itemPosition = tvViewModel.getTV().id (activity as MainActivity).mainActive()
tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("menu")
Log.i(TAG, "onItemClicked ${tvViewModel.getTV().id}")
}
(activity as? MainActivity)?.switchMainFragment()
}
fun setPosition() {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) ?: return
val rowPosition = tvViewModel.getRowPosition()
val itemPosition = tvViewModel.getItemPosition()
setPosition(rowPosition, itemPosition)
}
fun setPosition(rowPosition: Int, itemPosition: Int) {
rowList[rowPosition].post {
when (val layoutManager = (rowList[rowPosition] as RecyclerView).layoutManager) {
is GridLayoutManager -> {
layoutManager.findViewByPosition(
itemPosition
)?.requestFocus()
}
is LinearLayoutManager -> {
layoutManager.findViewByPosition(
itemPosition
)?.requestFocus()
}
} }
} }
} }
@@ -348,7 +257,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
fun fragmentReady() { fun fragmentReady() {
tvListViewModel.getTVViewModel(itemPosition)?.changed("init") tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel -> tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
updateEPG(tvViewModel) updateEPG(tvViewModel)
@@ -360,7 +269,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
if (itemPosition > -1 && itemPosition < tvListViewModel.size()) { if (itemPosition > -1 && itemPosition < tvListViewModel.size()) {
this.itemPosition = itemPosition this.itemPosition = itemPosition
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("num") tvListViewModel.getTVViewModel(itemPosition)?.changed()
} else { } else {
Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show() Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show()
} }
@@ -374,7 +283,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
itemPosition = tvListViewModel.size() - 1 itemPosition = tvListViewModel.size() - 1
} }
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("prev") tvListViewModel.getTVViewModel(itemPosition)?.changed()
} }
} }
@@ -385,7 +294,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
itemPosition = 0 itemPosition = 0
} }
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed("next") tvListViewModel.getTVViewModel(itemPosition)?.changed()
} }
} }
@@ -405,51 +314,23 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
} }
fun shouldHasFocus(tvModel: TVViewModel): Boolean {
return tvModel == tvListViewModel.getTVViewModel(itemPosition)
}
override fun onHiddenChanged(hidden: Boolean) {
Log.i(TAG, "onHiddenChanged $hidden")
super.onHiddenChanged(hidden)
if (!hidden) {
val tvModel = tvListViewModel.getTVViewModel(itemPosition)
val rowPosition = tvModel?.getRowPosition()
val itemPosition = tvModel?.getItemPosition()
Log.i(TAG, "toPosition $rowPosition $itemPosition")
for (i in rowList) {
if (i.tag as Int == rowPosition) {
((i as RecyclerView).adapter as CardAdapter).updateEPG()
(i.adapter as CardAdapter).focusable = true
(i.adapter as CardAdapter).toPosition(itemPosition!!)
break
}
}
} else {
view?.post {
for (i in rowList) {
((i as RecyclerView).adapter as CardAdapter).focusable = false
}
}
}
}
override fun onResume() { override fun onResume() {
Log.i(TAG, "onResume") Log.i(TAG, "onResume")
super.onResume() super.onResume()
} }
override fun onStop() {
Log.i(TAG, "onStop")
super.onStop()
SP.itemPosition = itemPosition
Log.i(TAG, "$POSITION $itemPosition saved")
}
override fun onDestroy() { override fun onDestroy() {
Log.i(TAG, "onDestroy") Log.i(TAG, "onDestroy")
super.onDestroy() super.onDestroy()
} }
override fun onDestroyView() {
Log.i(TAG, "onDestroyView")
super.onDestroyView()
_binding = null
}
companion object { companion object {
private const val TAG = "MainFragment" private const val TAG = "MainFragment"
private const val POSITION = "position" private const val POSITION = "position"
@@ -0,0 +1,23 @@
package com.lizongying.mytv
import android.app.Application
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
class MyApplication : Application() {
private lateinit var displayMetrics: DisplayMetrics
override fun onCreate() {
super.onCreate()
displayMetrics = DisplayMetrics()
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
// windowManager.defaultDisplay.getRealMetrics(displayMetrics)
windowManager.defaultDisplay.getMetrics(displayMetrics)
}
fun getDisplayMetrics(): DisplayMetrics {
return displayMetrics
}
}
@@ -1,127 +0,0 @@
package com.lizongying.mytv
import android.app.Application
import android.content.Context
import android.content.res.Resources
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.multidex.MultiDex
import com.lizongying.mytv.models.MyViewModel
class MyTVApplication : Application() {
private lateinit var displayMetrics: DisplayMetrics
private lateinit var realDisplayMetrics: DisplayMetrics
private lateinit var windowManager: WindowManager
private var width = 0
private var height = 0
private var shouldWidth = 0
private var shouldHeight = 0
private var deviation = 0
private var ratio = 1.0
private var density = 2.0f
private var scale = 1.0f
lateinit var myViewModel: MyViewModel
override fun onCreate() {
super.onCreate()
displayMetrics = DisplayMetrics()
realDisplayMetrics = DisplayMetrics()
windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.defaultDisplay.getMetrics(displayMetrics)
windowManager.defaultDisplay.getRealMetrics(realDisplayMetrics)
if (realDisplayMetrics.heightPixels > realDisplayMetrics.widthPixels) {
width = realDisplayMetrics.heightPixels
height = realDisplayMetrics.widthPixels
} else {
width = realDisplayMetrics.widthPixels
height = realDisplayMetrics.heightPixels
}
density = displayMetrics.density
ratio = if ((width.toDouble() / height) < (16.0 / 9)) {
width * 2 / 1920.0 / density
} else {
height * 2 / 1080.0 / density
}
if ((width.toDouble() / height) < (16.0 / 9.0)) {
ratio = width * 2 / 1920.0 / density
shouldWidth = width
shouldHeight = (width * 9.0 / 16.0).toInt()
} else {
ratio = height * 2 / 1080.0 / density
shouldHeight = height
shouldWidth = (height * 16.0 / 9.0).toInt()
}
deviation = width - Resources.getSystem().displayMetrics.widthPixels
scale = Resources.getSystem().displayMetrics.scaledDensity
myViewModel = ViewModelProvider(
ViewModelStore(),
ViewModelProvider.NewInstanceFactory()
)[MyViewModel::class.java]
}
fun getDisplayMetrics(): DisplayMetrics {
return displayMetrics
}
fun getRealDisplayMetrics(): DisplayMetrics {
return realDisplayMetrics
}
fun shouldWidthPx(): Int {
return shouldWidth
}
fun shouldHeightPx(): Int {
return shouldHeight
}
fun getWidth(): Int {
return width
}
fun getHeight(): Int {
return height
}
fun getDeviation(): Int {
return deviation
}
fun getRatio(): Double {
return ratio
}
fun dp2Px(dp: Int): Int {
return (dp * ratio * density + 0.5f).toInt()
}
fun px2Px(px: Int): Int {
return (px * ratio + 0.5f).toInt()
}
fun px2PxFont(px: Float): Float {
return (px * ratio / scale).toFloat()
}
fun sp2Px(sp: Float): Float {
return (sp * ratio * scale).toFloat()
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(base)
}
}
@@ -1,6 +0,0 @@
package com.lizongying.mytv
interface OnSharedPreferenceChangeListener {
fun onSharedPreferenceChanged(key: String)
}
@@ -3,6 +3,8 @@ package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
@@ -15,37 +17,45 @@ import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import com.google.android.exoplayer2.SimpleExoPlayer
import com.lizongying.mytv.databinding.PlayerBinding import com.lizongying.mytv.databinding.PlayerBinding
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
class PlayerFragment : Fragment() { class PlayerFragment : Fragment(), SurfaceHolder.Callback {
private var _binding: PlayerBinding? = null private var _binding: PlayerBinding? = null
private var playerView: PlayerView? = null private var playerView: PlayerView? = null
private var tvViewModel: TVViewModel? = null private var tvViewModel: TVViewModel? = null
private val aspectRatio = 16f / 9f private val aspectRatio = 16f / 9f
private lateinit var surfaceView: SurfaceView
private lateinit var surfaceHolder: SurfaceHolder
private var exoPlayer: SimpleExoPlayer? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
_binding = PlayerBinding.inflate(inflater, container, false) _binding = PlayerBinding.inflate(inflater, container, false)
playerView = _binding!!.playerView if (Utils.isTmallDevice()) {
_binding!!.playerView.visibility = View.GONE
surfaceView = _binding!!.surfaceView
surfaceHolder = surfaceView.holder
surfaceHolder.addCallback(this)
} else {
_binding!!.surfaceView.visibility = View.GONE
playerView = _binding!!.playerView
}
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object : playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
@OptIn(UnstableApi::class)
override fun onGlobalLayout() { override fun onGlobalLayout() {
playerView!!.viewTreeObserver.removeOnGlobalLayoutListener(this) playerView!!.viewTreeObserver.removeOnGlobalLayoutListener(this)
// val renderersFactory = context?.let { DefaultRenderersFactory(it) }
// renderersFactory?.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
playerView!!.player = activity?.let { playerView!!.player = activity?.let {
ExoPlayer.Builder(it) ExoPlayer.Builder(it)
// .setRenderersFactory(renderersFactory!!)
.build() .build()
} }
playerView!!.player?.playWhenReady = true playerView!!.player?.playWhenReady = true
@@ -68,22 +78,21 @@ class PlayerFragment : Fragment() {
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error) super.onPlayerError(error)
Log.e(TAG, "PlaybackException $error") Log.e(TAG, "PlaybackException $error")
val err = "播放错误" tvViewModel?.changed()
tvViewModel?.setErrInfo(err)
tvViewModel?.changed("retry")
} }
override fun onIsPlayingChanged(isPlaying: Boolean) { override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying) super.onIsPlayingChanged(isPlaying)
if (isPlaying) { if (isPlaying) {
tvViewModel?.setErrInfo("") (activity as MainActivity).isPlaying()
} }
} }
}) })
} }
}) })
(activity as MainActivity).fragmentReady(TAG) (activity as MainActivity).fragmentReady("PlayerFragment")
return _binding!!.root return _binding!!.root
} }
@@ -93,7 +102,10 @@ class PlayerFragment : Fragment() {
playerView?.player?.run { playerView?.player?.run {
setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent())) setMediaItem(MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
prepare() prepare()
volume = tvViewModel.getTV().volume }
exoPlayer?.run {
setMediaItem(com.google.android.exoplayer2.MediaItem.fromUri(tvViewModel.getVideoUrlCurrent()))
prepare()
} }
} }
@@ -105,6 +117,11 @@ class PlayerFragment : Fragment() {
playerView!!.player?.prepare() playerView!!.player?.prepare()
playerView!!.player?.play() playerView!!.player?.play()
} }
if (exoPlayer?.isPlaying == false) {
Log.i(TAG, "replay")
exoPlayer?.prepare()
exoPlayer?.play()
}
} }
override fun onResume() { override fun onResume() {
@@ -117,6 +134,9 @@ class PlayerFragment : Fragment() {
if (playerView != null && playerView!!.player?.isPlaying == true) { if (playerView != null && playerView!!.player?.isPlaying == true) {
playerView!!.player?.stop() playerView!!.player?.stop()
} }
if (exoPlayer?.isPlaying == true) {
exoPlayer?.stop()
}
} }
override fun onDestroy() { override fun onDestroy() {
@@ -124,6 +144,7 @@ class PlayerFragment : Fragment() {
if (playerView != null) { if (playerView != null) {
playerView!!.player?.release() playerView!!.player?.release()
} }
exoPlayer?.release()
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -132,6 +153,18 @@ class PlayerFragment : Fragment() {
} }
companion object { companion object {
private const val TAG = "PlayerFragment" private const val TAG = "PlaybackVideoFragment"
}
override fun surfaceCreated(holder: SurfaceHolder) {
exoPlayer = SimpleExoPlayer.Builder(requireContext()).build()
exoPlayer?.setVideoSurfaceHolder(surfaceHolder)
exoPlayer?.playWhenReady = true
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
} }
} }
@@ -1,10 +1,9 @@
package com.lizongying.mytv.requests package com.lizongying.mytv
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.lizongying.mytv.Utils
import com.lizongying.mytv.Utils.getDateFormat import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.Auth import com.lizongying.mytv.api.Auth
@@ -18,14 +17,12 @@ import com.lizongying.mytv.api.KvcollectRequest
import com.lizongying.mytv.api.KvcollectRequest2 import com.lizongying.mytv.api.KvcollectRequest2
import com.lizongying.mytv.api.LiveInfo import com.lizongying.mytv.api.LiveInfo
import com.lizongying.mytv.api.LiveInfoRequest import com.lizongying.mytv.api.LiveInfoRequest
import com.lizongying.mytv.api.Token2
import com.lizongying.mytv.api.YSP import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.api.YSPApiService import com.lizongying.mytv.api.YSPApiService
import com.lizongying.mytv.api.YSPBtraceService import com.lizongying.mytv.api.YSPBtraceService
import com.lizongying.mytv.api.YSPJceService import com.lizongying.mytv.api.YSPJceService
import com.lizongying.mytv.api.YSPProtoService import com.lizongying.mytv.api.YSPProtoService
import com.lizongying.mytv.api.YSPTokenService import com.lizongying.mytv.api.YSPTokenService
import com.lizongying.mytv.models.TVList
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel
@@ -43,7 +40,6 @@ import kotlin.random.Random
object Request { object Request {
private const val TAG = "Request" private const val TAG = "Request"
private var yspTokenService: YSPTokenService = ApiClient().yspTokenService private var yspTokenService: YSPTokenService = ApiClient().yspTokenService
private var yspToken2Service: YSPApiService = ApiClient().yspToken2Service
private var yspApiService: YSPApiService = ApiClient().yspApiService private var yspApiService: YSPApiService = ApiClient().yspApiService
private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService private var yspBtraceService: YSPBtraceService = ApiClient().yspBtraceService
private var yspBtraceService2: YSPBtraceService = ApiClient().yspBtraceService2 private var yspBtraceService2: YSPBtraceService = ApiClient().yspBtraceService2
@@ -56,9 +52,8 @@ object Request {
private var token = "" private var token = ""
private var tokenFH = "" private var tokenFH = ""
private var needAuth = true private var needAuth = false
private var needToken = false private var needToken = false
private var needToken2 = true
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
private lateinit var btraceRunnable: BtraceRunnable private lateinit var btraceRunnable: BtraceRunnable
@@ -77,7 +72,6 @@ object Request {
} }
private var call: Call<LiveInfo>? = null private var call: Call<LiveInfo>? = null
private var callToken: Call<Token2>? = null
private var callAuth: Call<Auth>? = null private var callAuth: Call<Auth>? = null
private var callInfo: Call<Info>? = null private var callInfo: Call<Info>? = null
private var callFAuth: Call<FAuth>? = null private var callFAuth: Call<FAuth>? = null
@@ -88,117 +82,11 @@ object Request {
private fun cancelCall() { private fun cancelCall() {
call?.cancel() call?.cancel()
callAuth?.cancel() callAuth?.cancel()
callToken?.cancel()
callInfo?.cancel() callInfo?.cancel()
callFAuth?.cancel() callFAuth?.cancel()
callPage?.cancel() callPage?.cancel()
} }
private fun fetchToken(tvModel: TVViewModel, cookie: String) {
cancelCall()
val title = tvModel.getTV().title
callToken = yspToken2Service.getToken("guid=${YSP.getGuid()}; $cookie", "519748109", YSP.getGuid(), "59306155", "b42702bf7309a179d102f3d51b1add2fda0bc7ada64cb801" ,"1", YSP.getTimeStr())
callToken?.enqueue(object : Callback<Token2> {
override fun onResponse(call: Call<Token2>, response: Response<Token2>) {
if (response.isSuccessful) {
val liveInfo = response.body()
if (liveInfo?.data?.token != null) {
Log.i(TAG, "token ${liveInfo.data.token}")
YSP.token2 = liveInfo.data.token
YSP.getTokenData(tvModel)
fetchAuth(tvModel, cookie)
} else {
Log.e(TAG, "$title token error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
Log.e(TAG, "$title token status error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
}
override fun onFailure(call: Call<Token2>, t: Throwable) {
Log.e(TAG, "$title token request error $t")
if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++
if (tvModel.getTV().needToken) {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
fetchToken(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
} else {
fetchToken(tvModel, cookie)
}
} else {
val err = "认证请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
}
}
})
}
private fun fetchAuth(tvModel: TVViewModel, cookie: String) { private fun fetchAuth(tvModel: TVViewModel, cookie: String) {
cancelCall() cancelCall()
@@ -213,11 +101,11 @@ object Request {
val liveInfo = response.body() val liveInfo = response.body()
if (liveInfo?.data?.token != null) { if (liveInfo?.data?.token != null) {
Log.i(TAG, "auth ${liveInfo.data.token}") Log.i(TAG, "token ${liveInfo.data.token}")
YSP.token = liveInfo.data.token YSP.token = liveInfo.data.token
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else { } else {
Log.e(TAG, "$title auth error") Log.e(TAG, "$title token error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) { if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++ tvModel.retryTimes++
if (tvModel.getTV().needToken) { if (tvModel.getTV().needToken) {
@@ -228,19 +116,11 @@ object Request {
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} else {
val err = "认证结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
@@ -255,19 +135,11 @@ object Request {
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} else {
val err = "认证状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -284,19 +156,11 @@ object Request {
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} else {
val err = "认证请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
}) })
@@ -305,7 +169,7 @@ object Request {
private fun fetchVideo(tvModel: TVViewModel, cookie: String) { private fun fetchVideo(tvModel: TVViewModel, cookie: String) {
cancelCall() cancelCall()
if (Request::btraceRunnable.isInitialized) { if (::btraceRunnable.isInitialized) {
handler.removeCallbacks(btraceRunnable) handler.removeCallbacks(btraceRunnable)
} }
@@ -316,14 +180,8 @@ object Request {
val request = LiveInfoRequest(data) val request = LiveInfoRequest(data)
call = request.let { call = request.let {
yspApiService.getLiveInfo( yspApiService.getLiveInfo(
"guid=${YSP.getGuid()}; gr_user_id=1; $cookie; newLogin=1; nseqId=${YSP.nseqId}; nrequest-id=${YSP.nrequest_id}", "guid=${YSP.getGuid()}; $cookie",
YSP.token, YSP.token,
YSP.yspsdkinput,
YSP.openapi_signature,
YSP.yspticket,
"${YSP.nseqId}",
YSP.nrequest_id,
"519748109",
it it
) )
} }
@@ -363,42 +221,24 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
if (needAuth) { fetchVideo(tvModel)
fetchAuth(tvModel) // fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
} else {
val err = "结果错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") { if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") {
val err = "版权错误" Log.e(TAG, "$title error ${liveInfo.data.errinfo}")
Log.e(TAG, "$title $err") tvModel.setErrInfo(liveInfo.data.errinfo)
tvModel.setErrInfo(err)
} else { } else {
Log.e(TAG, "$title url error $request $liveInfo") Log.e(TAG, "$title url error $request $liveInfo")
if (tvModel.retryTimes < tvModel.retryMaxTimes) { if (tvModel.retryTimes < tvModel.retryMaxTimes) {
@@ -407,35 +247,18 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
if (needAuth) { fetchVideo(tvModel)
fetchAuth(tvModel) // fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
} else {
val err = "其他错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -447,35 +270,18 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
if (needAuth) { fetchVideo(tvModel)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel)
} else {
fetchVideo(tvModel, cookie)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie) // fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
} else {
val err = "状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -492,82 +298,16 @@ object Request {
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else {
// TODO
} }
} }
} else { } else {
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} }
} else {
val err = "请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
}) })
} }
private fun fetchToken(tvModel: TVViewModel) {
cancelCall()
if (tvModel.needGetToken) {
callInfo = yspTokenService.getInfo("")
callInfo?.enqueue(object : Callback<Info> {
override fun onResponse(call: Call<Info>, response: Response<Info>) {
if (response.isSuccessful && response.body()?.data?.token != null) {
token = response.body()?.data?.token!!
Log.i(TAG, "info success $token")
tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie)
} else if (response.code() == 304) {
tvModel.needGetToken = false
tvModel.tokenYSPRetryTimes = 0
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109; yspopenid=$openid; vusession=$token"
fetchVideo(tvModel, cookie)
} else {
Log.e(TAG, "info status error")
if (tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
fetchToken(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109"
fetchToken(tvModel, cookie)
} else {
// TODO
}
}
}
}
override fun onFailure(call: Call<Info>, t: Throwable) {
Log.e(TAG, "info request error $t")
if (tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++
fetchAuth(tvModel)
} else {
if (!tvModel.getTV().mustToken) {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109"
fetchToken(tvModel, cookie)
} else {
// TODO
}
}
}
})
} else {
val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchToken(tvModel, cookie)
}
}
private fun fetchAuth(tvModel: TVViewModel) { private fun fetchAuth(tvModel: TVViewModel) {
cancelCall() cancelCall()
if (tvModel.needGetToken) { if (tvModel.needGetToken) {
@@ -598,8 +338,6 @@ object Request {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
// TODO
} }
} }
} }
@@ -615,15 +353,13 @@ object Request {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
// TODO
} }
} }
} }
}) })
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; yspopenid=$openid; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} }
@@ -659,8 +395,6 @@ object Request {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else {
// TODO
} }
} }
} }
@@ -676,8 +410,6 @@ object Request {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109"
fetchVideo(tvModel, cookie) fetchVideo(tvModel, cookie)
} else {
// TODO
} }
} }
} }
@@ -713,10 +445,6 @@ object Request {
if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) { if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) {
tvModel.tokenFHRetryTimes++ tvModel.tokenFHRetryTimes++
fetchFAuth(tvModel) fetchFAuth(tvModel)
} else {
val err = "状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -726,10 +454,6 @@ object Request {
if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) { if (tvModel.tokenFHRetryTimes < tvModel.tokenFHRetryMaxTimes) {
tvModel.tokenFHRetryTimes++ tvModel.tokenFHRetryTimes++
fetchFAuth(tvModel) fetchFAuth(tvModel)
} else {
val err = "请求错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
}) })
@@ -745,42 +469,19 @@ object Request {
if (token == "") { if (token == "") {
tvModel.needGetToken = true tvModel.needGetToken = true
} }
if (needToken2) { if (needAuth) {
if (needAuth) { fetchAuth(tvModel)
fetchToken(tvModel)
} else {
fetchAuth(tvModel)
}
} else { } else {
if (needAuth) { fetchVideo(tvModel)
fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} }
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; updateProtocol=1; deviceModel=120"
if (needAuth) {
if (needToken2) { fetchAuth(tvModel, cookie)
if (needAuth) {
fetchToken(tvModel, cookie)
} else {
fetchAuth(tvModel, cookie)
}
} else { } else {
if (needAuth) { fetchVideo(tvModel, cookie)
fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
//
// if (needAuth) {
// fetchAuth(tvModel, cookie)
// } else {
// fetchVideo(tvModel, cookie)
// }
} }
} }
@@ -899,7 +600,7 @@ object Request {
seq = tvModel.seq, seq = tvModel.seq,
durl = url, durl = url,
url = url, url = url,
// _dc = "", _dc = randomNumber,
) )
val e = val e =
@@ -931,7 +632,7 @@ object Request {
val guid = YSP.getGuid() val guid = YSP.getGuid()
val pid = tvModel.getTV().pid val pid = tvModel.getTV().pid
val sid = tvModel.getTV().sid val sid = tvModel.getTV().sid
// val randomNumber = Random.nextDouble() val randomNumber = Random.nextDouble()
val url = tvModel.getTV().videoUrl.first() val url = tvModel.getTV().videoUrl.first()
val r = KvcollectRequest2( val r = KvcollectRequest2(
@@ -946,12 +647,12 @@ object Request {
seq = tvModel.seq, seq = tvModel.seq,
durl = url, durl = url,
url = url, url = url,
_dc = "", _dc = randomNumber,
) )
// val e = val e =
// "BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=${r.geturltime}&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=${r.open_id}&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}" "BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=${r.geturltime}&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=${r.open_id}&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
// r.signature = YSP.getAuthSignature(e) r.signature = YSP.getAuthSignature(e)
callBtracePage = yspBtraceService3.kvcollect3( callBtracePage = yspBtraceService3.kvcollect3(
guid = r.guid, guid = r.guid,
@@ -960,18 +661,18 @@ object Request {
livepid = r.livepid, livepid = r.livepid,
sUrl = r.sUrl, sUrl = r.sUrl,
playno = r.playno, playno = r.playno,
// rand_str = r.rand_str, rand_str = r.rand_str,
ftime = r.ftime, ftime = r.ftime,
seq = "${r.seq}", seq = "${r.seq}",
durl = r.durl, durl = r.durl,
// url = r.url, url = r.url,
_dc = "${r._dc}", _dc = "${r._dc}",
// signature = r.signature signature = r.signature
) )
callBtracePage?.enqueue(object : Callback<Void> { callBtracePage?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) { override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) { if (response.isSuccessful) {
Log.i(TAG, "$title kvcollect success") Log.d(TAG, "$title kvcollect success")
} else { } else {
Log.e(TAG, "$title kvcollect status error") Log.e(TAG, "$title kvcollect status error")
} }
@@ -1114,6 +815,6 @@ object Request {
} }
fun setRequestListener(listener: RequestListener) { fun setRequestListener(listener: RequestListener) {
Request.listener = listener this.listener = listener
} }
} }
+4 -20
View File
@@ -13,12 +13,12 @@ object SP {
// If use channel num to select channel or not // If use channel num to select channel or not
private const val KEY_CHANNEL_NUM = "channel_num" private const val KEY_CHANNEL_NUM = "channel_num"
const val KEY_TIME = "time" private const val KEY_TIME = "time"
// If start app on device boot or not // If start app on device boot or not
private const val KEY_BOOT_STARTUP = "boot_startup" private const val KEY_BOOT_STARTUP = "boot_startup"
const val KEY_GRID = "grid" private const val KEY_GRID = "grid"
// Position in list of the selected channel item // Position in list of the selected channel item
private const val KEY_POSITION = "position" private const val KEY_POSITION = "position"
@@ -28,8 +28,6 @@ object SP {
private lateinit var sp: SharedPreferences private lateinit var sp: SharedPreferences
private var listener: OnSharedPreferenceChangeListener? = null
/** /**
* The method must be invoked as early as possible(At least before using the keys) * The method must be invoked as early as possible(At least before using the keys)
*/ */
@@ -37,10 +35,6 @@ object SP {
sp = context.getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE) sp = context.getSharedPreferences(SP_FILE_NAME, Context.MODE_PRIVATE)
} }
fun setOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
this.listener = listener
}
var channelReversal: Boolean var channelReversal: Boolean
get() = sp.getBoolean(KEY_CHANNEL_REVERSAL, false) get() = sp.getBoolean(KEY_CHANNEL_REVERSAL, false)
set(value) = sp.edit().putBoolean(KEY_CHANNEL_REVERSAL, value).apply() set(value) = sp.edit().putBoolean(KEY_CHANNEL_REVERSAL, value).apply()
@@ -51,12 +45,7 @@ object SP {
var time: Boolean var time: Boolean
get() = sp.getBoolean(KEY_TIME, true) get() = sp.getBoolean(KEY_TIME, true)
set(value) { set(value) = sp.edit().putBoolean(KEY_TIME, value).apply()
if (value != this.time) {
sp.edit().putBoolean(KEY_TIME, value).apply()
listener?.onSharedPreferenceChanged(KEY_TIME)
}
}
var bootStartup: Boolean var bootStartup: Boolean
get() = sp.getBoolean(KEY_BOOT_STARTUP, false) get() = sp.getBoolean(KEY_BOOT_STARTUP, false)
@@ -64,12 +53,7 @@ object SP {
var grid: Boolean var grid: Boolean
get() = sp.getBoolean(KEY_GRID, false) get() = sp.getBoolean(KEY_GRID, false)
set(value) { set(value) = sp.edit().putBoolean(KEY_GRID, value).apply()
if (value != this.grid) {
sp.edit().putBoolean(KEY_GRID, value).apply()
listener?.onSharedPreferenceChanged(KEY_GRID)
}
}
var itemPosition: Int var itemPosition: Int
get() = sp.getInt(KEY_POSITION, 0) get() = sp.getInt(KEY_POSITION, 0)
@@ -1,23 +1,11 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.SettingBinding import com.lizongying.mytv.databinding.SettingBinding
@@ -33,11 +21,6 @@ class SettingFragment : DialogFragment() {
dialog?.window?.apply { dialog?.window?.apply {
addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
setAttributes(attributes)
}
} }
} }
@@ -88,156 +71,35 @@ class SettingFragment : DialogFragment() {
} }
} }
binding.switchGrid.run { updateManager = UpdateManager(context, this, context.appVersionCode)
isChecked = SP.grid binding.checkVersion.setOnClickListener(
setOnCheckedChangeListener { _, isChecked -> OnClickListenerCheckVersion(
SP.grid = isChecked activity as MainActivity,
(activity as MainActivity).settingDelayHide() updateManager
} )
}
binding.checkVersion.setOnClickListener {
(activity as MainActivity).settingDelayHide()
requestInstallPermissions()
}
binding.clear.setOnClickListener {
(requireActivity() as MainActivity).syncTime()
SP.guid = ""
YSP.getGuid()
}
val application = requireActivity().applicationContext as MyTVApplication
val textSize = application.px2PxFont(binding.switchChannelReversal.textSize)
binding.content.layoutParams.width =
application.px2Px(binding.content.layoutParams.width)
binding.content.setPadding(
application.px2Px(binding.content.paddingLeft),
application.px2Px(binding.content.paddingTop),
application.px2Px(binding.content.paddingRight),
application.px2Px(binding.content.paddingBottom)
) )
binding.name.textSize = application.px2PxFont(binding.name.textSize)
binding.version.textSize = textSize
val layoutParamsVersion = binding.version.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsVersion.topMargin = application.px2Px(binding.version.marginTop)
binding.version.layoutParams = layoutParamsVersion
binding.checkVersion.textSize = textSize binding.exit.setOnClickListener{
val layoutParamsCheckVersion =
binding.checkVersion.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsCheckVersion.marginEnd = application.px2Px(binding.checkVersion.marginEnd)
binding.checkVersion.layoutParams = layoutParamsCheckVersion
binding.versionName.textSize = textSize
binding.clear.textSize = textSize
binding.exit.textSize = textSize
val layoutParamsChannelSwitch =
binding.switchChannelReversal.layoutParams as ViewGroup.MarginLayoutParams
layoutParamsChannelSwitch.topMargin =
application.px2Px(binding.switchChannelReversal.marginTop)
binding.switchChannelReversal.textSize = textSize
binding.switchChannelReversal.layoutParams = layoutParamsChannelSwitch
binding.switchChannelNum.textSize = textSize
binding.switchChannelNum.layoutParams = layoutParamsChannelSwitch
binding.switchTime.textSize = textSize
binding.switchTime.layoutParams = layoutParamsChannelSwitch
binding.switchBootStartup.textSize = textSize
binding.switchBootStartup.layoutParams = layoutParamsChannelSwitch
binding.switchGrid.textSize = textSize
binding.switchGrid.layoutParams = layoutParamsChannelSwitch
binding.appreciate.layoutParams.width =
application.px2Px(binding.appreciate.layoutParams.width)
binding.exit.setOnClickListener {
requireActivity().finishAffinity() requireActivity().finishAffinity()
} }
// val myViewModel = application.myViewModel
// application.myViewModel.downloadProgress.observe(viewLifecycleOwner) { _ ->
// val downloadProgress = myViewModel.downloadProgress.value
// if (downloadProgress != null) {
// if (downloadProgress == 100) {
// binding.progressBar.visibility = GONE
// } else {
// if (!binding.progressBar.isVisible) {
// binding.progressBar.visibility = VISIBLE
// }
// binding.progressBar.progress = downloadProgress
// }
// }
// }
updateManager = UpdateManager(context, context.appVersionCode)
return binding.root return binding.root
} }
private fun requestInstallPermissions() { fun setVersionName(versionName: String) {
val context = requireContext() if (_binding != null) {
val permissionsList: MutableList<String> = ArrayList() binding.versionName.text = versionName
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.packageManager.canRequestPackageInstalls()) {
permissionsList.add(Manifest.permission.REQUEST_INSTALL_PACKAGES)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED
) {
permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissionsList.isNotEmpty()) {
ActivityCompat.requestPermissions(
requireActivity(),
permissionsList.toTypedArray<String>(),
PERMISSIONS_REQUEST_CODE
)
} else {
updateManager.checkAndUpdate()
} }
} }
override fun onRequestPermissionsResult( internal class OnClickListenerCheckVersion(
requestCode: Int, private val mainActivity: MainActivity,
permissions: Array<out String>, private val updateManager: UpdateManager
grantResults: IntArray ) :
) { View.OnClickListener {
super.onRequestPermissionsResult(requestCode, permissions, grantResults) override fun onClick(view: View?) {
if (requestCode == PERMISSIONS_REQUEST_CODE) { mainActivity.settingDelayHide()
var allPermissionsGranted = true updateManager.checkAndUpdate()
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false
break
}
}
if (allPermissionsGranted) {
updateManager.checkAndUpdate()
} else {
Toast.makeText(context, "权限授权失败", Toast.LENGTH_LONG).show()
}
} }
} }
@@ -248,7 +110,6 @@ class SettingFragment : DialogFragment() {
companion object { companion object {
const val TAG = "SettingFragment" const val TAG = "SettingFragment"
const val PERMISSIONS_REQUEST_CODE = 1
} }
} }
@@ -1,4 +1,4 @@
package com.lizongying.mytv.models package com.lizongying.mytv
import com.lizongying.mytv.models.ProgramType import com.lizongying.mytv.models.ProgramType
import java.io.Serializable import java.io.Serializable
@@ -15,22 +15,18 @@ data class TV(
var programType: ProgramType, var programType: ProgramType,
var needToken: Boolean = false, var needToken: Boolean = false,
var mustToken: Boolean = false, var mustToken: Boolean = false,
var volume: Float = 0.1F,
) : Serializable { ) : Serializable {
override fun toString(): String { override fun toString(): String {
return "TV{" + return "TV{" +
"id=" + id + "id=" + id +
", title='" + title + '\'' + ", title='" + title + '\'' +
", alias='" + alias + '\'' +
", videoUrl='" + videoUrl + '\'' + ", videoUrl='" + videoUrl + '\'' +
", channel='" + channel + '\'' +
", logo='" + logo + '\'' + ", logo='" + logo + '\'' +
", pid='" + pid + '\'' + ", pid='" + pid + '\'' +
", sid='" + sid + '\'' + ", sid='" + sid + '\'' +
", programType='" + programType + '\'' + ", programType='" + programType + '\'' +
", needToken='" + needToken + '\'' +
", mustToken='" + mustToken + '\'' +
'}' '}'
} }
} }
@@ -1,6 +1,6 @@
package com.lizongying.mytv.models package com.lizongying.mytv
import com.lizongying.mytv.R import com.lizongying.mytv.models.ProgramType
object TVList { object TVList {
val list: Map<String, List<TV>> by lazy { val list: Map<String, List<TV>> by lazy {
@@ -21,8 +21,7 @@ object TVList {
"2022576801", "2022576801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -35,8 +34,7 @@ object TVList {
"2022576701", "2022576701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -49,8 +47,7 @@ object TVList {
"2022576501", "2022576501",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true, mustToken = true
0.5F
), ),
TV( TV(
0, 0,
@@ -63,8 +60,7 @@ object TVList {
"2022576601", "2022576601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -77,8 +73,7 @@ object TVList {
"2022576401", "2022576401",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -91,8 +86,7 @@ object TVList {
"2013693901", "2013693901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true, mustToken = true
0.5F
), ),
TV( TV(
0, 0,
@@ -105,8 +99,7 @@ object TVList {
"2022576201", "2022576201",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -119,8 +112,7 @@ object TVList {
"2022576101", "2022576101",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true, mustToken = true
0.5F
), ),
TV( TV(
0, 0,
@@ -133,8 +125,7 @@ object TVList {
"2022576001", "2022576001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -147,8 +138,7 @@ object TVList {
"2022573001", "2022573001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -161,8 +151,7 @@ object TVList {
"2022575901", "2022575901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -175,8 +164,7 @@ object TVList {
"2022575801", "2022575801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -189,8 +177,7 @@ object TVList {
"2022575701", "2022575701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -203,8 +190,7 @@ object TVList {
"2022575601", "2022575601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -217,8 +203,7 @@ object TVList {
"2022575501", "2022575501",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -231,8 +216,7 @@ object TVList {
"2022575401", "2022575401",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -245,8 +229,7 @@ object TVList {
"2022575301", "2022575301",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -259,8 +242,7 @@ object TVList {
"2022576301", "2022576301",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -273,8 +255,7 @@ object TVList {
"2022575201", "2022575201",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -287,8 +268,7 @@ object TVList {
"2020603421", "2020603421",
ProgramType.Y_JCE, ProgramType.Y_JCE,
false, false,
mustToken = false, mustToken = false
0.8F
), ),
TV( TV(
0, 0,
@@ -758,8 +738,7 @@ object TVList {
"2019927403", "2019927403",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -772,8 +751,7 @@ object TVList {
"2022606701", "2022606701",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false, mustToken = false
0.8F
), ),
TV( TV(
0, 0,
@@ -786,8 +764,7 @@ object TVList {
"2022823801", "2022823801",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false, mustToken = false
0.6F
), ),
), ),
"港澳台" to listOf( "港澳台" to listOf(
@@ -802,8 +779,7 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false, mustToken = false
0.7F
), ),
TV( TV(
0, 0,
@@ -816,8 +792,7 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false, mustToken = false
0.7F
), ),
TV( TV(
0, 0,
@@ -830,8 +805,7 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false, mustToken = false
0.7F
), ),
), ),
"国际" to listOf( "国际" to listOf(
@@ -846,8 +820,7 @@ object TVList {
"2022575001", "2022575001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -860,8 +833,7 @@ object TVList {
"2022574901", "2022574901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -874,8 +846,7 @@ object TVList {
"2022574801", "2022574801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -888,8 +859,7 @@ object TVList {
"2022574601", "2022574601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -902,8 +872,7 @@ object TVList {
"2022571701", "2022571701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
TV( TV(
0, 0,
@@ -916,8 +885,7 @@ object TVList {
"2022574701", "2022574701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false, mustToken = false
0.5F
), ),
) )
) )
@@ -932,7 +900,6 @@ object TVList {
v.forEach { v1 -> v.forEach { v1 ->
if (!v1.mustToken) { if (!v1.mustToken) {
v1.id = id v1.id = id
v1.needToken = false
id++ id++
group.add(v1) group.add(v1)
} }
@@ -2,11 +2,10 @@ package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.marginEnd
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.lizongying.mytv.Utils.getDateFormat import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.databinding.TimeBinding import com.lizongying.mytv.databinding.TimeBinding
@@ -22,57 +21,74 @@ class TimeFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
Log.i(TAG, "onCreateView")
_binding = TimeBinding.inflate(inflater, container, false) _binding = TimeBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTVApplication val activity = requireActivity()
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
binding.time.layoutParams.width = application.px2Px(binding.time.layoutParams.width) displayMetrics.density
binding.time.layoutParams.height = application.px2Px(binding.time.layoutParams.height)
val layoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams var screenWidth = displayMetrics.widthPixels
layoutParams.topMargin = application.px2Px(binding.time.marginTop) var screenHeight = displayMetrics.heightPixels
layoutParams.marginEnd = application.px2Px(binding.time.marginEnd) if (screenHeight > screenWidth) {
binding.time.layoutParams = layoutParams screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
binding.content.textSize = application.px2PxFont(binding.content.textSize)
binding.channel.textSize = application.px2PxFont(binding.channel.textSize)
binding.main.layoutParams.width = application.shouldWidthPx()
binding.main.layoutParams.height = application.shouldHeightPx()
if (SP.time) {
handler.removeCallbacks(showRunnable)
handler.postDelayed(showRunnable, 0)
} else {
handler.removeCallbacks(showRunnable)
} }
(activity as MainActivity).fragmentReady(TAG) val ratio = 16f / 9f
if (screenWidth / screenHeight > ratio) {
val x = ((screenWidth - screenHeight * ratio) / 2).toInt()
val originalLayoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.rightMargin += x
binding.time.layoutParams = originalLayoutParams
}
if (screenWidth / screenHeight < ratio) {
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
val originalLayoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
binding.time.layoutParams = originalLayoutParams
}
(activity as MainActivity).fragmentReady("TimeFragment")
return binding.root return binding.root
} }
override fun onHiddenChanged(hidden: Boolean) { override fun onResume() {
super.onHiddenChanged(hidden) super.onResume()
if (!hidden) { if (view?.visibility == View.VISIBLE) {
handler.removeCallbacks(showRunnable) handler.removeCallbacks(showRunnable)
handler.postDelayed(showRunnable, 0) handler.postDelayed(showRunnable, 0)
} else {
handler.removeCallbacks(showRunnable)
} }
} }
override fun onPause() {
super.onPause()
handler.removeCallbacks(showRunnable)
}
private val showRunnable: Runnable = Runnable { private val showRunnable: Runnable = Runnable {
run { run {
if (_binding == null) {
return@Runnable
}
binding.content.text = getDateFormat("HH:mm") binding.content.text = getDateFormat("HH:mm")
handler.postDelayed(showRunnable, delay) handler.postDelayed(showRunnable, delay)
} }
} }
override fun onDestroyView() { fun show() {
view?.visibility = View.VISIBLE
handler.removeCallbacks(showRunnable) handler.removeCallbacks(showRunnable)
handler.postDelayed(showRunnable, 0)
}
fun hide() {
view?.visibility = View.GONE
handler.removeCallbacks(showRunnable)
}
override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
@@ -1,73 +1,206 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.app.Activity
import android.app.DownloadManager
import android.app.DownloadManager.Request
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import androidx.fragment.app.FragmentActivity import androidx.core.app.ActivityCompat
import androidx.work.OneTimeWorkRequestBuilder import androidx.core.content.PermissionChecker
import androidx.work.WorkManager import androidx.core.content.PermissionChecker.checkSelfPermission
import androidx.work.workDataOf import com.lizongying.mytv.api.ReleaseV2
import com.lizongying.mytv.api.ApiClient import com.lizongying.mytv.requests.MyRequest
import com.lizongying.mytv.requests.ReleaseRequest
import com.lizongying.mytv.requests.ReleaseResponse
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
class UpdateManager( class UpdateManager(
private var context: Context, private var context: Context,
private var settingFragment: SettingFragment,
private var versionCode: Long private var versionCode: Long
) : ) :
ConfirmationFragment.ConfirmationListener { ConfirmationFragment.ConfirmationListener {
private var releaseRequest = ReleaseRequest() private var myRequest = MyRequest()
private var release: ReleaseResponse? = null private var release: ReleaseV2? = null
private var downloadReceiver: DownloadReceiver? = null
fun checkAndUpdate() { fun checkAndUpdate() {
if (!haveStoragePermission()) {
return
}
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
var text = "版本获取失败" var text = "版本获取失败"
var update = false
try { try {
release = releaseRequest.getRelease() release = myRequest.getRelease()
val code = release?.version_code Log.i(TAG, "versionCode $versionCode ${release?.c}")
if (code != null) { if (release?.c != null) {
if (code.toLong() >= versionCode) { text = if (release?.c!! > versionCode) {
text = "最新版本:${release?.version_name}" "最新版本:${release?.n}\n${release?.d ?: ""}"
update = true
} else { } else {
text = "已是最新版本,不需要更新" "已是最新版本,不需要更新"
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error occurred: ${e.message}", e) Log.e(TAG, "Error occurred: ${e.message}", e)
} }
updateUI(text, update) updateUI(text)
} }
} }
private fun updateUI(text: String, update: Boolean) { private fun updateUI(text: String) {
val dialog = ConfirmationFragment(this@UpdateManager, text, update) val dialog = ConfirmationFragment(this@UpdateManager, text)
dialog.show((context as FragmentActivity).supportFragmentManager, TAG) dialog.show(settingFragment.fragmentManager, TAG)
} }
private fun startDownload(context: Context, release: ReleaseResponse) { private fun haveStoragePermission(): Boolean {
val versionName = release.version_name if (Build.VERSION.SDK_INT >= 23) {
val apkName = "my-tv" if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
val apkFileName = "$apkName-${release.version_name}.apk" === PermissionChecker.PERMISSION_GRANTED
val downloadUrl = ) {
"${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk" Log.e("Permission error", "You have permission")
Log.i(TAG, "downloadUrl: $downloadUrl") return true
val downloadRequest = OneTimeWorkRequestBuilder<UpdateWorker>() } else {
.setInputData( Log.e("Permission error", "You have asked for permission")
workDataOf( ActivityCompat.requestPermissions(
"VERSION_NAME" to versionName, context as Activity, arrayOf(
"APK_FILENAME" to apkFileName, android.Manifest.permission.WRITE_EXTERNAL_STORAGE
"DOWNLOAD_URL" to downloadUrl ), 1
)
return false
}
} else { //you don't need to worry about these stuff below api level 23
Log.e("Permission error", "You already have the permission")
return true
}
}
private fun startDownload(release: ReleaseV2) {
val apkFileName = "my-tv-${release.n}.apk"
Log.i(TAG, "apkFileName $apkFileName")
val downloadManager =
context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = Request(Uri.parse(release.u))
Log.i(TAG, "url ${Uri.parse(release.u)}")
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.mkdirs()
request.setDestinationInExternalFilesDir(
context,
Environment.DIRECTORY_DOWNLOADS,
apkFileName
)
request.setTitle("${settingFragment.resources.getString(R.string.app_name)} ${release.n}")
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setAllowedOverRoaming(false)
request.setMimeType("application/vnd.android.package-archive")
// 获取下载任务的引用
val downloadReference = downloadManager.enqueue(request)
downloadReceiver = DownloadReceiver(context, apkFileName, downloadReference)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.registerReceiver(
downloadReceiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
Context.RECEIVER_NOT_EXPORTED,
)
} else {
context.registerReceiver(
downloadReceiver,
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
}
getDownloadProgress(context, downloadReference) { progress ->
println("Download progress: $progress%")
}
}
private fun getDownloadProgress(
context: Context,
downloadId: Long,
progressListener: (Int) -> Unit
) {
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val handler = Handler(Looper.getMainLooper())
val intervalMillis: Long = 1000
handler.post(object : Runnable {
override fun run() {
Log.i(TAG, "search")
val query = DownloadManager.Query().setFilterById(downloadId)
val cursor: Cursor = downloadManager.query(query)
cursor.use {
if (it.moveToFirst()) {
val bytesDownloadedIndex =
it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
val bytesTotalIndex =
it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
// 检查列名是否存在
if (bytesDownloadedIndex != -1 && bytesTotalIndex != -1) {
val bytesDownloaded = it.getInt(bytesDownloadedIndex)
val bytesTotal = it.getInt(bytesTotalIndex)
if (bytesTotal != -1) {
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
progressListener(progress)
if (progress == 100) {
return
}
}
}
}
}
// handler.postDelayed(this, intervalMillis)
}
})
}
private class DownloadReceiver(
private val context: Context,
private val apkFileName: String,
private val downloadReference: Long
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)
Log.i(TAG, "reference $reference")
val progress = intent.getIntExtra("progress", 0)
Log.i(TAG, "progress $progress")
// 检查是否是我们发起的下载
if (reference == downloadReference) {
// 下载完成,触发安装
installNewVersion()
}
}
private fun installNewVersion() {
val installIntent = Intent(Intent.ACTION_VIEW)
val apkUri = Uri.fromFile(
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
apkFileName
) )
) )
.build() installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive")
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
WorkManager.getInstance(context).enqueue(downloadRequest) context.startActivity(installIntent)
}
} }
companion object { companion object {
@@ -76,9 +209,16 @@ class UpdateManager(
override fun onConfirm() { override fun onConfirm() {
Log.i(TAG, "onConfirm $release") Log.i(TAG, "onConfirm $release")
release?.let { startDownload(context, it) } release?.let { startDownload(it) }
} }
override fun onCancel() { override fun onCancel() {
} }
fun destroy() {
if (downloadReceiver != null) {
context.unregisterReceiver(downloadReceiver)
Log.i(TAG, "destroy downloadReceiver")
}
}
} }
@@ -1,158 +0,0 @@
package com.lizongying.mytv
import android.app.DownloadManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.lizongying.mytv.models.MyViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
class UpdateWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
private lateinit var notificationManager: NotificationManager
private lateinit var notificationBuilder: NotificationCompat.Builder
private lateinit var myViewModel: MyViewModel
override suspend fun doWork(): Result {
val downloadUrl = inputData.getString("DOWNLOAD_URL") ?: return Result.failure()
val apkFileName = inputData.getString("APK_FILENAME") ?: return Result.failure()
val versionName = inputData.getString("VERSION_NAME") ?: return Result.failure()
showNotification(applicationContext)
val app = applicationContext as MyTVApplication
myViewModel = app.myViewModel
return withContext(Dispatchers.IO) {
try {
downloadAndInstall(applicationContext, downloadUrl, apkFileName, versionName)
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
}
private fun showNotification(context: Context) {
notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationBuilder = NotificationCompat.Builder(context, "download_channel").apply {
setSmallIcon(android.R.drawable.stat_sys_download)
setContentTitle("Downloading Update")
setContentText("Download in progress")
priority = NotificationCompat.PRIORITY_LOW
setOngoing(true)
setProgress(100, 0, false)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"download_channel",
"Download Updates",
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}
notificationManager.notify(1, notificationBuilder.build())
}
private fun downloadAndInstall(
context: Context,
downloadUrl: String,
apkFileName: String,
versionName: String
) {
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.mkdirs()
Log.i(TAG, "save dir ${Environment.DIRECTORY_DOWNLOADS}")
val request = DownloadManager.Request(Uri.parse(downloadUrl)).apply {
setTitle("${context.resources.getString(R.string.app_name)} $versionName Downloading")
setDescription("Downloading the latest version of the app")
setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, apkFileName)
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
// setAllowedOverRoaming(false)
setMimeType("application/vnd.android.package-archive")
}
val manager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadId = manager.enqueue(request)
var downloading = true
while (downloading) {
val query = DownloadManager.Query().setFilterById(downloadId)
val cursor = manager.query(query)
if (cursor.moveToFirst()) {
val bytesDownloaded =
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val bytesTotal =
cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
if (bytesTotal > 0) {
val progress = (bytesDownloaded * 100L / bytesTotal).toInt()
Log.i(TAG, "progress $progress")
// myViewModel.setDownloadProgress(progress)
notificationBuilder.setProgress(100, progress, false)
notificationManager.notify(1, notificationBuilder.build())
}
when (cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))) {
DownloadManager.STATUS_SUCCESSFUL -> downloading = false
DownloadManager.STATUS_FAILED -> {
downloading = false
throw Exception("Download failed")
}
}
}
cursor.close()
}
notificationBuilder.setContentText("Download complete")
.setProgress(0, 0, false)
.setOngoing(false)
notificationManager.notify(1, notificationBuilder.build())
val apkFile = File(
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
apkFileName
)
if (apkFile.exists()) {
val apkUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(context, context.packageName + ".fileprovider", apkFile)
.apply {
Intent.FLAG_GRANT_READ_URI_PERMISSION
}
} else {
Uri.parse("file://${apkFile.absolutePath}")
// Uri.fromFile(apkFile)
}
// val apkUri = Uri.parse("file://$apkFile")
Log.i(TAG, "apkUri $apkUri")
val installIntent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(installIntent)
} else {
Log.e(TAG, "APK file does not exist!")
}
}
companion object {
private const val TAG = "UpdateWorker"
}
}
+6 -35
View File
@@ -5,11 +5,8 @@ import android.os.Build
import android.util.TypedValue import android.util.TypedValue
import com.google.gson.Gson import com.google.gson.Gson
import com.lizongying.mytv.api.TimeResponse import com.lizongying.mytv.api.TimeResponse
import com.lizongying.mytv.requests.Request
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException import java.io.IOException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@@ -18,8 +15,6 @@ import java.util.Locale
object Utils { object Utils {
private var between: Long = 0 private var between: Long = 0
private var listener: Request.RequestListener? = null
fun getDateFormat(format: String): String { fun getDateFormat(format: String): String {
return SimpleDateFormat( return SimpleDateFormat(
format, format,
@@ -36,32 +31,13 @@ object Utils {
} }
suspend fun init() { suspend fun init() {
var currentTimeMillis: Long = 0
try { try {
val currentTimeMillis = getTimestampFromServer() currentTimeMillis = getTimestampFromServer()
if (currentTimeMillis > 0) {
between = System.currentTimeMillis() - currentTimeMillis
}
} catch (e: Exception) { } catch (e: Exception) {
println("Failed to retrieve timestamp from server: ${e.message}") println("Failed to retrieve timestamp from server: ${e.message}")
} }
between = System.currentTimeMillis() - currentTimeMillis
withContext(Dispatchers.Main) {
listener?.onRequestFinished(null)
}
}
class RetryInterceptor(private val maxRetry: Int) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response = chain.proceed(request)
var tryCount = 0
while (!response.isSuccessful && tryCount < maxRetry) {
tryCount++
response = chain.proceed(request)
}
return response
}
} }
/** /**
@@ -71,8 +47,8 @@ object Utils {
private suspend fun getTimestampFromServer(): Long { private suspend fun getTimestampFromServer(): Long {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder() val client = okhttp3.OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(3)) .connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS)
.build() .readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build()
val request = okhttp3.Request.Builder() val request = okhttp3.Request.Builder()
.url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp") .url("https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp")
.build() .build()
@@ -83,6 +59,7 @@ object Utils {
Gson().fromJson(string, TimeResponse::class.java).data.t.toLong() Gson().fromJson(string, TimeResponse::class.java).data.t.toLong()
} }
} catch (e: IOException) { } catch (e: IOException) {
// Handle network errors
throw IOException("Error during network request", e) throw IOException("Error during network request", e)
} }
} }
@@ -95,8 +72,6 @@ object Utils {
} }
fun dpToPx(dp: Int): Int { fun dpToPx(dp: Int): Int {
// val density = Resources.getSystem().displayMetrics.density
// return (dp * density + 0.5f).toInt()
return TypedValue.applyDimension( return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), Resources.getSystem().displayMetrics TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), Resources.getSystem().displayMetrics
).toInt() ).toInt()
@@ -113,8 +88,4 @@ object Utils {
} }
fun isTmallDevice() = Build.MANUFACTURER.equals("Tmall", ignoreCase = true) fun isTmallDevice() = Build.MANUFACTURER.equals("Tmall", ignoreCase = true)
fun setRequestListener(listener: Request.RequestListener) {
this.listener = listener
}
} }
@@ -3,9 +3,7 @@ package com.lizongying.mytv.api
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import com.google.gson.Gson
import com.lizongying.mytv.jce.JceConverterFactory import com.lizongying.mytv.jce.JceConverterFactory
import com.lizongying.mytv.requests.ReleaseService
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.TlsVersion import okhttp3.TlsVersion
@@ -25,19 +23,17 @@ class ApiClient {
private val protoUrl = "https://capi.yangshipin.cn/" private val protoUrl = "https://capi.yangshipin.cn/"
private val traceUrl = "https://btrace.yangshipin.cn/" private val traceUrl = "https://btrace.yangshipin.cn/"
private val trace2Url = "https://aatc-api.yangshipin.cn/" private val trace2Url = "https://aatc-api.yangshipin.cn/"
private val tokenUrl = "https://h5access.yangshipin.cn/"
private val trace3Url = "https://dtrace.ysp.cctv.cn/" private val trace3Url = "https://dtrace.ysp.cctv.cn/"
private val jceUrl = "https://jacc.ysp.cctv.cn/" private val jceUrl = "https://jacc.ysp.cctv.cn/"
private val fUrl = "https://m.fengshows.com/" private val fUrl = "https://m.fengshows.com/"
private var okHttpClient = getUnsafeOkHttpClient() private var okHttpClient = getUnsafeOkHttpClient()
private var gson = Gson()
val yspApiService: YSPApiService by lazy { val yspApiService: YSPApiService by lazy {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(yspUrl) .baseUrl(yspUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPApiService::class.java) .build().create(YSPApiService::class.java)
} }
@@ -45,15 +41,15 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(myUrl) .baseUrl(myUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPTokenService::class.java) .build().create(YSPTokenService::class.java)
} }
val releaseService: ReleaseService by lazy { val releaseService: ReleaseService by lazy {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(HOST) .baseUrl(myUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(ReleaseService::class.java) .build().create(ReleaseService::class.java)
} }
@@ -69,7 +65,7 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(traceUrl) .baseUrl(traceUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
@@ -77,23 +73,15 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(trace2Url) .baseUrl(trace2Url)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
val yspToken2Service: YSPApiService by lazy {
Retrofit.Builder()
.baseUrl(tokenUrl)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build().create(YSPApiService::class.java)
}
val yspBtraceService3: YSPBtraceService by lazy { val yspBtraceService3: YSPBtraceService by lazy {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(trace3Url) .baseUrl(trace3Url)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
@@ -109,7 +97,7 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(fUrl) .baseUrl(fUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create())
.build().create(FAuthService::class.java) .build().create(FAuthService::class.java)
} }
@@ -182,9 +170,4 @@ class ApiClient {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
companion object {
const val HOST = "https://gitee.com/lizongying/my-tv/raw/"
const val DOWNLOAD_HOST = "https://gitee.com/lizongying/my-tv/releases/download/"
}
} }
@@ -1,17 +1,17 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
data class KvcollectRequest( data class KvcollectRequest(
val BossId: Int = 99150, val BossId: Int = 2727,
val Pwd: Int = 1999332929, val Pwd: Int = 1424084450,
val cdn: String = "waibao", val cdn: String = "waibao",
val cmd: Int = 263, val cmd: Int = 263,
val defn: String = "fhd", val defn: String = "fhd",
val downspeed: Int = 10, val downspeed: Double = 10.0,
val durl: String = "", // val durl: String = "", //
val errcode: String = "", val errcode: String = "",
val fact1: String = "ysp_pc_live_b", val fact1: String = "ysp_pc_live_b",
val firstreport: Int = 0, val firstreport: Int = 0,
val fplayerver: String = "180", val fplayerver: String = "100",
val ftime: String = "", // val ftime: String = "", //
val geturltime: Int = 0, val geturltime: Int = 0,
val guid: String = "", // val guid: String = "", //
@@ -39,17 +39,17 @@ data class KvcollectRequest(
data class KvcollectRequest2( data class KvcollectRequest2(
val BossId: Int = 99150, val BossId: Int = 2727,
val Pwd: Int = 1999332929, val Pwd: Int = 1424084450,
val cdn: String = "waibao", val cdn: String = "waibao",
val cmd: Int = 263, val cmd: Int = 263,
val defn: String = "fhd", val defn: String = "fhd",
val downspeed: Int = 10, val downspeed: String = "10",
val durl: String = "", // val durl: String = "", //
val errcode: String = "", val errcode: String = "",
val fact1: String = "ysp_pc_live_b", val fact1: String = "ysp_pc_live_b",
val firstreport: Int = 0, val firstreport: Int = 0,
val fplayerver: String = "180", val fplayerver: String = "100",
val ftime: String = "", // val ftime: String = "", //
val geturltime: Int = 0, val geturltime: Int = 0,
val guid: String = "", // val guid: String = "", //
@@ -68,9 +68,9 @@ data class KvcollectRequest2(
val sRef: String = "", val sRef: String = "",
val sUrl: String = "", // val sUrl: String = "", //
val sdtfrom: String = "ysp_pc_01", val sdtfrom: String = "ysp_pc_01",
val seq: Int = 0, // val seq: Int = 9, //
var signature: String = "", var signature: String = "",
val url: String = "", // val url: String = "", //
val viewid: String = "", // val viewid: String = "", //
val _dc: String = "", val _dc: Double = 0.0,
) )
@@ -0,0 +1,12 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface ReleaseService {
@GET("my-tv/v2/release/{name}")
fun getRelease(
@Path("name") date: String = "1",
): Call<ReleaseV2>
}
@@ -1,11 +0,0 @@
package com.lizongying.mytv.api
data class Token2(
val ret: Int?,
val msg: String?,
val data: Data,
) {
data class Data(
val token: String,
)
}
@@ -1,11 +1,10 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
import android.content.Context import android.content.Context
import android.util.Log import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.SP import com.lizongying.mytv.SP
import com.lizongying.mytv.Utils.getDateTimestamp import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest
import kotlin.math.floor import kotlin.math.floor
import kotlin.random.Random import kotlin.random.Random
@@ -54,14 +53,6 @@ object YSP {
private var appid = "ysp_pc" private var appid = "ysp_pc"
var token = "" var token = ""
var token2 = ""
var yspsdkinput = ""
var openapi_signature = ""
var yspticket = ""
var nseqId = 1
var nrequest_id = ""
private var encryptor = Encryptor() private var encryptor = Encryptor()
@@ -85,45 +76,10 @@ object YSP {
cKey = cKey =
encryptor.encrypt(cnlid, timeStr, appVer, guid, platform) encryptor.encrypt(cnlid, timeStr, appVer, guid, platform)
randStr = getRand()
signature = getSignature() signature = getSignature()
yspticket = encryptor.encrypt2("$livepid&$timeStr&$guid&519748109").lowercase()
return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}""" return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}"""
} }
fun getTokenData(tvModel: TVViewModel) {
livepid = tvModel.getTV().pid
cnlid = tvModel.getTV().sid
defn = "fhd"
randStr = getRand()
if (tvModel.retryTimes > 0) {
guid = newGuid()
}
cKey =
encryptor.encrypt(cnlid, timeStr, appVer, guid, platform)
yspsdkinput =
md("adjust=$adjust&app_version=$appVersion&appVer=$appVer&channel=$channel&cKey=$cKey&cmd=$cmd&cnlid=$cnlid&defn=$defn&devid=$devid&dtype=$dtype&encryptVer=$encryptVer&guid=$guid&livepid=$livepid&otype=$otype&platform=$platform&sphttps=$sphttps&stream=$stream")
nseqId++
nrequest_id = "999999" + getRand() + getTimeStr()
openapi_signature =
md("yspappid:519748109;host:www.yangshipin.cn;protocol:https:;token:$token2;input:$yspsdkinput-$guid-$nseqId-$nrequest_id;")
}
fun md(str: String): String {
val md = MessageDigest.getInstance("MD5")
md.update(str.toByteArray())
val digest = md.digest()
return digest.let { it -> it.joinToString("") { "%02x".format(it) } }
}
fun getAuthData(tvModel: TVViewModel): String { fun getAuthData(tvModel: TVViewModel): String {
livepid = tvModel.getTV().pid livepid = tvModel.getTV().pid
@@ -137,7 +93,7 @@ object YSP {
return """pid=$livepid&guid=$guid&appid=$appid&rand_str=$randStr&signature=$signature""" return """pid=$livepid&guid=$guid&appid=$appid&rand_str=$randStr&signature=$signature"""
} }
fun getTimeStr(): String { private fun getTimeStr(): String {
return getDateTimestamp().toString() return getDateTimestamp().toString()
} }
@@ -151,7 +107,6 @@ object YSP {
var guid = SP.guid var guid = SP.guid
if (guid.length < 18) { if (guid.length < 18) {
guid = generateGuid() guid = generateGuid()
this.guid = guid
SP.guid = guid SP.guid = guid
} }
return guid return guid
@@ -2,11 +2,9 @@ package com.lizongying.mytv.api
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.Headers import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Query
interface YSPApiService { interface YSPApiService {
@@ -20,12 +18,6 @@ interface YSPApiService {
fun getLiveInfo( fun getLiveInfo(
@Header("cookie") cookie: String, @Header("cookie") cookie: String,
@Header("Yspplayertoken") token: String, @Header("Yspplayertoken") token: String,
@Header("Yspsdkinput") Yspsdkinput: String,
@Header("yspsdksign") yspsdksign: String,
@Header("Yspticket") Yspticket: String,
@Header("Seqid") Seqid: String,
@Header("Request-Id") RequestId: String,
@Header("Yspappid") Yspappid: String="519748109",
@Body request: LiveInfoRequest, @Body request: LiveInfoRequest,
): Call<LiveInfo> ): Call<LiveInfo>
@@ -40,20 +32,4 @@ interface YSPApiService {
@Header("cookie") cookie: String, @Header("cookie") cookie: String,
@Body request: AuthRequest, @Body request: AuthRequest,
): Call<Auth> ): Call<Auth>
@GET("web/open/token")
@Headers(
"content-type: application/json",
"referer: https://www.yangshipin.cn/",
"user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
fun getToken(
@Header("cookie") cookie: String,
@Query("yspappid") yspappid: String = "519748109",
@Query("guid") guid: String = "",
@Query("vappid") vappid: String = "59306155",
@Query("vsecret") vsecret: String = "b42702bf7309a179d102f3d51b1add2fda0bc7ada64cb801",
@Query("raw") raw: String = "1",
@Query("ts") ts: String = "",
): Call<Token2>
} }
@@ -87,8 +87,8 @@ interface YSPBtraceService {
"referer: https://www.yangshipin.cn/", "referer: https://www.yangshipin.cn/",
) )
fun kvcollect3( fun kvcollect3(
@Field("BossId") BossId: String = "9150", @Field("BossId") BossId: String = "9141",
@Field("Pwd") Pwd: String = "1999332929", @Field("Pwd") Pwd: String = "1137850982",
@Field("prog") prog: String = "", // @Field("prog") prog: String = "", //
@Field("playno") playno: String = "", // @Field("playno") playno: String = "", //
@Field("guid") guid: String = "", // @Field("guid") guid: String = "", //
@@ -102,7 +102,7 @@ interface YSPBtraceService {
@Field("firstreport") firstreport: String = "-", @Field("firstreport") firstreport: String = "-",
@Field("sUrl") sUrl: String = "", // @Field("sUrl") sUrl: String = "", //
@Field("sRef") sRef: String = "-", @Field("sRef") sRef: String = "-",
@Field("fplayerver") fplayerver: String = "180", @Field("fplayerver") fplayerver: String = "100",
@Field("livepid") livepid: String = "", @Field("livepid") livepid: String = "",
@Field("viewid") viewid: String = "", @Field("viewid") viewid: String = "",
@Field("seq") seq: String = "", @Field("seq") seq: String = "",
@@ -118,8 +118,8 @@ interface YSPBtraceService {
@Field("_dc") _dc: String = "", @Field("_dc") _dc: String = "",
@Field("live_type") live_type: String = "-", @Field("live_type") live_type: String = "-",
@Field("ftime") ftime: String = "", @Field("ftime") ftime: String = "",
// @Field("url") url: String = "", @Field("url") url: String = "",
// @Field("rand_str") rand_str: String = "", // @Field("rand_str") rand_str: String = "", //
// @Field("signature") signature: String = "", @Field("signature") signature: String = "",
): Call<Void> ): Call<Void>
} }
@@ -1,19 +0,0 @@
package com.lizongying.mytv.models
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
private var _downloadProgress = MutableLiveData<Int>()
val downloadProgress: LiveData<Int>
get() = _downloadProgress
fun setDownloadProgress(progress: Int) {
_downloadProgress.value = progress
}
init {
_downloadProgress.value = 0
}
}
@@ -3,7 +3,6 @@ package com.lizongying.mytv.models
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.lizongying.mytv.SP
class TVListViewModel : ViewModel() { class TVListViewModel : ViewModel() {
@@ -17,6 +16,10 @@ class TVListViewModel : ViewModel() {
val itemPosition: LiveData<Int> val itemPosition: LiveData<Int>
get() = _itemPosition get() = _itemPosition
private val _itemPositionCurrent = MutableLiveData<Int>()
val itemPositionCurrent: LiveData<Int>
get() = _itemPositionCurrent
fun addTVViewModel(tvViewModel: TVViewModel) { fun addTVViewModel(tvViewModel: TVViewModel) {
if (_tvListViewModel.value == null) { if (_tvListViewModel.value == null) {
_tvListViewModel.value = mutableListOf(tvViewModel) _tvListViewModel.value = mutableListOf(tvViewModel)
@@ -29,16 +32,23 @@ class TVListViewModel : ViewModel() {
return _tvListViewModel.value?.get(id) return _tvListViewModel.value?.get(id)
} }
fun getTVViewModelCurrent(): TVViewModel? {
return _itemPositionCurrent.value?.let { _tvListViewModel.value?.get(it) }
}
fun setItemPosition(position: Int) { fun setItemPosition(position: Int) {
_itemPosition.value = position _itemPosition.value = position
SP.itemPosition = position _itemPositionCurrent.value = position
}
fun setItemPositionCurrent(position: Int) {
_itemPositionCurrent.value = position
} }
fun size(): Int { fun size(): Int {
if (_tvListViewModel.value == null) { if (_tvListViewModel.value == null) {
return 0 return 0
} }
return _tvListViewModel.value!!.size return _tvListViewModel.value!!.size
} }
} }
@@ -1,8 +1,10 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.lizongying.mytv.TV
import com.lizongying.mytv.api.FEPG import com.lizongying.mytv.api.FEPG
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
import com.tencent.videolite.android.datamodel.cctvjce.TVProgram import com.tencent.videolite.android.datamodel.cctvjce.TVProgram
@@ -16,8 +18,6 @@ class TVViewModel(private var tv: TV) : ViewModel() {
var retryTimes = 0 var retryTimes = 0
var retryMaxTimes = 8 var retryMaxTimes = 8
var authYSPRetryTimes = 0
var authYSPRetryMaxTimes = 3
var tokenYSPRetryTimes = 0 var tokenYSPRetryTimes = 0
var tokenYSPRetryMaxTimes = 0 var tokenYSPRetryMaxTimes = 0
var tokenFHRetryTimes = 0 var tokenFHRetryTimes = 0
@@ -41,8 +41,8 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val videoIndex: LiveData<Int> val videoIndex: LiveData<Int>
get() = _videoIndex get() = _videoIndex
private val _change = MutableLiveData<String>() private val _change = MutableLiveData<Boolean>()
val change: LiveData<String> val change: LiveData<Boolean>
get() = _change get() = _change
private val _ready = MutableLiveData<Boolean>() private val _ready = MutableLiveData<Boolean>()
@@ -56,27 +56,36 @@ class TVViewModel(private var tv: TV) : ViewModel() {
if (_videoUrl.value!!.last().contains("cctv.cn")) { if (_videoUrl.value!!.last().contains("cctv.cn")) {
tv.videoUrl = tv.videoUrl.subList(0, tv.videoUrl.lastIndex) + listOf(url) tv.videoUrl = tv.videoUrl.subList(0, tv.videoUrl.lastIndex) + listOf(url)
} else { } else {
tv.videoUrl += listOf(url) tv.videoUrl = tv.videoUrl + listOf(url)
} }
} else { } else {
tv.videoUrl += listOf(url) tv.videoUrl = tv.videoUrl + listOf(url)
} }
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex _videoIndex.value = tv.videoUrl.lastIndex
} }
fun changed(from: String) { fun firstSource() {
retryTimes = 0 if (_videoUrl.value!!.isNotEmpty()) {
authYSPRetryTimes = 0 setVideoIndex(0)
tokenYSPRetryTimes = 0 allReady()
tokenFHRetryTimes = 0 } else {
_change.value = from Log.e(TAG, "no first")
}
}
fun changed() {
_change.value = true
} }
fun allReady() { fun allReady() {
_ready.value = true _ready.value = true
} }
fun setVideoIndex(videoIndex: Int) {
_videoIndex.value = videoIndex
}
init { init {
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex _videoIndex.value = tv.videoUrl.lastIndex
@@ -10,23 +10,20 @@ import retrofit2.Response
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class ReleaseRequest { class MyRequest {
private var releaseService = ApiClient().releaseService private var releaseService = ApiClient().releaseService
suspend fun getRelease(): ReleaseResponse? { suspend fun getRelease(): ReleaseV2? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
fetchRelease() fetchRelease()
} }
} }
private suspend fun fetchRelease(): ReleaseResponse? { private suspend fun fetchRelease(): ReleaseV2? {
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
releaseService.getRelease() releaseService.getRelease()
.enqueue(object : Callback<ReleaseResponse> { .enqueue(object : Callback<ReleaseV2> {
override fun onResponse( override fun onResponse(call: Call<ReleaseV2>, response: Response<ReleaseV2>) {
call: Call<ReleaseResponse>,
response: Response<ReleaseResponse>
) {
if (response.isSuccessful) { if (response.isSuccessful) {
continuation.resume(response.body()) continuation.resume(response.body())
} else { } else {
@@ -34,7 +31,7 @@ class ReleaseRequest {
} }
} }
override fun onFailure(call: Call<ReleaseResponse>, t: Throwable) { override fun onFailure(call: Call<ReleaseV2>, t: Throwable) {
continuation.resume(null) continuation.resume(null)
} }
}) })
@@ -42,6 +39,6 @@ class ReleaseRequest {
} }
companion object { companion object {
private const val TAG = "ReleaseRequest" private const val TAG = "MyRequest"
} }
} }
@@ -1,7 +0,0 @@
package com.lizongying.mytv.requests
data class ReleaseResponse(
val version_code: Int?,
val version_name: String?,
)
@@ -1,10 +0,0 @@
package com.lizongying.mytv.requests
import retrofit2.Call
import retrofit2.http.GET
interface ReleaseService {
@GET("JELLY_BEAN_MR1/version.json")
fun getRelease(
): Call<ReleaseResponse>
}
@@ -1,6 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" /> <solid android:color="@color/white" />
<corners <corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape> </shape>
@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FF263238" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/focus" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -1,6 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
-23
View File
@@ -1,23 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="50"
android:viewportHeight="50"
android:width="62.5dp"
android:height="62.5dp">
<group
android:scaleX="0.1"
android:scaleY="-0.1"
android:translateY="50">
<path
android:pathData="M205 377c-27 -27 -40 -33 -65 -29 -30 4 -70 -19 -70 -40 0 -5 -16 -24 -35 -42 -38 -36 -44 -66 -23 -111 22 -49 51 -55 248 -55l182 0 29 29c46 46 37 106 -20 136 -20 10 -31 25 -35 49 -4 22 -20 46 -42 65 -29 26 -43 31 -85 31 -44 0 -55 -4 -84 -33zm138 2c29 -13 57 -58 57 -93 0 -17 5 -26 15 -26 24 0 65 -46 65 -71 0 -13 -10 -33 -22 -46 -21 -22 -26 -23 -205 -23 -182 0 -184 0 -208 25 -33 32 -32 70 3 105 16 15 36 40 45 54 18 27 54 35 74 15 8 -8 16 -2 29 19 9 17 27 35 38 41 28 13 80 13 109 0z"
android:fillColor="#FFEEEEEE" />
<path
android:pathData="M174 269c-10 -17 13 -36 27 -22 12 12 4 33 -11 33 -5 0 -12 -5 -16 -11z"
android:fillColor="#FFEEEEEE" />
<path
android:pathData="M294 269c-10 -17 13 -36 27 -22 12 12 4 33 -11 33 -5 0 -12 -5 -16 -11z"
android:fillColor="#FFEEEEEE" />
<path
android:pathData="M194 196c-32 -14 -50 -33 -41 -42 3 -3 20 4 38 15 43 26 75 26 118 0 34 -20 49 -20 36 1 -10 16 -69 40 -97 40 -13 -1 -37 -7 -54 -14z"
android:fillColor="#FFEEEEEE" />
</group>
</vector>
-60
View File
@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card"
android:layout_width="150dp"
android:layout_height="100dp"
android:orientation="vertical"
android:layout_gravity="center_vertical"
>
<ImageView
android:id="@+id/icon"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/rounded_white_top"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:padding="5dp" />
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/icon"
android:background="@drawable/rounded_dark_bottom"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="start"
android:text="Title"
android:maxLines="1"
android:ellipsize="end"
android:textColor="#FFEEEEEE"
android:textSize="14sp" />
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
android:layout_marginTop="8dp"
android:gravity="start"
android:maxLines="1"
android:ellipsize="end"
android:text="Description"
android:textColor="#B3EEEEEE"
android:textSize="12sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+17 -42
View File
@@ -1,44 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/channel_fragment"
android:layout_width="match_parent" android:layout_width="90dp"
android:layout_height="match_parent" android:layout_height="60dp"
android:gravity="center" android:layout_gravity="end|top"
> android:layout_marginTop="20dp"
<FrameLayout android:layout_marginEnd="170dp">
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<RelativeLayout <TextView
android:id="@+id/channel" android:id="@+id/channel_content"
android:layout_width="90dp" android:layout_width="wrap_content"
android:layout_height="60dp" android:layout_height="wrap_content"
android:layout_gravity="end|top" android:layout_gravity="end"
android:layout_marginTop="15dp" android:layout_marginTop="0dp"
android:layout_marginEnd="170dp" android:textColor="#FFEEEEEE"
android:gravity="end|bottom" > android:textStyle="bold"
android:textSize="40sp" />
<TextView </FrameLayout>
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="40sp"
tools:text="99"
android:layout_alignBaseline="@+id/time"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="32sp"
tools:text="12:34" />
</RelativeLayout>
</FrameLayout>
</LinearLayout>
-24
View File
@@ -1,24 +0,0 @@
<!-- res/layout/fragment_progress_dialog.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:padding="20dp">
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_update"
tools:text="开始更新">
</Button>
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:progressDrawable="@android:drawable/progress_horizontal" />
</LinearLayout>
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" >
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" >
<ImageView
android:id="@+id/logo"
android:src="@drawable/sad_cloud"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#FFEEEEEE"
android:text="Message"
android:textSize="20sp" />
</LinearLayout>
</FrameLayout>
+48 -55
View File
@@ -1,60 +1,53 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:id="@+id/info_fragment"
android:layout_height="match_parent" android:layout_width="320dp"
android:gravity="center" android:layout_height="80dp"
> android:layout_gravity="center_horizontal|bottom"
<FrameLayout android:layout_marginBottom="20dp"
android:id="@+id/container" android:background="@drawable/rounded_background">
android:layout_width="match_parent"
<ImageView
android:id="@+id/info_logo"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:padding="10dp" />
<LinearLayout
android:layout_width="216dp"
android:layout_height="match_parent" android:layout_height="match_parent"
> android:layout_marginStart="100dp"
android:background="#FF263238"
android:gravity="start|center_vertical"
android:orientation="vertical"
android:padding="7dp">
<LinearLayout <TextView
android:id="@+id/info" android:id="@+id/textView"
android:layout_width="400dp" android:layout_width="wrap_content"
android:layout_height="80dp" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="start"
android:layout_marginBottom="20dp" android:layout_marginTop="0dp"
android:orientation="horizontal"> android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<ImageView <TextView
android:id="@+id/logo" android:id="@+id/info_desc"
android:layout_width="100dp" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:background="@drawable/rounded_white_left" android:layout_gravity="start"
android:padding="10dp" /> android:layout_marginTop="8dp"
android:gravity="start"
android:singleLine="true"
android:textColor="#B3EEEEEE"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout <View
android:id="@+id/main" android:layout_width="8dp"
android:layout_width="300dp" android:layout_height="wrap_content"
android:layout_height="match_parent" android:layout_gravity="end"
android:layout_marginStart="0dp" android:background="@drawable/rounded_background2" />
android:background="@drawable/rounded_dark_right" </FrameLayout>
android:gravity="start|center_vertical"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="20sp" />
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="8dp"
android:gravity="start"
android:singleLine="true"
android:textColor="#B3EEEEEE"
android:textSize="14sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</LinearLayout>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
>
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center" >
<ProgressBar
android:id="@+id/bar"
android:layout_width="50dp"
android:layout_height="50dp"
android:indeterminateTint="@color/white"/>
</LinearLayout>
</FrameLayout>
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="false"
android:gravity="center"
>
<FrameLayout
android:id="@+id/menu"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ScrollView
android:id="@+id/scroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>
+10 -1
View File
@@ -1,11 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player" android:id="@+id/player_fragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black" android:background="@color/black"
android:keepScreenOn="true"> android:keepScreenOn="true">
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:resize_mode="fill"
app:use_controller="false" />
<androidx.media3.ui.PlayerView <androidx.media3.ui.PlayerView
android:id="@+id/player_view" android:id="@+id/player_view"
android:layout_width="match_parent" android:layout_width="match_parent"
-29
View File
@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/transparent"
android:clickable="true"
android:focusable="false"
>
<TextView
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:layout_marginStart="20dp"
android:layout_marginBottom="5dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/items"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
+17 -53
View File
@@ -1,28 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/setting" android:id="@+id/dialog"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<ScrollView <LinearLayout
android:id="@+id/container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:fillViewport="true"
>
<LinearLayout <LinearLayout
android:id="@+id/content"
android:layout_width="300dp" android:layout_width="300dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="20dp" android:paddingTop="20dp"
android:paddingBottom="20dp" android:paddingLeft="7dp"
android:paddingLeft="10dp" android:paddingRight="7dp"
android:paddingRight="10dp" android:background="#FF263238"
android:background="@drawable/rounded_dark_left"
> >
<TextView <TextView
android:id="@+id/name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/app_name" android:text="@string/app_name"
@@ -33,10 +27,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/" android:text="当前版本: $versionName\n获取最新: https://github.com/lizongying/my-tv/releases/"
android:textSize="14sp"
/> />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="300dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
> >
@@ -44,7 +37,7 @@
android:id="@+id/check_version" android:id="@+id/check_version"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="14sp" android:layout_marginEnd="10dp"
android:text="@string/check_version" android:text="@string/check_version"
/> />
<ProgressBar <ProgressBar
@@ -55,74 +48,45 @@
<TextView <TextView
android:id="@+id/version_name" android:id="@+id/version_name"
android:text="" android:text=""
android:textSize="14sp"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<!-- <androidx.core.widget.ContentLoadingProgressBar-->
<!-- android:id="@+id/progressBar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:indeterminate="false"-->
<!-- android:max="100"-->
<!-- android:progress="0"-->
<!-- android:visibility="gone"-->
<!-- android:progressDrawable="@android:drawable/progress_horizontal" />-->
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="@string/clear"
/>
<Button <Button
android:id="@+id/exit" android:id="@+id/exit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="14sp" android:layout_marginEnd="10dp"
android:text="@string/exit" android:text="@string/exit"
/> />
<Switch <Switch
android:id="@+id/switch_channel_reversal" android:id="@+id/switch_channel_reversal"
android:text="@string/title_channel_reversal" android:text="@string/title_channel_reversal"
android:textSize="14sp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Switch <Switch
android:id="@+id/switch_channel_num" android:id="@+id/switch_channel_num"
android:text="@string/title_channel_num" android:text="@string/title_channel_num"
android:textSize="14sp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Switch <Switch
android:id="@+id/switch_time" android:id="@+id/switch_time"
android:text="@string/title_time" android:text="@string/title_time"
android:textSize="14sp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
<Switch <Switch
android:id="@+id/switch_boot_startup" android:id="@+id/switch_boot_startup"
android:text="@string/title_boot_startup" android:text="@string/title_boot_startup"
android:textSize="14sp"
android:layout_marginTop="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Switch
android:id="@+id/switch_grid"
android:text="@string/title_grid"
android:textSize="14sp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
</ScrollView> <ImageView
<ImageView android:layout_width="300dp"
android:id="@+id/appreciate" android:layout_height="wrap_content"
android:layout_width="300dp" android:src="@drawable/appreciate"
android:layout_height="wrap_content" android:background="@color/white" />
android:src="@drawable/appreciate" </LinearLayout>
android:background="@drawable/rounded_white_right" /> </FrameLayout>
</LinearLayout>
+17 -43
View File
@@ -1,45 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/time"
android:layout_width="match_parent" android:layout_width="100dp"
android:layout_height="match_parent" android:layout_height="40dp"
android:gravity="center" android:layout_gravity="end|top"
> android:layout_marginTop="25dp"
<FrameLayout android:layout_marginEnd="50dp">
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<RelativeLayout <TextView
android:id="@+id/time" android:id="@+id/content"
android:layout_width="120dp" android:layout_width="wrap_content"
android:layout_height="60dp" android:layout_height="wrap_content"
android:layout_gravity="end|top" android:layout_gravity="end"
android:layout_marginTop="15dp" android:layout_marginTop="0dp"
android:layout_marginEnd="50dp" android:textColor="#FFEEEEEE"
android:gravity="end|bottom" > android:textStyle="bold"
android:textSize="32sp" />
<TextView </FrameLayout>
android:id="@+id/channel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="40sp"
tools:text="99"
android:layout_alignBaseline="@+id/content"/>
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="32sp"
tools:text="12:34" />
</RelativeLayout>
</FrameLayout>
</LinearLayout>
+2 -2
View File
@@ -1,7 +1,7 @@
<resources> <resources>
<color name="focus">#0096a6</color>
<color name="fastlane_background">#30000000</color>
<color name="black">#000</color> <color name="black">#000</color>
<color name="white">#FFF</color> <color name="white">#FFF</color>
<color name="focus">#0096a6</color>
<color name="blur">#223239</color>
<color name="ic_launcher_background">#223239</color> <color name="ic_launcher_background">#223239</color>
</resources> </resources>
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="server_url">http://localhost:8080</string>
</resources> </resources>
+2 -4
View File
@@ -4,9 +4,7 @@
<string name="title_channel_num">换台时显示频道号</string> <string name="title_channel_num">换台时显示频道号</string>
<string name="check_version">检查更新</string> <string name="check_version">检查更新</string>
<string name="title_boot_startup">开机自启</string> <string name="title_boot_startup">开机自启</string>
<string name="title_grid">频道列表采用网格样式</string> <string name="dismiss_error">好的</string>
<string name="title_time">显示时间</string> <string name="title_time">显示时间</string>
<string name="clear">恢复默认</string> <string name="exit">退出</string>
<string name="exit">退出应用</string>
<string name="start_update">开始更新</string>
</resources> </resources>
+7
View File
@@ -1,3 +1,10 @@
<resources> <resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="lbImageCardViewType">Title|Content</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
</style>
</resources> </resources>
+3 -1
View File
@@ -1,6 +1,8 @@
<resources> <resources>
<style name="Theme.MyTV" parent="@style/Theme.Leanback"> <style name="Theme.MyTV" parent="@style/Theme.Leanback">
<item name="android:fitsSystemWindows">false</item> <item name="browsePaddingStart">11dp</item>
<item name="browsePaddingEnd">11dp</item>
<item name="browseRowsMarginTop">54dp</item>
</style> </style>
</resources> </resources>
-3
View File
@@ -1,3 +0,0 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
Executable → Regular
View File
+1 -1
View File
@@ -1 +1 @@
{"version_code": 33620480, "version_name": "v2.1.2"} {"version_code": 33619968, "version_name": "v2.1.0"}