Compare commits

...
33 Commits
Author SHA1 Message Date
Li ZongYing 5d4353249f fix ysp 2024-06-20 13:43:14 +08:00
Li ZongYing a8944e75b1 ysp 2024-06-08 11:23:31 +08:00
Li ZongYing d1eae8947e ysp 2024-06-08 10:50:22 +08:00
Li ZongYing fccd64c07b ysp 2024-06-08 10:49:10 +08:00
Li ZongYing eac355a1c1 fix black screen 2024-06-07 17:49:49 +08:00
Li ZongYing 6c6736fd0a gradle lib version 2024-06-07 17:22:42 +08:00
Li ZongYing f4c1db33b3 ysp 2024-06-07 13:13:05 +08:00
Li ZongYing e55584c910 Balanced sounds 2024-06-03 18:17:43 +08:00
Li ZongYing cf8d7f00a8 fix ysp 2024-05-31 11:10:18 +08:00
Li ZongYing bcfca9e28c fix gson crash 2024-05-27 14:13:34 +08:00
Li ZongYing 31ec13eed7 fix gson crash 2024-05-26 20:50:08 +08:00
Li ZongYing 5a78913407 fix gson crash 2024-05-26 20:49:01 +08:00
Li ZongYing bb38cf5033 fix gson crash 2024-05-26 20:20:42 +08:00
Li ZongYing 51d9c4c2a7 fix gson crash 2024-05-26 20:16:10 +08:00
Li ZongYing 70da1d8241 fix the settings options are not displayed 2024-05-26 09:36:24 +08:00
Li ZongYing ef17427f64 add reset 2024-05-21 09:55:15 +08:00
Li ZongYing c1143afb4e fix ysp 2024-05-20 22:23:16 +08:00
Li ZongYing 68f158e556 fix ysp 2024-05-20 22:08:37 +08:00
Li ZongYing 4ef95d60c4 fix ysp 2024-05-20 22:02:51 +08:00
Li ZongYing b782d1f841 fix some bug 2024-05-16 15:35:48 +08:00
Li ZongYing 7e57aa8c03 fix error in playing 2024-05-16 14:37:16 +08:00
Li ZongYing c16a4bcb9c optimize time 2024-05-09 22:26:37 +08:00
Li ZongYing 0286d118ed fix update 2024-05-07 17:55:08 +08:00
Li ZongYing fc83defe31 optimize version check 2024-05-07 12:56:34 +08:00
Li ZongYing 6fc73efd47 optimize version check 2024-05-07 12:55:11 +08:00
Li ZongYing b157f1eb28 fix time 2024-05-07 11:38:10 +08:00
Li ZongYing 2b172ae615 fix retry bug 2024-05-01 17:00:33 +08:00
Li ZongYing 39e5cc7c1f opitimize request 2024-05-01 16:52:33 +08:00
Li ZongYing df5b006024 optimize style 2024-04-29 12:12:59 +08:00
Li ZongYing fe888c36ac left->start 2024-04-27 19:11:08 +08:00
Li ZongYing 5aa08f51a1 error page 2024-04-26 19:57:12 +08:00
Li ZongYing fe92fa5853 optimize style 2024-04-25 22:36:37 +08:00
Li ZongYing 3d8ab65955 fix ysp 2024-04-23 19:41:30 +08:00
86 changed files with 2736 additions and 1129 deletions
+23
View File
@@ -37,6 +37,13 @@ jobs:
# override default build-tools version (29.0.3) -- optional # override default build-tools version (29.0.3) -- optional
BUILD_TOOLS_VERSION: "34.0.0" BUILD_TOOLS_VERSION: "34.0.0"
- name: Get History
id: get_history
run: |
chmod +x history.sh
output=$(./history.sh)
echo "$output" > history.md
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1
@@ -47,6 +54,7 @@ jobs:
release_name: Release ${{ github.ref }} release_name: Release ${{ github.ref }}
draft: false draft: false
prerelease: false prerelease: false
body_path: history.md
- name: Upload Release Asset - name: Upload Release Asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
@@ -57,3 +65,18 @@ jobs:
asset_path: ${{ steps.sign_app.outputs.signedReleaseFile }} asset_path: ${{ steps.sign_app.outputs.signedReleaseFile }}
asset_name: my-tv-${{ github.ref_name }}.apk asset_name: my-tv-${{ github.ref_name }}.apk
asset_content_type: application/vnd.android.package-archive asset_content_type: application/vnd.android.package-archive
- name: Gitee Create Release
run: |
latest_commit=$(git rev-parse HEAD)
history=$(cat history.md)
curl -v POST https://gitee.com/api/v5/repos/${{ github.repository }}/releases \
-H "Content-Type: application/json" \
-d '{
"access_token": "${{ secrets.GITEE_ACCESS_TOKEN}}",
"tag_name": "${{ github.ref_name }}",
"name": "Release ${{ github.ref_name }}",
"body": "'"$history"'",
"prerelease": false,
"target_commitish": "'"$latest_commit"'"
}'
+207 -119
View File
@@ -1,189 +1,277 @@
## 更新日志 ## 更新日誌
### v2.1.7
*
### v2.1.4
* 解決切換頻道時黑屏問題
### v2.1.1
* 平衡聲音
### v2.1.0
* 恢復播放
### 以下已不可用
### v2.0.9
* 修復部分設備閃退的問題
* 修復設置頁可能顯示不全的問題
### v2.0.5
* 設置頁增加恢復默認按鈕,目前只用於同步系統時間
### v2.0.4
* 修復不能播放的問題
* 包含國際頻道
### v2.0.3
* 修復不能播放的問題
* 不包含國際頻道
### v2.0.0
* 解決卡頓問題
* 解決網格樣式時,點擊空白處不退出的問題
* 解決頻道列表樣式切換可能不生效的問題
### v1.9.8(通用)
* 優化在線升級
* 優化可能狀態錯誤的問題
### v1.9.6(通用)
* 優化在線升級
* 優化可能狀態錯誤的問題
### v1.9.4(通用)
* 修復默認時間不顯示問題
* 設置頁居中
* 頻道號和時間對齊
### v1.9.2(通用)
* 修復一個重試的錯誤
### v1.9.0(通用)
* 減少視頻播放失敗情況
* 全面屏手機居中顯示
### v1.8.8(通用)
* 樣式優化
### v1.8.6(通用)
* 增加錯誤顯示
* 節目預告優化
* 頻道列表頁單擊切換頻道
### v1.8.4(通用)
* 解決部分情況下打開後黑屏問題
* 延長頻道信息顯示時間,增加信息顯示長度
* 調整頻道列表間距
* 其他樣式優化
### v1.8.2(通用)
* 修復無法播放問題
### v1.8.0(通用) ### v1.8.0(通用)
* 修复返回键无法退出问题 * 修復返回鍵無法退出問題
* 设置中增加退出按钮 * 設置中增加退出按鈕
* 修改非电视上的样式 * 修改非電視上的樣式
* 修復低版本閃退問題
### v1.7.8(通用) ### v1.7.8(通用)
* 修复播放过程中的卡顿问题 * 修復播放過程中的卡頓問題
* 增加时间显示 * 增加時間顯示
* 频道号优化 * 頻道號優化
* 遥控器左键打开频道列表 * 遙控器左鍵打開頻道列表
* 遥控器右键打开设置菜单 * 遙控器右鍵打開設置菜單
* 频道列表空白处点击隐藏频道列表 * 頻道列表空白處點擊隱藏頻道列表
### v1.7.6(通用) ### v1.7.6(通用)
* 临时去掉部分频道,提高频道的可用性 * 臨時去掉部分頻道,提高頻道的可用性
* 部分频道增加节目单 * 部分頻道增加節目單
### v1.7.5(安卓5及以上专用) ### v1.7.5(安卓5及以上專用)
* 修复播放过程中的卡顿问题 * 修復播放過程中的卡頓問題
* 增加时间显示 * 增加時間顯示
* 频道号优化 * 頻道號優化
* 设置中增加退出按钮 * 設置中增加退出按鈕
### v1.7.4(通用) ### v1.7.4(通用)
* 修复368 * 修復368
### v1.7.3(安卓5及以上专用) ### v1.7.3(安卓5及以上專用)
* 临时去掉部分频道,提高频道的可用性 * 臨時去掉部分頻道,提高頻道的可用性
* 部分频道增加节目单 * 部分頻道增加節目單
* 遥控器左键进入节目列表 * 遙控器左鍵進入節目列表
* 遥控器右键进入菜单 * 遙控器右鍵進入菜單
### v1.7.2(通用) ### v1.7.2(通用)
* 支持节目列表网格样式和行样式切换,软件重启后生效 * 支持節目列表網格樣式和行樣式切換,軟件重啟後生效
* 节目列表样式变更 * 節目列表樣式變更
### v1.7.1(安卓5及以上专用) ### v1.7.1(安卓5及以上專用)
* 解决设置页更新闪退的问题 * 解決設置頁更新閃退的問題
* 凤凰卫视回归 * 鳳凰衛視回歸
* 解决368问题 * 解決368問題
### v1.7.0(通用) ### v1.7.0(通用)
* 网络请求优化 * 網絡請求優化
### v1.6.9(安卓5及以上专用) ### v1.6.9(安卓5及以上專用)
* 去掉港澳台和国际频道 * 去掉港澳台和國際頻道
* 解决部分情况下3、6、8等频道无法播放的问题 * 解決部分情況下3、6、8等頻道無法播放的問題
* 解决部分情况下启动后黑屏问题 * 解決部分情況下啟動後黑屏問題
### v1.6.8(通用) ### v1.6.8(通用)
* 修复部分设备崩溃的问题 * 修復部分設備崩潰的問題
* 修复部分设备凤凰卫视无法播放的问题 * 修復部分設備鳳凰衛視無法播放的問題
### v1.6.7(安卓5及以上专用) ### v1.6.7(安卓5及以上專用)
* 手机双击打开配置 * 手機雙擊打開配置
* 自动更新 * 自動更新
### v1.6.6(通用) ### v1.6.6(通用)
* 更新重庆卫视图标 * 更新重慶衛視圖標
* 凤凰卫视增强画质 * 鳳凰衛視增強畫質
* 凤凰卫视增加EPG * 鳳凰衛視增加EPG
### v1.6.5(安卓5及以上专用) ### v1.6.5(安卓5及以上專用)
* 增加CETV1图标 * 增加CETV1圖標
* 稳定性提升 * 穩定性提升
### v1.6.4(通用) ### v1.6.4(通用)
* 增加CETV1 * 增加CETV1
* 增加凤凰卫视 * 增加鳳凰衛視
* 默认关闭开机启动 * 默認關閉開機啟動
### v1.6.3(安卓5及以上专用) ### v1.6.3(安卓5及以上專用)
* 增加CETV1 * 增加CETV1
* 凤凰卫视增强画质 * 鳳凰衛視增強畫質
* 默认关闭开机启动 * 默認關閉開機啟動
* 延迟菜单自动关闭时间 * 延遲菜單自動關閉時間
* 解决一些可能导致首次打开时黑屏的问题 * 解決一些可能導致首次打開時黑屏的問題
### v1.6.2(通用) ### v1.6.2(通用)
* 修复按键无效的问题 * 修復按鍵無效的問題
* 新的频道列表样式 * 新的頻道列表樣式
### v1.6.1(安卓5及以上专用) ### v1.6.1(安卓5及以上專用)
* 增加凤凰卫视 * 增加鳳凰衛視
### v1.6.0(通用) ### v1.6.0(通用)
* 通用(春晚緊急修復) * 通用(春晚緊急修復)
### v1.5.9(安卓5及以上专用) ### v1.5.9(安卓5及以上專用)
* 解决天猫魔盒闪退问题 * 解決天貓魔盒閃退問題
### v1.5.8(通用) ### v1.5.8(通用)
* 修复央视6画质差的问题 * 修復央視6畫質差的問題
* 增加兵团卫视 * 增加兵團衛視
* 播放失败重试 * 播放失敗重試
### v1.5.7(安卓5及以上专用) ### v1.5.7(安卓5及以上專用)
* 修复播放失败的问题 * 修復播放失敗的問題
### v1.5.6(通用) ### v1.5.6(通用)
* 解决部分设备系统时间不对导致播放失败的问题 * 解決部分設備系統時間不對導致播放失敗的問題
### v1.5.5(安卓5及以上专用) ### v1.5.5(安卓5及以上專用)
* 修复播放失败的问题 * 修復播放失敗的問題
* 修复APP恢复后频道号、频道列表不自动消失的问题 * 修復APP恢復後頻道號、頻道列表不自動消失的問題
### v1.5.4(通用) ### v1.5.4(通用)
* 修复播放失败的问题 * 修復播放失敗的問題
### 以下已不可用 ### v1.5.3(安卓5及以上專用)
### v1.5.3(安卓5及以上专用) * 修復部分情況下APP切換後無法繼續播放的問題
* 優化重試邏輯
* 修复部分情况下APP切换后无法继续播放的问题
* 优化重试逻辑
### v1.5.2(通用) ### v1.5.2(通用)
* 修复APP恢复后频道号、频道列表不自动消失的问题 * 修復APP恢復後頻道號、頻道列表不自動消失的問題
### v1.5.1(安卓5及以上专用) ### v1.5.1(安卓5及以上專用)
* 性能优化 * 性能優化
### v1.5.0(通用) ### v1.5.0(通用)
* 修复部分情况下APP切换后无法继续播放的问题 * 修復部分情況下APP切換後無法繼續播放的問題
### v1.4.9(安卓5及以上专用) ### v1.4.9(安卓5及以上專用)
* 同步v1.4.8 * 同步v1.4.8
### v1.4.8(通用) ### v1.4.8(通用)
* 频道号从1开始,CCTV5+为18 * 頻道號從1開始,CCTV5+為18
* 提高CCTV6清晰度 * 提高CCTV6清晰度
* 增加天津卫视、新疆卫视 * 增加天津衛視、新疆衛視
### v1.4.7(安卓5及以上专用) ### v1.4.7(安卓5及以上專用)
* 修复部分用户CCTV13播放过程中卡住的问题 * 修復部分用戶CCTV13播放過程中卡住的問題
* 调整CCTV的频道顺序 * 調整CCTV的頻道順序
### v1.4.6(通用) ### v1.4.6(通用)
* 10以下频道不再需要先按0 * 10以下頻道不再需要先按0
### v1.4.5(安卓5及以上专用) ### v1.4.5(安卓5及以上專用)
* 数字选台配置 * 數字選台配置
### v1.4.4(通用) ### v1.4.4(通用)
* 优化图标显示 * 優化圖標顯示
* 增加换台反转 * 增加換台反轉
### v1.4.3(安卓5及以上专用) ### v1.4.3(安卓5及以上專用)
* 支持频道反转配置 * 支持頻道反轉配置
### v1.4.2(通用) ### v1.4.2(通用)
@@ -191,40 +279,40 @@
### v1.4.1 ### v1.4.1
* 解决部分用户无法打开菜单的问题 * 解決部分用戶無法打開菜單的問題
### v1.4.0 ### v1.4.0
* 解决极个别高版本机型黑屏问题 * 解決極個別高版本機型黑屏問題
### v1.3.9 ### v1.3.9
* 提高稳定性 * 提高穩定性
* 提高连接速度 * 提高連接速度
### v1.3.4 ### v1.3.4
* 部分错误会提示用户 * 部分錯誤會提示用戶
* 菜单3秒钟后自动关闭 * 菜單3秒鐘後自動關閉
### v1.3.3 ### v1.3.3
* 部分错误会提示用户 * 部分錯誤會提示用戶
* 菜单3秒钟后自动关闭 * 菜單3秒鐘後自動關閉
### v1.3.2 ### v1.3.2
* 增加重试,减少因网络问题导致的播放失败 * 增加重試,減少因網絡問題導致的播放失敗
* 优化横幅banner * 優化橫幅banner
### v1.3.1 ### v1.3.1
* 增加CCTV 8K 超高清频道 * 增加CCTV 8K 超高清頻道
* 增加国际频道栏目 * 增加國際頻道欄目
### v1.3.0 ### v1.3.0
* 处理368可能失败的情形 * 處理368可能失敗的情形
### v1.2.9 ### v1.2.9
@@ -236,32 +324,32 @@
### v1.2.7 ### v1.2.7
* 此版本是为了测试安卓6.0以下版本,如之前可以正常运行,请勿安装。 * 此版本是為了測試安卓6.0以下版本,如之前可以正常運行,請勿安裝。
### v1.2.6 ### v1.2.6
* 支持安卓4.2 * 支持安卓4.2
* 解决部分频道无法播放的问题 * 解決部分頻道無法播放的問題
* 修复切换时有时没有恢复播放的问题 * 修復切換時有時沒有恢復播放的問題
* 左右键不再切换源 * 左右鍵不再切換源
* 增大频道信息标题尺寸,缩小与节目信息的间隔 * 增大頻道信息標題尺寸,縮小與節目信息的間隔
### v1.2.5 ### v1.2.5
* 美化频道信息显示 * 美化頻道信息顯示
* 优化节目单获取 * 優化節目單獲取
### v1.2.4 ### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑 * 改變換台滑動方向,上一個頻道下滑,下一個頻道上滑
* 软件退出时,退出播放器 * 軟件退出時,退出播放器
* 播放相同的频道,不再重复加载 * 播放相同的頻道,不再重複加載
* 暂时移除部分频道 * 暫時移除部分頻道
### v1.2.3 ### v1.2.3
* 固定视频比列为16:9 * 固定視頻比例為16:9
* 隐藏全面屏底部的小横条 * 隱藏全面屏底部的小橫條
* 移除移动专区 * 移除移動專區
* 修复一个闪退问题 * 修復一個閃退問題
* 更换图标和应用名 * 更換圖標和應用名
+9 -1
View File
@@ -1,6 +1,6 @@
.PHONY: all .PHONY: all
all: info all: info gen-version
branch := $(shell git rev-parse --abbrev-ref HEAD) branch := $(shell git rev-parse --abbrev-ref HEAD)
commit := $(shell git rev-parse --short HEAD) commit := $(shell git rev-parse --short HEAD)
@@ -9,3 +9,11 @@ info:
@echo 'SHELL='$(SHELL) @echo 'SHELL='$(SHELL)
@echo 'branch='$(branch) @echo 'branch='$(branch)
@echo 'commit='$(commit) @echo 'commit='$(commit)
gen-version:
git describe --tags --always
git describe --tags --always | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print ($$1*16777216)+($$2*65536)+($$3*256)+$$4}'
#make gen v=v2.0.0
gen:
echo $(v) | sed 's/v/ /g' | sed 's/\./ /g' | sed 's/-/ /g' | awk '{print "{\"version_code\": " ($$1*16777216)+($$2*65536)+($$3*256)+$$4 ", \"version_name\": \"" "v$(v)" "\"}"}' > version.json
+13 -18
View File
@@ -4,17 +4,16 @@
## 使用 ## 使用
下载安装: 1. 下载
[github](https://github.com/lizongying/my-tv/releases/) * [github](https://github.com/lizongying/my-tv/releases/)
[gitee](https://gitee.com/lizongying/my-tv/releases/) * [gitee](https://gitee.com/lizongying/my-tv/releases/)
2. 安裝
小米电视可以使用小米电视助手进行安装 * U盘安装
* 小米电视可以使用小米电视助手进行安装
如电视可以启用ADB,也可以通过ADB进行安装: * 如电视可以启用ADB,也可以通过ADB进行安装
```shell
```shell adb install my-tv.apk
adb install my-tv.apk ```
```
![image](./screenshots/img_3.png) ![image](./screenshots/img_3.png)
![image](./screenshots/img_2.png) ![image](./screenshots/img_2.png)
@@ -27,19 +26,15 @@ adb install my-tv.apk
## TODO ## TODO
* 音量不同 * 音量不同
* 大湾区卫视、广东4k超高清、广东珠江、三沙卫视 * 大湾区卫视、广东4k超高清、广东珠江、三沙卫视、翡翠
* CHC高清三个电影频道 * CHC高清三个电影频道
* 地方频道 * 地方频道
* 收藏夹 * 收藏夹
* 海外 * 海外
* 1.5.0 无法安装,1.5.1 可以安装 * 隐藏频道
* 节目增加预告
* 频道列表优化
* epg更新后菜单更新
* 防止菜单第一个获取焦点
* 隐藏
* 亮度调节 * 亮度调节
* 音量调节 * 音量调节
* 軟解
## 版权说明 ## 版权说明
+1
View File
@@ -4,3 +4,4 @@
/CMakeFiles/ /CMakeFiles/
/Makefile /Makefile
/src/main/cpp/native.c /src/main/cpp/native.c
/src/main/cpp/aes.c
+3 -1
View File
@@ -21,7 +21,9 @@ 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(
+25 -32
View File
@@ -26,6 +26,7 @@ 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"
} }
} }
@@ -77,10 +78,9 @@ 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 0 return 1
} }
} }
@@ -88,40 +88,33 @@ static def VersionName() {
try { try {
def process = "git describe --tags --always".execute() def process = "git describe --tags --always".execute()
process.waitFor() process.waitFor()
return process.text.trim() - "v" def versionName = 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"
} }
} }
dependencies { dependencies {
def media3_version = "1.2.1" implementation(libs.media3.ui)
implementation(libs.media3.exoplayer)
implementation "androidx.media3:media3-ui:$media3_version" implementation(libs.media3.exoplayer.hls)
implementation(libs.protobuf.kotlin)
// For media playback using ExoPlayer implementation(libs.gson)
implementation "androidx.media3:media3-exoplayer:$media3_version" implementation(libs.converter.gson)
implementation(libs.converter.protobuf)
// For HLS playback support with ExoPlayer implementation(libs.retrofit)
implementation "androidx.media3:media3-exoplayer-hls:$media3_version" implementation(libs.glide)
implementation(libs.work.runtime.ktx)
// 21:2.9.0 17:2.6.4 implementation(libs.core.ktx)
def retrofit2_version = "2.6.4" implementation(libs.multidex)
implementation(libs.leanback)
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1' implementation(libs.lifecycle.runtime.ktx)
implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version" implementation(libs.constraintlayout)
implementation "com.squareup.retrofit2:converter-protobuf:$retrofit2_version" implementation(libs.serialization.json)
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" implementation(libs.coroutines.android)
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation(libs.javax.annotation.api)
implementation 'androidx.core:core-ktx:1.11.0-beta02'
implementation 'androidx.leanback:leanback:1.2.0-alpha02'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
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'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
} }
+18 -1
View File
@@ -10,11 +10,14 @@
<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.READ_EXTERNAL_STORAGE" />
<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=".MyApplication" android:name=".MyTVApplication"
android:allowBackup="true" android:allowBackup="true"
android:banner="@drawable/banner" android:banner="@drawable/banner"
android:icon="@drawable/logo" android:icon="@drawable/logo"
@@ -36,6 +39,9 @@
<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
@@ -58,5 +64,16 @@
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
@@ -0,0 +1,9 @@
#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.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
@@ -1,18 +1,21 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.graphics.Color import android.util.Log
import android.view.ContextThemeWrapper
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.ScaleAnimation import android.view.animation.ScaleAnimation
import android.widget.ImageView import androidx.core.view.marginTop
import androidx.core.view.updatePadding import androidx.core.view.setPadding
import androidx.leanback.widget.ImageCardView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.CardBinding
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 kotlin.math.abs
class CardAdapter( class CardAdapter(
@@ -25,15 +28,31 @@ class CardAdapter(
private var listener: ItemListener? = null private var listener: ItemListener? = null
private var focused: View? = null private var focused: View? = null
var visiable = false var focusable = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cardView = object : val inflater = LayoutInflater.from(parent.context)
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {} 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.isFocusable = true
cardView.isFocusableInTouchMode = true cardView.isFocusableInTouchMode = true
cardView.updatePadding(1, 0, 1, 0) return ViewHolder(cardView, binding)
return ViewHolder(cardView)
} }
fun clear() { fun clear() {
@@ -41,14 +60,14 @@ class CardAdapter(
recyclerView.invalidate() recyclerView.invalidate()
} }
private fun startScaleAnimation(view: View, fromScale: Float, toScale: Float, duration: Long) { private fun startScaleAnimation(view: View) {
val scaleAnimation = ScaleAnimation( val scaleAnimation = ScaleAnimation(
fromScale, toScale, 0.9f, 1.0f,
fromScale, toScale, 0.9f, 1.0f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f, ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f ScaleAnimation.RELATIVE_TO_SELF, 0.5f
) )
scaleAnimation.duration = duration scaleAnimation.duration = 200
scaleAnimation.fillAfter = false scaleAnimation.fillAfter = false
view.startAnimation(scaleAnimation) view.startAnimation(scaleAnimation)
} }
@@ -57,7 +76,7 @@ class CardAdapter(
val item = tvListViewModel.getTVViewModel(position) val item = tvListViewModel.getTVViewModel(position)
val tvViewModel = item as TVViewModel val tvViewModel = item as TVViewModel
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view
cardView.tag = tvViewModel cardView.tag = tvViewModel
val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus -> val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus ->
@@ -65,16 +84,12 @@ class CardAdapter(
listener?.onItemHasFocus(item) listener?.onItemHasFocus(item)
focused = cardView focused = cardView
startScaleAnimation(cardView, 0.9f, 1.0f, 200) if (focusable) {
// startScaleAnimation(cardView)
// if (mainFragment.shouldHasFocus(view.tag as TVViewModel)) { viewHolder.focus(true)
// } }
} else {
// if (visiable) { viewHolder.focus(false)
// startScaleAnimation(cardView, 0.9f, 1.0f, 200)
// } else {
// visiable = true
// }
} }
} }
@@ -84,6 +99,34 @@ class CardAdapter(
listener?.onItemClicked(item) 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? -> cardView.setOnKeyListener { _, keyCode, event: KeyEvent? ->
if (event?.action == KeyEvent.ACTION_DOWN) { if (event?.action == KeyEvent.ACTION_DOWN) {
return@setOnKeyListener listener?.onKey(keyCode) ?: false return@setOnKeyListener listener?.onKey(keyCode) ?: false
@@ -91,30 +134,62 @@ class CardAdapter(
false false
} }
cardView.titleText = tvViewModel.getTV().title viewHolder.binding.title.text = tvViewModel.getTV().title
cardView.mainImageView?.let { viewHolder.binding.icon.let {
Glide.with(viewHolder.view.context) Glide.with(viewHolder.view.context)
.load(tvViewModel.getTV().logo) .load(tvViewModel.getTV().logo)
.centerInside() .centerInside()
.into(it) .into(it)
} }
cardView.mainImageView?.setBackgroundColor(Color.WHITE)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE)
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()) {
cardView.contentText = epg.last().title viewHolder.binding.desc.text = epg.last().title
} else { } else {
cardView.contentText = "" viewHolder.binding.desc.text = ""
} }
} }
override fun getItemCount() = tvListViewModel.size() override fun getItemCount() = tvListViewModel.size()
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { class ViewHolder(itemView: View, var binding: CardBinding) : RecyclerView.ViewHolder(itemView) {
val view = 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) { fun toPosition(position: Int) {
@@ -1,10 +1,13 @@
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.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
@@ -14,7 +17,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 = 3000 private val delay: Long = 5000
private var channel = 0 private var channel = 0
private var channelCount = 0 private var channelCount = 0
@@ -25,38 +28,23 @@ 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 activity = requireActivity() val application = requireActivity().applicationContext as MyTVApplication
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
displayMetrics.density binding.channel.layoutParams.width = application.px2Px(binding.channel.layoutParams.width)
binding.channel.layoutParams.height = application.px2Px(binding.channel.layoutParams.height)
var screenWidth = displayMetrics.widthPixels val layoutParams = binding.channel.layoutParams as ViewGroup.MarginLayoutParams
var screenHeight = displayMetrics.heightPixels layoutParams.topMargin = application.px2Px(binding.channel.marginTop)
if (screenHeight > screenWidth) { layoutParams.marginEnd = application.px2Px(binding.channel.marginEnd)
screenWidth = displayMetrics.heightPixels binding.channel.layoutParams = layoutParams
screenHeight = displayMetrics.widthPixels
}
val ratio = 16f / 9f binding.content.textSize = application.px2PxFont(binding.content.textSize)
binding.time.textSize = application.px2PxFont(binding.time.textSize)
if (screenWidth / screenHeight > ratio) { binding.main.layoutParams.width = application.shouldWidthPx()
val x = ((screenWidth - screenHeight * ratio) / 2).toInt() binding.main.layoutParams.height = application.shouldHeightPx()
val originalLayoutParams =
binding.channel.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.rightMargin += x
binding.channel.layoutParams = originalLayoutParams
}
if (screenWidth / screenHeight < ratio) { (activity as MainActivity).fragmentReady(TAG)
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
val originalLayoutParams =
binding.channel.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
binding.channel.layoutParams = originalLayoutParams
}
(activity as MainActivity).fragmentReady("ChannelFragment")
return binding.root return binding.root
} }
@@ -3,28 +3,61 @@ 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("确定更新吗?") builder.setTitle(message)
.setMessage(message) if (update) {
.setPositiveButton( builder.setMessage("确定更新吗?")
"确定" .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")
} }
@@ -0,0 +1,49 @@
package com.lizongying.mytv
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.marginStart
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment
import com.lizongying.mytv.databinding.ErrorBinding
class ErrorFragment : Fragment() {
private var _binding: ErrorBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = ErrorBinding.inflate(inflater, container, false)
val application = requireActivity().applicationContext as MyTVApplication
binding.logo.layoutParams.width = application.px2Px(binding.logo.layoutParams.width)
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 {
private const val TAG = "ErrorFragment"
}
}
@@ -1,54 +0,0 @@
package com.lizongying.mytv
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
import androidx.core.content.ContextCompat
import androidx.leanback.widget.ImageCardView
import androidx.recyclerview.widget.RecyclerView
class GrayOverlayItemDecoration(private val context: Context) : RecyclerView.ItemDecoration() {
private val grayOverlayPaint = Paint().apply {
color = ContextCompat.getColor(context, R.color.gray_overlay)
style = Paint.Style.FILL
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
val childCount = parent.childCount
for (i in 0 until childCount) {
val child = parent.getChildAt(i)
if (!child.hasFocus()) {
(child as ImageCardView).setInfoAreaBackgroundColor(ContextCompat.getColor(context, R.color.blur))
// child.alpha = 0.5f
// 计算遮罩层的大小
// val overlayRect = Rect(
// child.left,
// child.top,
// child.right,
// child.bottom
// )
// // 绘制灰色遮罩层
// c.drawRect(overlayRect, grayOverlayPaint)
} else {
(child as ImageCardView).setInfoAreaBackgroundColor(ContextCompat.getColor(context, R.color.focus))
// child.alpha = 1f
}
}
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
// 在此处设置偏移量为0,以防止遮罩层影响项的布局
outRect.setEmpty()
}
}
@@ -1,11 +1,16 @@
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
@@ -16,7 +21,7 @@ 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 = 3000 private val delay: Long = 5000
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@@ -24,47 +29,56 @@ class InfoFragment : Fragment() {
): View { ): View {
_binding = InfoBinding.inflate(inflater, container, false) _binding = InfoBinding.inflate(inflater, container, false)
val activity = requireActivity() val application = requireActivity().applicationContext as MyTVApplication
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
displayMetrics.density binding.info.layoutParams.width = application.px2Px(binding.info.layoutParams.width)
binding.info.layoutParams.height = application.px2Px(binding.info.layoutParams.height)
var screenWidth = displayMetrics.widthPixels val layoutParams = binding.info.layoutParams as ViewGroup.MarginLayoutParams
var screenHeight = displayMetrics.heightPixels layoutParams.bottomMargin = application.px2Px(binding.info.marginBottom)
if (screenHeight > screenWidth) { binding.info.layoutParams = layoutParams
screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
}
val ratio = 16f / 9f 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))
if (screenWidth / screenHeight < ratio) { val layoutParamsMain = binding.main.layoutParams as ViewGroup.MarginLayoutParams
val y = ((screenHeight - screenWidth / ratio) / 2).toInt() layoutParamsMain.marginStart = application.px2Px(binding.main.marginStart)
val originalLayoutParams = binding.info.layoutParams as ViewGroup.MarginLayoutParams binding.main.layoutParams = layoutParamsMain
originalLayoutParams.bottomMargin += y
binding.info.layoutParams = originalLayoutParams 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) {
binding.textView.text = tvViewModel.getTV().title if (_binding == null) {
return
}
binding.title.text = tvViewModel.getTV().title
Glide.with(this) Glide.with(this)
.load(tvViewModel.getTV().logo) .load(tvViewModel.getTV().logo)
.into(binding.infoLogo) .into(binding.logo)
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.infoDesc.text = epg.last().title binding.desc.text = epg.last().title
} else { } else {
binding.infoDesc.text = "" binding.desc.text = ""
} }
handler.removeCallbacks(removeRunnable) handler.removeCallbacks(removeRunnable)
@@ -0,0 +1,29 @@
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
}
}
@@ -0,0 +1,37 @@
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,8 +1,5 @@
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
@@ -11,22 +8,31 @@ import android.util.Log
import android.view.GestureDetector 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.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 { class MainActivity : FragmentActivity(), Request.RequestListener, OnSharedPreferenceChangeListener {
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()
@@ -39,61 +45,98 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private val handler = Handler() private val handler = Handler()
private val delayHideMain: Long = 10000 private val delayHideMain: Long = 10000
private val delayHideSetting: Long = 10000 private val delayHideSetting: Long = 15000
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) {
supportFragmentManager.beginTransaction() val transaction = 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)
.commit() .hide(errorFragment)
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) { // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val connectivityManager = // val connectivityManager =
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager // getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerDefaultNetworkCallback(object : // connectivityManager.registerDefaultNetworkCallback(object :
ConnectivityManager.NetworkCallback() { // ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { // override fun onAvailable(network: Network) {
super.onAvailable(network) // super.onAvailable(network)
Log.i(TAG, "net ${Build.VERSION.SDK_INT}") // Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
if (this@MainActivity.isNetworkConnected) { // if (this@MainActivity.isNetworkConnected) {
Log.i(TAG, "net isNetworkConnected") // Log.i(TAG, "net isNetworkConnected")
ready++ // ready++
} // }
} // }
}) // })
} else { // } else {
Log.i(TAG, "net ${Build.VERSION.SDK_INT}") // Log.i(TAG, "net ${Build.VERSION.SDK_INT}")
ready++ // ready++
} // }
SP.setOnSharedPreferenceChangeListener(this)
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun showInfoFragment(tvViewModel: TVViewModel) {
@@ -103,6 +146,52 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
} }
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
@@ -119,7 +208,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
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) {
@@ -134,14 +222,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
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()
@@ -164,7 +244,6 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun settingDelayHide() { fun settingDelayHide() {
handler.removeCallbacks(hideSetting) handler.removeCallbacks(hideSetting)
handler.postDelayed(hideSetting, delayHideSetting) handler.postDelayed(hideSetting, delayHideSetting)
showTime()
} }
fun settingNeverHide() { fun settingNeverHide() {
@@ -192,21 +271,39 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
fun fragmentReady(tag: String) { fun fragmentReady(tag: String) {
ready++ ready++
Log.i(TAG, "ready $tag $ready ") Log.i(TAG, "ready $tag $ready ")
if (ready == 6) { if (ready == 7) {
mainFragment.fragmentReady() mainFragment.fragmentReady()
showTime()
} }
} }
private fun showTime() { private fun showTime() {
Log.i(TAG, "showTime ${SP.time}")
if (SP.time) { if (SP.time) {
timeFragment.show() showFragment(timeFragment)
} else { } else {
timeFragment.hide() hideFragment(timeFragment)
} }
} }
private fun showFragment(fragment: Fragment) {
if (!fragment.isHidden) {
return
}
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 {
if (event != null) { if (event != null) {
gestureDetector.onTouchEvent(event) gestureDetector.onTouchEvent(event)
@@ -265,20 +362,17 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
return return
} }
Log.i(TAG, "settingFragment ${settingFragment.isVisible}") if (settingFragment.isVisible) {
if (!settingFragment.isVisible) { return
settingFragment.show(supportFragmentManager, "setting")
settingDelayHide()
} else {
handler.removeCallbacks(hideSetting)
settingFragment.dismiss()
} }
settingFragment.show(supportFragmentManager, TAG)
settingDelayHide()
} }
private val hideSetting = Runnable { private val hideSetting = Runnable {
if (settingFragment.isVisible) { if (settingFragment.isVisible) {
settingFragment.dismiss() settingFragment.dismiss()
showTime()
} }
} }
@@ -343,6 +437,11 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
}, 2000) }, 2000)
} }
private fun active() {
if (settingFragment.isVisible) {
settingDelayHide()
}
}
fun onKey(keyCode: Int): Boolean { fun onKey(keyCode: Int): Boolean {
Log.i(TAG, "keyCode $keyCode") Log.i(TAG, "keyCode $keyCode")
@@ -413,7 +512,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_UNKNOWN -> { KeyEvent.KEYCODE_UNKNOWN -> {
showSetting() // showSetting()
return true return true
} }
@@ -433,6 +532,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_ENTER -> { KeyEvent.KEYCODE_ENTER -> {
active()
switchMainFragment() switchMainFragment()
} }
@@ -441,6 +541,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
active()
channelUp() channelUp()
} }
@@ -449,6 +550,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> {
active()
channelDown() channelDown()
} }
@@ -457,6 +559,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
active()
if (mainFragment.isHidden && !settingFragment.isVisible) { if (mainFragment.isHidden && !settingFragment.isVisible) {
switchMainFragment() switchMainFragment()
return true return true
@@ -464,6 +567,7 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
active()
if (mainFragment.isHidden && !settingFragment.isVisible) { if (mainFragment.isHidden && !settingFragment.isVisible) {
showSetting() showSetting()
return true return true
@@ -484,13 +588,20 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
private fun getAppSignature() = this.appSignature private fun getAppSignature() = this.appSignature
override fun onStart() { override fun onStart() {
Log.i(TAG, "onStart") Log.i(TAG, "onStart MainActivity")
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)
} }
@@ -514,4 +625,12 @@ class MainActivity : FragmentActivity(), Request.RequestListener {
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,23 +2,29 @@ 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.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.core.view.marginStart
import androidx.core.view.marginTop
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lizongying.mytv.Utils.dpToPx
import com.lizongying.mytv.api.YSP import com.lizongying.mytv.api.YSP
import com.lizongying.mytv.databinding.MenuBinding import com.lizongying.mytv.databinding.MenuBinding
import com.lizongying.mytv.databinding.RowBinding 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
@@ -36,52 +42,39 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
private var lastVideoUrl = "" private var lastVideoUrl = ""
private lateinit var application: MyTVApplication
private lateinit var gestureDetector: GestureDetector
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val context = requireContext()
_binding = MenuBinding.inflate(inflater, container, false) _binding = MenuBinding.inflate(inflater, container, false)
val activity = requireActivity() application = requireActivity().applicationContext as MyTVApplication
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
displayMetrics.density binding.menu.layoutParams.width = application.shouldWidthPx()
binding.menu.layoutParams.height = application.shouldHeightPx()
var screenWidth = displayMetrics.widthPixels binding.container.setOnClickListener {
var screenHeight = displayMetrics.heightPixels
if (screenHeight > screenWidth) {
screenWidth = displayMetrics.heightPixels
screenHeight = displayMetrics.widthPixels
}
val ratio = 16f / 9f
if (screenWidth / screenHeight > ratio) {
val x = ((screenWidth - screenHeight * ratio) / 2).toInt()
val originalLayoutParams =
binding.scroll.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.leftMargin += x
originalLayoutParams.rightMargin += x
binding.scroll.layoutParams = originalLayoutParams
}
if (screenWidth / screenHeight < ratio) {
val y = ((screenHeight - screenWidth / ratio) / 2).toInt()
val originalLayoutParams =
binding.scroll.layoutParams as ViewGroup.MarginLayoutParams
originalLayoutParams.topMargin += y
originalLayoutParams.bottomMargin += y
binding.scroll.layoutParams = originalLayoutParams
}
binding.menu.setOnClickListener {
hideSelf() hideSelf()
} }
gestureDetector = GestureDetector(context, GestureListener())
return binding.root return binding.root
} }
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
hideSelf()
return true
}
}
private fun hideSelf() { private fun hideSelf() {
requireActivity().supportFragmentManager.beginTransaction() requireActivity().supportFragmentManager.beginTransaction()
.hide(this) .hide(this)
@@ -115,46 +108,70 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
val adapter = val adapter =
CardAdapter( CardAdapter(
itemBinding.rowItems, itemBinding.items,
this, this,
tvListViewModelCurrent, tvListViewModelCurrent,
) )
rowList.add(itemBinding.rowItems) rowList.add(itemBinding.items)
adapter.setItemListener(this) adapter.setItemListener(this)
itemBinding.rowHeader.text = k itemBinding.header.text = k
itemBinding.rowItems.tag = idx.toInt() itemBinding.items.tag = idx.toInt()
itemBinding.rowItems.adapter = adapter itemBinding.items.adapter = adapter
itemBinding.rowItems.addOnScrollListener(object : RecyclerView.OnScrollListener() { itemBinding.items.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
(activity as MainActivity).mainActive() (activity as MainActivity).mainActive()
} }
}) })
val itemDecoration = context?.let { GrayOverlayItemDecoration(it) } val itemDecoration = context?.let { ItemDecoration(it) }
if (itemDecoration != null) { if (itemDecoration != null) {
itemBinding.rowItems.addItemDecoration(itemDecoration) itemBinding.items.addItemDecoration(itemDecoration)
} }
if (SP.grid) { if (SP.grid) {
itemBinding.rowItems.layoutManager = itemBinding.items.layoutManager =
GridLayoutManager(context, 6) GridLayoutManager(context, 6)
itemBinding.rowItems.layoutParams.height = itemBinding.items.layoutParams.height =
dpToPx(92 * ((tvListViewModelCurrent.size() + 6 - 1) / 6)) application.dp2Px(110 * ((tvListViewModelCurrent.size() + 6 - 1) / 6) + 5)
} else { } else {
itemBinding.rowItems.layoutManager = itemBinding.items.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
} }
val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams val layoutParams = itemBinding.row.layoutParams as ViewGroup.MarginLayoutParams
layoutParams.topMargin = dpToPx(11F) layoutParams.topMargin = application.dp2Px(11)
itemBinding.row.layoutParams = layoutParams itemBinding.row.layoutParams = layoutParams
itemBinding.row.setOnClickListener { itemBinding.row.setOnClickListener {
hideSelf() 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) content.addView(itemBinding.row)
idx++ idx++
@@ -170,8 +187,15 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
if (tvViewModel.errInfo.value != null if (tvViewModel.errInfo.value != null
&& tvViewModel.getTV().id == itemPosition && tvViewModel.getTV().id == itemPosition
) { ) {
Toast.makeText(context, tvViewModel.errInfo.value, Toast.LENGTH_SHORT) if (tvViewModel.errInfo.value == "") {
.show() (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())
}
} }
} }
tvViewModel.ready.observe(viewLifecycleOwner) { _ -> tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
@@ -211,12 +235,28 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
} }
} }
(activity as MainActivity).fragmentReady("MainFragment") (activity as MainActivity).fragmentReady(TAG)
} }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { fun changeMenu() {
super.onViewCreated(view, savedInstanceState) if (SP.grid) {
for (i in rowList) {
if (i is RecyclerView) {
i.layoutManager = GridLayoutManager(context, 6)
i.layoutParams.height =
application.dp2Px(110 * (((i.adapter as CardAdapter).getItemCount() + 6 - 1) / 6) + 5)
}
}
} else {
for (i in rowList) {
if (i is RecyclerView) {
i.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
i.layoutParams.height = application.dp2Px(110 + 5)
}
}
}
} }
override fun onKey(keyCode: Int): Boolean { override fun onKey(keyCode: Int): Boolean {
@@ -237,13 +277,14 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
override fun onItemHasFocus(tvViewModel: TVViewModel) { override fun onItemHasFocus(tvViewModel: TVViewModel) {
tvListViewModel.setItemPositionCurrent(tvViewModel.getTV().id)
val row = tvViewModel.getRowPosition() val row = tvViewModel.getRowPosition()
for (i in rowList) { for (i in rowList) {
if (i.tag as Int != row) { if (i.tag as Int != row) {
((i as RecyclerView).adapter as CardAdapter).clear() ((i as RecyclerView).adapter as CardAdapter).focusable = false
(i.adapter as CardAdapter).clear()
} else {
((i as RecyclerView).adapter as CardAdapter).focusable = true
} }
} }
@@ -251,7 +292,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
override fun onItemClicked(tvViewModel: TVViewModel) { override fun onItemClicked(tvViewModel: TVViewModel) {
Log.i(TAG, "onItemClicked")
if (this.isHidden) { if (this.isHidden) {
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
return return
@@ -260,7 +300,8 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
if (itemPosition != tvViewModel.getTV().id) { if (itemPosition != tvViewModel.getTV().id) {
itemPosition = tvViewModel.getTV().id itemPosition = tvViewModel.getTV().id
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed() tvListViewModel.getTVViewModel(itemPosition)?.changed("menu")
Log.i(TAG, "onItemClicked ${tvViewModel.getTV().id}")
} }
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
} }
@@ -307,8 +348,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
fun fragmentReady() { fun fragmentReady() {
// request.fetchPage() tvListViewModel.getTVViewModel(itemPosition)?.changed("init")
tvListViewModel.getTVViewModel(itemPosition)?.changed()
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel -> tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
updateEPG(tvViewModel) updateEPG(tvViewModel)
@@ -320,7 +360,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() tvListViewModel.getTVViewModel(itemPosition)?.changed("num")
} else { } else {
Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show() Toast.makeText(context, "频道不存在", Toast.LENGTH_SHORT).show()
} }
@@ -334,7 +374,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
itemPosition = tvListViewModel.size() - 1 itemPosition = tvListViewModel.size() - 1
} }
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed() tvListViewModel.getTVViewModel(itemPosition)?.changed("prev")
} }
} }
@@ -345,7 +385,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
itemPosition = 0 itemPosition = 0
} }
tvListViewModel.setItemPosition(itemPosition) tvListViewModel.setItemPosition(itemPosition)
tvListViewModel.getTVViewModel(itemPosition)?.changed() tvListViewModel.getTVViewModel(itemPosition)?.changed("next")
} }
} }
@@ -370,6 +410,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
} }
override fun onHiddenChanged(hidden: Boolean) { override fun onHiddenChanged(hidden: Boolean) {
Log.i(TAG, "onHiddenChanged $hidden")
super.onHiddenChanged(hidden) super.onHiddenChanged(hidden)
if (!hidden) { if (!hidden) {
val tvModel = tvListViewModel.getTVViewModel(itemPosition) val tvModel = tvListViewModel.getTVViewModel(itemPosition)
@@ -378,15 +419,17 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
Log.i(TAG, "toPosition $rowPosition $itemPosition") Log.i(TAG, "toPosition $rowPosition $itemPosition")
for (i in rowList) { for (i in rowList) {
if (i.tag as Int == rowPosition) { if (i.tag as Int == rowPosition) {
((i as RecyclerView).adapter as CardAdapter).toPosition(itemPosition!!) ((i as RecyclerView).adapter as CardAdapter).updateEPG()
(i.adapter as CardAdapter).focusable = true
(i.adapter as CardAdapter).toPosition(itemPosition!!)
break break
} }
} }
} else { } else {
view?.post { view?.post {
// for (i in rowList) { for (i in rowList) {
// ((i as RecyclerView).adapter as CardAdapter).visiable = false ((i as RecyclerView).adapter as CardAdapter).focusable = false
// } }
} }
} }
} }
@@ -396,13 +439,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
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()
@@ -1,30 +0,0 @@
package com.lizongying.mytv
import android.app.Application
import android.content.Context
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.multidex.MultiDex
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
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(base)
}
}
@@ -0,0 +1,127 @@
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)
}
}
@@ -0,0 +1,6 @@
package com.lizongying.mytv
interface OnSharedPreferenceChangeListener {
fun onSharedPreferenceChanged(key: String)
}
@@ -3,8 +3,6 @@ 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
@@ -17,45 +15,37 @@ 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(), SurfaceHolder.Callback { class PlayerFragment : Fragment() {
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)
if (Utils.isTmallDevice()) { playerView = _binding!!.playerView
_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
@@ -78,14 +68,22 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
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")
tvViewModel?.changed() val err = "播放错误"
tvViewModel?.setErrInfo(err)
tvViewModel?.changed("retry")
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
if (isPlaying) {
tvViewModel?.setErrInfo("")
}
} }
}) })
} }
}) })
(activity as MainActivity).fragmentReady("PlayerFragment") (activity as MainActivity).fragmentReady(TAG)
return _binding!!.root return _binding!!.root
} }
@@ -95,10 +93,7 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
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()
} }
} }
@@ -110,11 +105,6 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
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() {
@@ -127,9 +117,6 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
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() {
@@ -137,7 +124,6 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
if (playerView != null) { if (playerView != null) {
playerView!!.player?.release() playerView!!.player?.release()
} }
exoPlayer?.release()
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -146,18 +132,6 @@ class PlayerFragment : Fragment(), SurfaceHolder.Callback {
} }
companion object { companion object {
private const val TAG = "PlaybackVideoFragment" private const val TAG = "PlayerFragment"
}
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) {
} }
} }
+20 -4
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"
private const val KEY_TIME = "time" 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"
private const val KEY_GRID = "grid" 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,6 +28,8 @@ 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)
*/ */
@@ -35,6 +37,10 @@ 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()
@@ -45,7 +51,12 @@ object SP {
var time: Boolean var time: Boolean
get() = sp.getBoolean(KEY_TIME, true) get() = sp.getBoolean(KEY_TIME, true)
set(value) = sp.edit().putBoolean(KEY_TIME, value).apply() set(value) {
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)
@@ -53,7 +64,12 @@ object SP {
var grid: Boolean var grid: Boolean
get() = sp.getBoolean(KEY_GRID, false) get() = sp.getBoolean(KEY_GRID, false)
set(value) = sp.edit().putBoolean(KEY_GRID, value).apply() set(value) {
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,11 +1,23 @@
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
@@ -21,6 +33,11 @@ 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)
}
} }
} }
@@ -79,35 +96,148 @@ class SettingFragment : DialogFragment() {
} }
} }
updateManager = UpdateManager(context, this, context.appVersionCode) binding.checkVersion.setOnClickListener {
binding.checkVersion.setOnClickListener( (activity as MainActivity).settingDelayHide()
OnClickListenerCheckVersion( requestInstallPermissions()
activity as MainActivity, }
updateManager
)
)
binding.exit.setOnClickListener{ 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
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
} }
fun setVersionName(versionName: String) { private fun requestInstallPermissions() {
if (_binding != null) { val context = requireContext()
binding.versionName.text = versionName val permissionsList: MutableList<String> = ArrayList()
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()
} }
} }
internal class OnClickListenerCheckVersion( override fun onRequestPermissionsResult(
private val mainActivity: MainActivity, requestCode: Int,
private val updateManager: UpdateManager permissions: Array<out String>,
) : grantResults: IntArray
View.OnClickListener { ) {
override fun onClick(view: View?) { super.onRequestPermissionsResult(requestCode, permissions, grantResults)
mainActivity.settingDelayHide() if (requestCode == PERMISSIONS_REQUEST_CODE) {
updateManager.checkAndUpdate() var allPermissionsGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false
break
}
}
if (allPermissionsGranted) {
updateManager.checkAndUpdate()
} else {
Toast.makeText(context, "权限授权失败", Toast.LENGTH_LONG).show()
}
} }
} }
@@ -118,6 +248,7 @@ class SettingFragment : DialogFragment() {
companion object { companion object {
const val TAG = "SettingFragment" const val TAG = "SettingFragment"
const val PERMISSIONS_REQUEST_CODE = 1
} }
} }
@@ -2,10 +2,11 @@ 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
@@ -21,74 +22,57 @@ 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 activity = requireActivity() val application = requireActivity().applicationContext as MyTVApplication
val application = activity.applicationContext as MyApplication
val displayMetrics = application.getDisplayMetrics()
displayMetrics.density binding.time.layoutParams.width = application.px2Px(binding.time.layoutParams.width)
binding.time.layoutParams.height = application.px2Px(binding.time.layoutParams.height)
var screenWidth = displayMetrics.widthPixels val layoutParams = binding.time.layoutParams as ViewGroup.MarginLayoutParams
var screenHeight = displayMetrics.heightPixels layoutParams.topMargin = application.px2Px(binding.time.marginTop)
if (screenHeight > screenWidth) { layoutParams.marginEnd = application.px2Px(binding.time.marginEnd)
screenWidth = displayMetrics.heightPixels binding.time.layoutParams = layoutParams
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)
} }
val ratio = 16f / 9f (activity as MainActivity).fragmentReady(TAG)
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 onResume() { override fun onHiddenChanged(hidden: Boolean) {
super.onResume() super.onHiddenChanged(hidden)
if (view?.visibility == View.VISIBLE) { if (!hidden) {
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)
} }
} }
fun show() {
view?.visibility = View.VISIBLE
handler.removeCallbacks(showRunnable)
handler.postDelayed(showRunnable, 0)
}
fun hide() {
view?.visibility = View.GONE
handler.removeCallbacks(showRunnable)
}
override fun onDestroyView() { override fun onDestroyView() {
handler.removeCallbacks(showRunnable)
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
@@ -1,206 +1,73 @@
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.core.app.ActivityCompat import androidx.fragment.app.FragmentActivity
import androidx.core.content.PermissionChecker import androidx.work.OneTimeWorkRequestBuilder
import androidx.core.content.PermissionChecker.checkSelfPermission import androidx.work.WorkManager
import com.lizongying.mytv.api.ReleaseV2 import androidx.work.workDataOf
import com.lizongying.mytv.requests.MyRequest import com.lizongying.mytv.api.ApiClient
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 myRequest = MyRequest() private var releaseRequest = ReleaseRequest()
private var release: ReleaseV2? = null private var release: ReleaseResponse? = 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 = myRequest.getRelease() release = releaseRequest.getRelease()
Log.i(TAG, "versionCode $versionCode ${release?.c}") val code = release?.version_code
if (release?.c != null) { if (code != null) {
text = if (release?.c!! > versionCode) { if (code.toLong() >= versionCode) {
"最新版本:${release?.n}\n${release?.d ?: ""}" text = "最新版本:${release?.version_name}"
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) updateUI(text, update)
} }
} }
private fun updateUI(text: String) { private fun updateUI(text: String, update: Boolean) {
val dialog = ConfirmationFragment(this@UpdateManager, text) val dialog = ConfirmationFragment(this@UpdateManager, text, update)
dialog.show(settingFragment.fragmentManager, TAG) dialog.show((context as FragmentActivity).supportFragmentManager, TAG)
} }
private fun haveStoragePermission(): Boolean { private fun startDownload(context: Context, release: ReleaseResponse) {
if (Build.VERSION.SDK_INT >= 23) { val versionName = release.version_name
if (checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) val apkName = "my-tv"
=== PermissionChecker.PERMISSION_GRANTED val apkFileName = "$apkName-${release.version_name}.apk"
) { val downloadUrl =
Log.e("Permission error", "You have permission") "${ApiClient.DOWNLOAD_HOST}${release.version_name}/$apkName-${release.version_name}.apk"
return true Log.i(TAG, "downloadUrl: $downloadUrl")
} else { val downloadRequest = OneTimeWorkRequestBuilder<UpdateWorker>()
Log.e("Permission error", "You have asked for permission") .setInputData(
ActivityCompat.requestPermissions( workDataOf(
context as Activity, arrayOf( "VERSION_NAME" to versionName,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE "APK_FILENAME" to apkFileName,
), 1 "DOWNLOAD_URL" to downloadUrl
)
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
) )
) )
installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive") .build()
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(installIntent) WorkManager.getInstance(context).enqueue(downloadRequest)
}
} }
companion object { companion object {
@@ -209,16 +76,9 @@ class UpdateManager(
override fun onConfirm() { override fun onConfirm() {
Log.i(TAG, "onConfirm $release") Log.i(TAG, "onConfirm $release")
release?.let { startDownload(it) } release?.let { startDownload(context, it) }
} }
override fun onCancel() { override fun onCancel() {
} }
fun destroy() {
if (downloadReceiver != null) {
context.unregisterReceiver(downloadReceiver)
Log.i(TAG, "destroy downloadReceiver")
}
}
} }
@@ -0,0 +1,158 @@
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"
}
}
+174 -6
View File
@@ -2,11 +2,15 @@ package com.lizongying.mytv
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import android.util.Log
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
@@ -15,6 +19,12 @@ import java.util.Locale
object Utils { object Utils {
private var between: Long = 0 private var between: Long = 0
private var a: String = ""
var b: String = ""
private var c: String = ""
private var listener: Request.RequestListener? = null
fun getDateFormat(format: String): String { fun getDateFormat(format: String): String {
return SimpleDateFormat( return SimpleDateFormat(
format, format,
@@ -31,13 +41,57 @@ object Utils {
} }
suspend fun init() { suspend fun init() {
var currentTimeMillis: Long = 0
try { try {
currentTimeMillis = getTimestampFromServer() val 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
var x = ""
try {
x = getNothing()
} catch (e: Exception) {
x = ""
println("a ${e.message}")
}
if (x != "") {
try {
x = getNothing2(x)
} catch (e: Exception) {
x = ""
println("b ${e.message}")
}
}
// if (x != "") {
// try {
// getNothing3(x)
// } catch (e: Exception) {
// println("b ${e.message}")
// }
// }
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
}
} }
/** /**
@@ -47,8 +101,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()
.connectTimeout(500, java.util.concurrent.TimeUnit.MILLISECONDS) .addInterceptor(RetryInterceptor(3))
.readTimeout(1, java.util.concurrent.TimeUnit.SECONDS).build() .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()
@@ -59,7 +113,115 @@ 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)
}
}
}
private val regex = Regex("""chunk-vendors\.([^.]+)\.js""")
private suspend fun getNothing(): String {
return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(3))
.build()
val request = okhttp3.Request.Builder()
.url("https://www.yangshipin.cn")
.build()
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val string = response.body()?.string()
val matchResult = string?.let { regex.find(it) }
var x = ""
if (matchResult != null) {
x = matchResult.groupValues[1]
}
x
}
} catch (e: IOException) {
throw IOException("Error during network request", e)
}
}
}
private val regex2 = Regex(""""ysp_tx"[^:]+:"([^"]+)[^:]+:"([^"]+)""")
private val regex3 = Regex(""""(https[^"]+wasm([^"]+))""")
private suspend fun getNothing2(x: String): String {
return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(3))
.build()
val request = okhttp3.Request.Builder()
.url("https://www.yangshipin.cn/js/chunk-vendors.$x.js")
.build()
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val string = response.body()?.string()
val matches = string?.let { regex2.findAll(it) }
var matchResult = matches?.lastOrNull()
if (matchResult != null) {
val (aa, bb) = matchResult.destructured
a = aa
b = bb
}
matchResult = string?.let { regex3.find(it) }
if (matchResult != null) {
val (cc) = matchResult.destructured
c = cc
Log.i("", "ccccc $c")
}
c
}
} catch (e: IOException) {
throw IOException("Error during network request", e)
}
}
}
private suspend fun getNothing3(x: String): String {
return withContext(Dispatchers.IO) {
val client = okhttp3.OkHttpClient.Builder()
.addInterceptor(RetryInterceptor(3))
.build()
val request = okhttp3.Request.Builder()
.url("https://www.yangshipin.cn/js/chunk-vendors.$x.js")
.build()
try {
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val string = response.body()?.string()
val matches = string?.let { regex2.findAll(it) }
var matchResult = matches?.lastOrNull()
if (matchResult != null) {
val (aa, bb) = matchResult.destructured
a = aa
b = bb
}
matchResult = string?.let { regex3.find(it) }
if (matchResult != null) {
val (cc) = matchResult.destructured
c = cc
}
c
}
} catch (e: IOException) {
throw IOException("Error during network request", e) throw IOException("Error during network request", e)
} }
} }
@@ -72,6 +234,8 @@ 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()
@@ -88,4 +252,8 @@ 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,7 +3,9 @@ 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
@@ -23,17 +25,19 @@ 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()) .addConverterFactory(GsonConverterFactory.create(gson))
.build().create(YSPApiService::class.java) .build().create(YSPApiService::class.java)
} }
@@ -41,15 +45,15 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(myUrl) .baseUrl(myUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson))
.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(myUrl) .baseUrl(HOST)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson))
.build().create(ReleaseService::class.java) .build().create(ReleaseService::class.java)
} }
@@ -65,7 +69,7 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(traceUrl) .baseUrl(traceUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson))
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
@@ -73,15 +77,23 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(trace2Url) .baseUrl(trace2Url)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson))
.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()) .addConverterFactory(GsonConverterFactory.create(gson))
.build().create(YSPBtraceService::class.java) .build().create(YSPBtraceService::class.java)
} }
@@ -97,7 +109,7 @@ class ApiClient {
Retrofit.Builder() Retrofit.Builder()
.baseUrl(fUrl) .baseUrl(fUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson))
.build().create(FAuthService::class.java) .build().create(FAuthService::class.java)
} }
@@ -170,4 +182,9 @@ 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,4 +1,4 @@
package com.lizongying.mytv package com.lizongying.mytv.api
import android.content.Context import android.content.Context
@@ -7,10 +7,14 @@ 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?
external fun hash3(data: ByteArray): ByteArray?
companion object { companion object {
init { init {
System.loadLibrary("native") System.loadLibrary("native")
@@ -1,17 +1,17 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
data class KvcollectRequest( data class KvcollectRequest(
val BossId: Int = 2727, val BossId: Int = 99150,
val Pwd: Int = 1424084450, val Pwd: Int = 1999332929,
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: Double = 10.0, val downspeed: Int = 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 = "100", val fplayerver: String = "180",
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 = 2727, val BossId: Int = 99150,
val Pwd: Int = 1424084450, val Pwd: Int = 1999332929,
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: String = "10", val downspeed: Int = 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 = "100", val fplayerver: String = "180",
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 = 9, // val seq: Int = 0, //
var signature: String = "", var signature: String = "",
val url: String = "", // val url: String = "", //
val viewid: String = "", // val viewid: String = "", //
val _dc: Double = 0.0, val _dc: String = "",
) )
@@ -1,12 +0,0 @@
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 = "2",
): Call<ReleaseV2>
}
@@ -0,0 +1,11 @@
package com.lizongying.mytv.api
data class Token2(
val ret: Int?,
val msg: String?,
val data: Data,
) {
data class Data(
val token: String,
)
}
@@ -1,10 +1,12 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
import android.content.Context import android.content.Context
import com.lizongying.mytv.Encryptor import android.util.Log
import com.lizongying.mytv.SP import com.lizongying.mytv.SP
import com.lizongying.mytv.Utils
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
@@ -53,6 +55,14 @@ 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()
@@ -76,10 +86,45 @@ 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
@@ -93,7 +138,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"""
} }
private fun getTimeStr(): String { fun getTimeStr(): String {
return getDateTimestamp().toString() return getDateTimestamp().toString()
} }
@@ -107,6 +152,7 @@ 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
@@ -137,13 +183,13 @@ object YSP {
private fun getAuthSignature(): String { private fun getAuthSignature(): String {
val e = val e =
"appid=${appid}&guid=${guid}&pid=${livepid}&rand_str=${randStr}".toByteArray() "appid=${appid}&guid=${guid}&pid=${livepid}&rand_str=${randStr}${Utils.b}".toByteArray()
val hashedData = encryptor.hash2(e) ?: return "" val hashedData = encryptor.hash3(e) ?: return ""
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } } return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
} }
fun getAuthSignature(e: String): String { fun getAuthSignature(e: String): String {
val hashedData = encryptor.hash2(e.toByteArray()) ?: return "" val hashedData = encryptor.hash3("$e${Utils.b}".toByteArray()) ?: return ""
return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } } return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } }
} }
} }
@@ -2,9 +2,11 @@ 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 {
@@ -18,6 +20,12 @@ 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>
@@ -32,4 +40,20 @@ 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 = "9141", @Field("BossId") BossId: String = "9150",
@Field("Pwd") Pwd: String = "1137850982", @Field("Pwd") Pwd: String = "1999332929",
@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 = "100", @Field("fplayerver") fplayerver: String = "180",
@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>
} }
@@ -0,0 +1,19 @@
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
}
}
@@ -1,4 +1,4 @@
package com.lizongying.mytv package com.lizongying.mytv.models
import com.lizongying.mytv.models.ProgramType import com.lizongying.mytv.models.ProgramType
import java.io.Serializable import java.io.Serializable
@@ -15,18 +15,22 @@ 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 package com.lizongying.mytv.models
import com.lizongying.mytv.models.ProgramType import com.lizongying.mytv.R
object TVList { object TVList {
val list: Map<String, List<TV>> by lazy { val list: Map<String, List<TV>> by lazy {
@@ -21,7 +21,8 @@ object TVList {
"2022576801", "2022576801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -34,7 +35,8 @@ object TVList {
"2022576701", "2022576701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -47,7 +49,8 @@ object TVList {
"2022576501", "2022576501",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true mustToken = true,
0.5F
), ),
TV( TV(
0, 0,
@@ -60,7 +63,8 @@ object TVList {
"2022576601", "2022576601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -73,7 +77,8 @@ object TVList {
"2022576401", "2022576401",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -86,7 +91,8 @@ object TVList {
"2013693901", "2013693901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true mustToken = true,
0.5F
), ),
TV( TV(
0, 0,
@@ -99,7 +105,8 @@ object TVList {
"2022576201", "2022576201",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -112,7 +119,8 @@ object TVList {
"2022576101", "2022576101",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = true mustToken = true,
0.5F
), ),
TV( TV(
0, 0,
@@ -125,7 +133,8 @@ object TVList {
"2022576001", "2022576001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -138,7 +147,8 @@ object TVList {
"2022573001", "2022573001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -151,7 +161,8 @@ object TVList {
"2022575901", "2022575901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -164,7 +175,8 @@ object TVList {
"2022575801", "2022575801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -177,7 +189,8 @@ object TVList {
"2022575701", "2022575701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -190,7 +203,8 @@ object TVList {
"2022575601", "2022575601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -203,7 +217,8 @@ object TVList {
"2022575501", "2022575501",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -216,7 +231,8 @@ object TVList {
"2022575401", "2022575401",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -229,7 +245,8 @@ object TVList {
"2022575301", "2022575301",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -242,7 +259,8 @@ object TVList {
"2022576301", "2022576301",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -255,7 +273,8 @@ object TVList {
"2022575201", "2022575201",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -268,7 +287,8 @@ object TVList {
"2020603421", "2020603421",
ProgramType.Y_JCE, ProgramType.Y_JCE,
false, false,
mustToken = false mustToken = false,
0.8F
), ),
TV( TV(
0, 0,
@@ -738,7 +758,8 @@ object TVList {
"2019927403", "2019927403",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -751,7 +772,8 @@ object TVList {
"2022606701", "2022606701",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false mustToken = false,
0.8F
), ),
TV( TV(
0, 0,
@@ -764,7 +786,8 @@ object TVList {
"2022823801", "2022823801",
ProgramType.Y_JCE, ProgramType.Y_JCE,
true, true,
mustToken = false mustToken = false,
0.6F
), ),
), ),
"港澳台" to listOf( "港澳台" to listOf(
@@ -779,7 +802,8 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false mustToken = false,
0.7F
), ),
TV( TV(
0, 0,
@@ -792,7 +816,8 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false mustToken = false,
0.7F
), ),
TV( TV(
0, 0,
@@ -805,7 +830,8 @@ object TVList {
"", "",
ProgramType.F, ProgramType.F,
false, false,
mustToken = false mustToken = false,
0.7F
), ),
), ),
"国际" to listOf( "国际" to listOf(
@@ -820,7 +846,8 @@ object TVList {
"2022575001", "2022575001",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -833,7 +860,8 @@ object TVList {
"2022574901", "2022574901",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -846,7 +874,8 @@ object TVList {
"2022574801", "2022574801",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -859,7 +888,8 @@ object TVList {
"2022574601", "2022574601",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -872,7 +902,8 @@ object TVList {
"2022571701", "2022571701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
TV( TV(
0, 0,
@@ -885,7 +916,8 @@ object TVList {
"2022574701", "2022574701",
ProgramType.Y_PROTO, ProgramType.Y_PROTO,
false, false,
mustToken = false mustToken = false,
0.5F
), ),
) )
) )
@@ -900,6 +932,7 @@ 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)
} }
@@ -3,6 +3,7 @@ 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() {
@@ -16,10 +17,6 @@ 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)
@@ -32,23 +29,16 @@ 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
_itemPositionCurrent.value = position SP.itemPosition = 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,10 +1,8 @@
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
@@ -18,6 +16,8 @@ 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<Boolean>() private val _change = MutableLiveData<String>()
val change: LiveData<Boolean> val change: LiveData<String>
get() = _change get() = _change
private val _ready = MutableLiveData<Boolean>() private val _ready = MutableLiveData<Boolean>()
@@ -56,36 +56,27 @@ 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 = tv.videoUrl + listOf(url) tv.videoUrl += listOf(url)
} }
} else { } else {
tv.videoUrl = tv.videoUrl + listOf(url) tv.videoUrl += listOf(url)
} }
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoUrl.lastIndex _videoIndex.value = tv.videoUrl.lastIndex
} }
fun firstSource() { fun changed(from: String) {
if (_videoUrl.value!!.isNotEmpty()) { retryTimes = 0
setVideoIndex(0) authYSPRetryTimes = 0
allReady() tokenYSPRetryTimes = 0
} else { tokenFHRetryTimes = 0
Log.e(TAG, "no first") _change.value = from
}
}
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,20 +10,23 @@ import retrofit2.Response
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class MyRequest { class ReleaseRequest {
private var releaseService = ApiClient().releaseService private var releaseService = ApiClient().releaseService
suspend fun getRelease(): ReleaseV2? { suspend fun getRelease(): ReleaseResponse? {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
fetchRelease() fetchRelease()
} }
} }
private suspend fun fetchRelease(): ReleaseV2? { private suspend fun fetchRelease(): ReleaseResponse? {
return suspendCoroutine { continuation -> return suspendCoroutine { continuation ->
releaseService.getRelease() releaseService.getRelease()
.enqueue(object : Callback<ReleaseV2> { .enqueue(object : Callback<ReleaseResponse> {
override fun onResponse(call: Call<ReleaseV2>, response: Response<ReleaseV2>) { override fun onResponse(
call: Call<ReleaseResponse>,
response: Response<ReleaseResponse>
) {
if (response.isSuccessful) { if (response.isSuccessful) {
continuation.resume(response.body()) continuation.resume(response.body())
} else { } else {
@@ -31,7 +34,7 @@ class MyRequest {
} }
} }
override fun onFailure(call: Call<ReleaseV2>, t: Throwable) { override fun onFailure(call: Call<ReleaseResponse>, t: Throwable) {
continuation.resume(null) continuation.resume(null)
} }
}) })
@@ -39,6 +42,6 @@ class MyRequest {
} }
companion object { companion object {
private const val TAG = "MyRequest" private const val TAG = "ReleaseRequest"
} }
} }
@@ -0,0 +1,7 @@
package com.lizongying.mytv.requests
data class ReleaseResponse(
val version_code: Int?,
val version_name: String?,
)
@@ -0,0 +1,10 @@
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,11 +1,10 @@
package com.lizongying.mytv package com.lizongying.mytv.requests
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.google.gson.Gson import com.lizongying.mytv.Utils
import java.net.URLDecoder
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
@@ -19,12 +18,14 @@ 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
@@ -42,6 +43,7 @@ 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
@@ -54,8 +56,9 @@ object Request {
private var token = "" private var token = ""
private var tokenFH = "" private var tokenFH = ""
private var needAuth = false private var needAuth = true
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
@@ -74,6 +77,7 @@ 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
@@ -84,11 +88,117 @@ 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()
@@ -103,11 +213,11 @@ object Request {
val liveInfo = response.body() val liveInfo = response.body()
if (liveInfo?.data?.token != null) { if (liveInfo?.data?.token != null) {
Log.i(TAG, "token ${liveInfo.data.token}") Log.i(TAG, "auth ${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 token error") Log.e(TAG, "$title auth error")
if (tvModel.retryTimes < tvModel.retryMaxTimes) { if (tvModel.retryTimes < tvModel.retryMaxTimes) {
tvModel.retryTimes++ tvModel.retryTimes++
if (tvModel.getTV().needToken) { if (tvModel.getTV().needToken) {
@@ -118,11 +228,19 @@ 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 {
@@ -137,11 +255,19 @@ 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)
} }
} }
} }
@@ -158,11 +284,19 @@ 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)
} }
} }
}) })
@@ -171,7 +305,7 @@ object Request {
private fun fetchVideo(tvModel: TVViewModel, cookie: String) { private fun fetchVideo(tvModel: TVViewModel, cookie: String) {
cancelCall() cancelCall()
if (::btraceRunnable.isInitialized) { if (Request::btraceRunnable.isInitialized) {
handler.removeCallbacks(btraceRunnable) handler.removeCallbacks(btraceRunnable)
} }
@@ -182,8 +316,14 @@ object Request {
val request = LiveInfoRequest(data) val request = LiveInfoRequest(data)
call = request.let { call = request.let {
yspApiService.getLiveInfo( yspApiService.getLiveInfo(
"guid=${YSP.getGuid()}; $cookie", "guid=${YSP.getGuid()}; gr_user_id=1; $cookie; newLogin=1; nseqId=${YSP.nseqId}; nrequest-id=${YSP.nrequest_id}",
YSP.token, YSP.token,
YSP.yspsdkinput,
YSP.openapi_signature,
YSP.yspticket,
"${YSP.nseqId}",
YSP.nrequest_id,
"519748109",
it it
) )
} }
@@ -194,21 +334,8 @@ object Request {
val liveInfo = response.body() val liveInfo = response.body()
if (liveInfo?.data?.playurl != null) { if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll if (liveInfo.data.extended_param != null) {
val decodedBytes = Base64.decode( val url = liveInfo.data.playurl + "&revoi=" + liveInfo.data.extended_param
chanll.substring(9, chanll.length - 3),
Base64.DEFAULT
)
val decodedString = String(decodedBytes)
val matchResult = regex.find(decodedString)
if (matchResult != null) {
val (key, iv) = matchResult.destructured
val keyBytes = Base64.decode(key, Base64.DEFAULT)
val ivBytes = Base64.decode(iv, Base64.DEFAULT)
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase() + liveInfo.data.extended_param
Log.d(TAG, "$title url $url") Log.d(TAG, "$title url $url")
tvModel.addVideoUrl(url) tvModel.addVideoUrl(url)
tvModel.allReady() tvModel.allReady()
@@ -223,24 +350,42 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
fetchVideo(tvModel) if (needAuth) {
// fetchAuth(tvModel) fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie) if (needAuth) {
// fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchVideo(tvModel, cookie) if (needAuth) {
// 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 == "应版权方要求,暂停提供直播信号,请点击观看其他精彩节目") {
Log.e(TAG, "$title error ${liveInfo.data.errinfo}") val err = "版权错误"
tvModel.setErrInfo(liveInfo.data.errinfo) Log.e(TAG, "$title $err")
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) {
@@ -249,18 +394,35 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
fetchVideo(tvModel) if (needAuth) {
// fetchAuth(tvModel) fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie) if (needAuth) {
// fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchVideo(tvModel, cookie) if (needAuth) {
// fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
} else {
val err = "其他错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -272,18 +434,35 @@ object Request {
if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) { if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
tvModel.tokenYSPRetryTimes++ tvModel.tokenYSPRetryTimes++
tvModel.needGetToken = true tvModel.needGetToken = true
fetchVideo(tvModel) if (needAuth) {
// fetchAuth(tvModel) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else { } else {
if (!tvModel.getTV().mustToken) { if (!tvModel.getTV().mustToken) {
fetchVideo(tvModel, cookie) if (needAuth) {
// fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} else {
val err = "错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} else { } else {
fetchVideo(tvModel, cookie) if (needAuth) {
// fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
} else {
val err = "状态错误"
Log.e(TAG, "$title $err")
tvModel.setErrInfo(err)
} }
} }
} }
@@ -300,16 +479,82 @@ 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) {
@@ -340,6 +585,8 @@ 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
} }
} }
} }
@@ -355,13 +602,15 @@ 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=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124; yspopenid=$openid; vusession=$token"
fetchAuth(tvModel, cookie) fetchAuth(tvModel, cookie)
} }
} }
@@ -397,6 +646,8 @@ 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
} }
} }
} }
@@ -412,6 +663,8 @@ 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
} }
} }
} }
@@ -447,6 +700,10 @@ 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)
} }
} }
} }
@@ -456,6 +713,10 @@ 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)
} }
} }
}) })
@@ -471,19 +732,42 @@ object Request {
if (token == "") { if (token == "") {
tvModel.needGetToken = true tvModel.needGetToken = true
} }
if (needAuth) { if (needToken2) {
fetchAuth(tvModel) if (needAuth) {
fetchToken(tvModel)
} else {
fetchAuth(tvModel)
}
} else { } else {
fetchVideo(tvModel) if (needAuth) {
fetchAuth(tvModel)
} else {
fetchVideo(tvModel)
}
} }
} else { } else {
val cookie = val cookie =
"versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; updateProtocol=1; deviceModel=120" "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=124"
if (needAuth) {
fetchAuth(tvModel, cookie) if (needToken2) {
if (needAuth) {
fetchToken(tvModel, cookie)
} else {
fetchAuth(tvModel, cookie)
}
} else { } else {
fetchVideo(tvModel, cookie) if (needAuth) {
fetchAuth(tvModel, cookie)
} else {
fetchVideo(tvModel, cookie)
}
} }
//
// if (needAuth) {
// fetchAuth(tvModel, cookie)
// } else {
// fetchVideo(tvModel, cookie)
// }
} }
} }
@@ -602,7 +886,7 @@ object Request {
seq = tvModel.seq, seq = tvModel.seq,
durl = url, durl = url,
url = url, url = url,
_dc = randomNumber, // _dc = "",
) )
val e = val e =
@@ -634,7 +918,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(
@@ -649,12 +933,12 @@ object Request {
seq = tvModel.seq, seq = tvModel.seq,
durl = url, durl = url,
url = url, url = url,
_dc = randomNumber, _dc = "",
) )
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,
@@ -663,18 +947,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.d(TAG, "$title kvcollect success") Log.i(TAG, "$title kvcollect success")
} else { } else {
Log.e(TAG, "$title kvcollect status error") Log.e(TAG, "$title kvcollect status error")
} }
@@ -817,6 +1101,6 @@ object Request {
} }
fun setRequestListener(listener: RequestListener) { fun setRequestListener(listener: RequestListener) {
this.listener = listener Request.listener = listener
} }
} }
@@ -1,4 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FF263238" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/blur" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/focus" />
<corners
android:bottomLeftRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topLeftRadius="4dp"
android:bottomLeftRadius="4dp" />
</shape>
@@ -0,0 +1,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:topRightRadius="4dp"
android:bottomRightRadius="4dp" />
</shape>
@@ -1,4 +1,6 @@
<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 android:radius="4dp" /> <!-- Adjust the radius as needed --> <corners
android:topLeftRadius="4dp"
android:topRightRadius="4dp" />
</shape> </shape>
+23
View File
@@ -0,0 +1,23 @@
<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
@@ -0,0 +1,60 @@
<?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>
+42 -17
View File
@@ -1,19 +1,44 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/channel" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="90dp" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="match_parent"
android:layout_gravity="end|top" android:gravity="center"
android:layout_marginTop="20dp" >
android:layout_marginEnd="170dp"> <FrameLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView <RelativeLayout
android:id="@+id/content" android:id="@+id/channel"
android:layout_width="wrap_content" android:layout_width="90dp"
android:layout_height="wrap_content" android:layout_height="60dp"
android:layout_gravity="end" android:layout_gravity="end|top"
android:layout_marginTop="0dp" android:layout_marginTop="15dp"
android:textColor="#FFEEEEEE" android:layout_marginEnd="170dp"
android:textStyle="bold" android:gravity="end|bottom" >
android:textSize="40sp" />
</FrameLayout> <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="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
@@ -0,0 +1,24 @@
<!-- 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
@@ -0,0 +1,30 @@
<?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>
+55 -48
View File
@@ -1,53 +1,60 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/info" android:layout_width="match_parent"
android:layout_width="320dp" android:layout_height="match_parent"
android:layout_height="80dp" android:gravity="center"
android:layout_gravity="center_horizontal|bottom" >
android:layout_marginBottom="20dp" <FrameLayout
android:background="@drawable/rounded_background"> android:id="@+id/container"
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">
<TextView <LinearLayout
android:id="@+id/textView" android:id="@+id/info"
android:layout_width="wrap_content" android:layout_width="400dp"
android:layout_height="wrap_content" android:layout_height="80dp"
android:layout_gravity="start" android:layout_gravity="center_horizontal|bottom"
android:layout_marginTop="0dp" android:layout_marginBottom="20dp"
android:gravity="start" android:orientation="horizontal">
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<TextView <ImageView
android:id="@+id/info_desc" android:id="@+id/logo"
android:layout_width="wrap_content" android:layout_width="100dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_gravity="start" android:background="@drawable/rounded_white_left"
android:layout_marginTop="8dp" android:padding="10dp" />
android:gravity="start"
android:singleLine="true"
android:textColor="#B3EEEEEE"
android:textSize="16sp" />
</LinearLayout>
<View <LinearLayout
android:layout_width="8dp" android:id="@+id/main"
android:layout_height="wrap_content" android:layout_width="300dp"
android:layout_gravity="end" android:layout_height="match_parent"
android:background="@drawable/rounded_background2" /> android:layout_marginStart="0dp"
</FrameLayout> android:background="@drawable/rounded_dark_right"
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
@@ -0,0 +1,21 @@
<?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>
+25 -14
View File
@@ -1,21 +1,32 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/menu" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:clickable="true" android:clickable="true"
android:focusable="false" android:focusable="false"
> android:gravity="center"
<ScrollView >
android:id="@+id/scroll"
<FrameLayout
android:id="@+id/menu"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
<LinearLayout >
android:id="@+id/content"
android:layout_width="match_parent" <ScrollView
android:layout_height="wrap_content" android:id="@+id/scroll"
android:orientation="vertical"> android:layout_width="wrap_content"
</LinearLayout> android:layout_height="wrap_content"
</ScrollView> >
</FrameLayout> <LinearLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>
+1 -9
View File
@@ -6,20 +6,12 @@
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"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
app:shutter_background_color="@android:color/transparent"
app:resize_mode="fill" app:resize_mode="fill"
app:use_controller="false" /> app:use_controller="false" />
</FrameLayout> </FrameLayout>
+2 -2
View File
@@ -10,7 +10,7 @@
> >
<TextView <TextView
android:id="@+id/row_header" android:id="@+id/header"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start" android:layout_gravity="start"
@@ -22,7 +22,7 @@
android:textSize="22sp" /> android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/row_items" android:id="@+id/items"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
+46 -16
View File
@@ -1,22 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/setting" android:id="@+id/setting"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
> >
<LinearLayout <ScrollView
android:id="@+id/container"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="match_parent"
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:paddingLeft="7dp" android:paddingBottom="20dp"
android:paddingRight="7dp" android:paddingLeft="10dp"
android:background="#FF263238" android:paddingRight="10dp"
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"
@@ -27,9 +33,10 @@
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="300dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
> >
@@ -37,7 +44,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:layout_marginEnd="10dp" android:textSize="14sp"
android:text="@string/check_version" android:text="@string/check_version"
/> />
<ProgressBar <ProgressBar
@@ -48,51 +55,74 @@
<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:layout_marginEnd="10dp" android:textSize="14sp"
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_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_grid" android:id="@+id/switch_grid"
android:text="@string/title_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>
<ImageView </ScrollView>
android:layout_width="300dp" <ImageView
android:layout_height="wrap_content" android:id="@+id/appreciate"
android:src="@drawable/appreciate" android:layout_width="300dp"
android:background="@color/white" /> android:layout_height="wrap_content"
</LinearLayout> android:src="@drawable/appreciate"
</FrameLayout> android:background="@drawable/rounded_white_right" />
</LinearLayout>
+43 -17
View File
@@ -1,19 +1,45 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/time" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="100dp" android:layout_width="match_parent"
android:layout_height="40dp" android:layout_height="match_parent"
android:layout_gravity="end|top" android:gravity="center"
android:layout_marginTop="25dp" >
android:layout_marginEnd="50dp"> <FrameLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView <RelativeLayout
android:id="@+id/content" android:id="@+id/time"
android:layout_width="wrap_content" android:layout_width="120dp"
android:layout_height="wrap_content" android:layout_height="60dp"
android:layout_gravity="end" android:layout_gravity="end|top"
android:layout_marginTop="0dp" android:layout_marginTop="15dp"
android:textColor="#FFEEEEEE" android:layout_marginEnd="50dp"
android:textStyle="bold" android:gravity="end|bottom" >
android:textSize="32sp" />
</FrameLayout> <TextView
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 -3
View File
@@ -1,8 +1,7 @@
<resources> <resources>
<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="gray_overlay">#7F000000</color>
<color name="focus">#0096a6</color> <color name="focus">#0096a6</color>
<color name="blur">#FF263238</color> <color name="blur">#223239</color>
<color name="ic_launcher_background">#223239</color>
</resources> </resources>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#223239</color>
</resources>
+3 -1
View File
@@ -6,5 +6,7 @@
<string name="title_boot_startup">开机自启</string> <string name="title_boot_startup">开机自启</string>
<string name="title_grid">频道列表采用网格样式</string> <string name="title_grid">频道列表采用网格样式</string>
<string name="title_time">显示时间</string> <string name="title_time">显示时间</string>
<string name="exit">退出</string> <string name="clear">恢复默认</string>
<string name="exit">退出应用</string>
<string name="start_update">开始更新</string>
</resources> </resources>
-22
View File
@@ -1,25 +1,3 @@
<resources> <resources>
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="cardType">infoUnder</item>
<item name="infoVisibility">always</item>
<!-- In order to keep backward compatibility we have to create an icon on right. -->
<item name="lbImageCardViewType">Title|Content|IconOnRight</item>
<!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. -->
<item name="infoAreaBackground">@null</item>
</style>
<style name="CustomImageCardViewImageStyle" parent="Widget.Leanback.ImageCardView.ImageStyle">
<item name="android:layout_width">160dp</item>
<item name="android:layout_height">40dp</item>
<item name="android:adjustViewBounds">true</item>
<item name="android:contentDescription">@null</item>
<item name="android:scaleType">centerCrop</item>
<item name="layout_viewType">main</item>
</style>
<style name="CustomImageCardTheme" parent="Theme.Leanback">
<item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
<item name="imageCardViewImageStyle">@style/CustomImageCardViewImageStyle</item>
</style>
</resources> </resources>
+1 -3
View File
@@ -1,8 +1,6 @@
<resources> <resources>
<style name="Theme.MyTV" parent="@style/Theme.Leanback"> <style name="Theme.MyTV" parent="@style/Theme.Leanback">
<item name="browsePaddingStart">11dp</item> <item name="android:fitsSystemWindows">false</item>
<item name="browsePaddingEnd">11dp</item>
<item name="browseRowsMarginTop">54dp</item>
</style> </style>
</resources> </resources>
+3
View File
@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="." />
</paths>
+40
View File
@@ -0,0 +1,40 @@
[versions]
media3 = "1.2.1" # minSdk17
gson = "2.10.1" # 2.10.1 and older: API level 19
failureaccess = "1.0.2"
guava = "33.2.1-android"
retrofit = "2.6.4" # 21:2.9.0 17:2.6.4
protobuf = "3.25.1"
glide = "4.11.0"
work = "2.9.0"
core_ktx = "1.11.0-beta02"
multidex = "2.0.1"
leanback = "1.2.0-alpha02"
lifecycle = "2.6.2"
constraintlayout = "2.1.4"
serialization = "1.6.2"
coroutines = "1.8.0-RC"
javax_annotation = "1.3.2"
[libraries]
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
failureaccess = { module = "com.google.guava:failureaccess", version.ref = "failureaccess" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
converter-protobuf = { module = "com.squareup.retrofit2:converter-protobuf", version.ref = "retrofit" }
protobuf-kotlin = { module = "com.google.protobuf:protobuf-kotlin", version.ref = "protobuf" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "core_ktx" }
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
leanback = { module = "androidx.leanback:leanback", version.ref = "leanback" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
javax-annotation-api = { module = "javax.annotation:javax.annotation-api", version.ref = "javax_annotation" }
+1 -1
View File
@@ -1,6 +1,6 @@
#Fri Dec 01 13:53:24 HKT 2023 #Fri Dec 01 13:53:24 HKT 2023
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-rc-1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Executable
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
in_changelog=false
while IFS= read -r line; do
if [[ "$line" == "## "* ]]; then
continue
fi
if [[ $in_changelog == false ]] && [[ "$line" == "### "* ]]; then
in_changelog=true
continue
fi
if [[ $in_changelog == true ]] && [[ "$line" == "### "* ]]; then
break
fi
echo "$line"
done < HISTORY.md
+1
View File
@@ -0,0 +1 @@
{"version_code": 33621760, "version_name": "v2.1.7"}