Compare commits

..
1 Commits
Author SHA1 Message Date
Li ZongYing 52c7887951 keep screen on 2023-12-16 14:26:31 +08:00
42 changed files with 1380 additions and 1495 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
distribution: 'temurin' distribution: 'temurin'
- name: Run build with Gradle wrapper - name: Run build with Gradle wrapper
run: ./gradlew assembleRelease -PIS_SO_BUILD=false run: ./gradlew assembleRelease
- name: Sign app APK - name: Sign app APK
id: sign_app id: sign_app
+2 -16
View File
@@ -4,27 +4,13 @@
## 使用 ## 使用
下载安装 [releases](https://github.com/lizongying/my-tv/releases/) 下载安装[releases](https://github.com/lizongying/my-tv/releases)
其他地址 [my-tv](https://lyrics.run/my-tv.html) [my-tv](https://lyrics.run/my-tv.html)
![image](./screenshots/img.png) ![image](./screenshots/img.png)
![image](./screenshots/img_1.png) ![image](./screenshots/img_1.png)
## 更新日志
### v1.2.5
* 美化频道信息显示
* 优化节目单获取
### v1.2.4
* 改变换台滑动方向,上一个频道下滑,下一个频道上滑
* 软件退出时,退出播放器
* 播放相同的频道,不再重复加载
* 暂时移除部分频道
## 其他 ## 其他
小米电视可以使用小米电视助手进行安装 小米电视可以使用小米电视助手进行安装
+1 -1
View File
@@ -3,4 +3,4 @@
/cmake_install.cmake /cmake_install.cmake
/CMakeFiles/ /CMakeFiles/
/Makefile /Makefile
/src/main/cpp/native.c /src/main/cpp/native-lib.c
+3 -5
View File
@@ -13,7 +13,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.lizongying.mytv" applicationId "com.lizongying.mytv"
minSdk 17 minSdk 21
targetSdk 33 targetSdk 33
versionCode VersionCode() versionCode VersionCode()
versionName VersionName() versionName VersionName()
@@ -24,7 +24,8 @@ android {
// For ndk-build, instead use the ndkBuild block. // For ndk-build, instead use the ndkBuild block.
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 : false}"
arguments "-DIS_SO_BUILD=${false}"
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"
} }
@@ -38,7 +39,6 @@ android {
// libraries Gradle should build and package with your app. // libraries Gradle should build and package with your app.
abiFilters "armeabi-v7a", "arm64-v8a" abiFilters "armeabi-v7a", "arm64-v8a"
} }
multiDexEnabled true
} }
buildTypes { buildTypes {
@@ -92,8 +92,6 @@ static def VersionName() {
dependencies { dependencies {
def media3_version = "1.1.1" def media3_version = "1.1.1"
implementation "androidx.media3:media3-ui:$media3_version"
// For media playback using ExoPlayer // For media playback using ExoPlayer
implementation "androidx.media3:media3-exoplayer:$media3_version" implementation "androidx.media3:media3-exoplayer:$media3_version"
+3 -4
View File
@@ -12,15 +12,14 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.MyTV"> android:theme="@style/Theme.MyTV">
<activity <activity
android:keepScreenOn="true" android:keepScreenOn="true"
android:name=".MainActivity" android:name=".MainActivity"
android:banner="@drawable/logo" android:banner="@drawable/tv"
android:exported="true" android:exported="true"
android:icon="@drawable/logo" android:icon="@drawable/tv"
android:logo="@drawable/logo" android:logo="@drawable/tv"
android:screenOrientation="landscape"> android:screenOrientation="landscape">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
Binary file not shown.
Binary file not shown.
+327
View File
@@ -0,0 +1,327 @@
#include "stdio.h"
#include "jni.h"
#include <openssl/rand.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/md5.h>
#include <android/log.h>
#include <stdbool.h>
#include <ctype.h>
#include <stdlib.h>
#define APP_SIGNATURE "f0005d8a185c47e72946053a0af5c0f5620605dd9e301ef0937e477f607ab0d7"
#define APP_SIGNATURE2 "9e58fc98c3967ae80eb25541241895368a900fa488e015b08614575c18fab95e"
#define TAG "native"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
static bool signatureVerified = false;
static const char key[33] = "48e5918a74ae21c972b90cce8af6c8be";
static const char iv[33] = "9a7e7d23610266b1d9fbf98581384d92";
static const char appendString[15] = "0f$IVHi9Qno?G";
static void toUpperCase(char *str) {
while (*str) {
*str = toupper((unsigned char) *str);
str++;
}
}
static char *binaryToHex(const unsigned char *data, size_t dataSize) {
char *hexString = (char *) malloc(dataSize * 2 + 1); // 每个字节需要两个十六进制字符,再加上字符串结束符
if (hexString == NULL) {
// 内存分配失败
return NULL;
}
for (size_t i = 0; i < dataSize; ++i) {
sprintf(hexString + i * 2, "%02X", data[i]);
}
hexString[dataSize * 2] = '\0'; // 添加字符串结束符
return hexString;
}
static unsigned char *hexStringToBinary(const char *hexString1) {
size_t inputLength = strlen(hexString1);
if (inputLength % 2 != 0) {
return NULL;
}
size_t length = inputLength / 2;
unsigned char *result1 = (unsigned char *) malloc(length);
for (size_t i = 0; i < length; i++) {
char byteString[3] = {hexString1[2 * i], hexString1[2 * i + 1], '\0'};
result1[i] = (unsigned char) strtoul(byteString, NULL, 16);
}
return result1;
}
static char *aes(const char *plaintext) {
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
return NULL;
}
// const char k[33] = "48e5918a74ae21c972b90cce8af6c8be";
unsigned char *resultKey = hexStringToBinary(key);
// const char i[33] = "9a7e7d23610266b1d9fbf98581384d92";
unsigned char *resultIv = hexStringToBinary(iv);
if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, resultKey, resultIv) != 1) {
free(resultKey);
free(resultIv);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
EVP_CIPHER_CTX_set_padding(ctx, 1);
size_t dataLength = strlen(plaintext);
size_t maxEncryptedLength = dataLength + EVP_CIPHER_block_size(EVP_aes_128_cbc());
unsigned char *encryptedData = (unsigned char *) malloc(maxEncryptedLength);
if (encryptedData == NULL) {
free(resultKey);
free(resultIv);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
int len;
if (EVP_EncryptUpdate(ctx, encryptedData, &len, (const unsigned char *) plaintext,
(int) dataLength) !=
1) {
free(resultKey);
free(resultIv);
free(encryptedData);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
size_t encryptedLength = len;
if (EVP_EncryptFinal_ex(ctx, encryptedData + len, &len) != 1) {
free(resultKey);
free(resultIv);
free(encryptedData);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
// 更新加密数据长度
encryptedLength += len;
// 创建一个 C 字符串以容纳加密后的数据
char *encryptedString = binaryToHex(encryptedData, encryptedLength);
if (encryptedString == NULL) {
// 内存分配失败
free(resultKey);
free(resultIv);
free(encryptedData);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
// 释放内存和资源
free(resultKey);
free(resultIv);
free(encryptedData);
EVP_CIPHER_CTX_free(ctx);
return encryptedString;
}
jbyte *appendStringToEnd(jbyte *data, size_t *dataLength) {
// 获取原始数据长度
size_t originalLength = *dataLength;
size_t appendStringLength = strlen(appendString);
// 分配新的内存空间,包括原始数据和附加字符串
data = (jbyte *) realloc(data,
originalLength + appendStringLength + 1);
if (data == NULL) {
// 内存分配失败的处理
return NULL;
}
// 将附加字符串复制到新的内存空间的末尾
memcpy(data + originalLength, appendString, appendStringLength);
// 在新数据的末尾添加 null 终止字符
data[originalLength + appendStringLength] = '\0';
// 更新数据长度
*dataLength = originalLength + appendStringLength + 1;
return data;
}
JNIEXPORT jbyteArray
JNICALL
Java_com_lizongying_mytv_Encryptor_hash(JNIEnv *env, jobject thiz, jbyteArray data_) {
if (signatureVerified == false) {
return NULL;
}
jbyte *data = (*env)->GetByteArrayElements(env, data_, NULL);
// 获取数据长度
size_t dataLength = (*env)->GetArrayLength(env, data_);
// LOGI("appendStringLength %s", data);
data = appendStringToEnd(data, &dataLength);
dataLength = strlen((const char *) data);
// LOGI("dataLength %d", dataLength);
// 分配内存以存储 MD5 值(16 字节)
unsigned char md5Digest[EVP_MAX_MD_SIZE];
unsigned int md5DigestLength;
// 创建 MD5 上下文
EVP_MD_CTX *md5Context = EVP_MD_CTX_new();
if (md5Context == NULL) {
// 处理错误
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return NULL;
}
// 初始化 MD5 上下文
if (EVP_DigestInit_ex(md5Context, EVP_md5(), NULL) != 1) {
// 处理错误
EVP_MD_CTX_free(md5Context);
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return NULL;
}
// 更新 MD5 上下文
if (EVP_DigestUpdate(md5Context, data, dataLength) != 1) {
// 处理错误
EVP_MD_CTX_free(md5Context);
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return NULL;
}
// 获取 MD5 值
if (EVP_DigestFinal_ex(md5Context, md5Digest, &md5DigestLength) != 1) {
// 处理错误
EVP_MD_CTX_free(md5Context);
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return NULL;
}
jsize md5Length = (jsize) md5DigestLength;
// 创建 Java 字节数组以容纳 MD5 值
jbyteArray md5Array = (*env)->NewByteArray(env, md5Length);
if (md5Array == NULL) {
// 处理错误
EVP_MD_CTX_free(md5Context);
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return NULL;
}
// 将 MD5 值复制到 Java 字节数组
(*env)->SetByteArrayRegion(env, md5Array, 0, md5Length, (jbyte *) md5Digest);
// 释放内存和资源
EVP_MD_CTX_free(md5Context);
(*env)->ReleaseByteArrayElements(env, data_, data, 0);
return md5Array;
}
JNIEXPORT void JNICALL
Java_com_lizongying_mytv_Encryptor_init(JNIEnv *env, jobject thiz, jobject context) {
// 获取 Context 类
jclass contextClass = (*env)->GetObjectClass(env, context);
// 获取 getAppSignature 方法的 ID
jmethodID methodId = (*env)->GetMethodID(env, contextClass, "getAppSignature",
"()Ljava/lang/String;");
// 调用 Kotlin 函数
jstring result = (jstring) (*env)->CallObjectMethod(env, context, methodId);
// 检查 result 是否为 NULL
if (result == NULL) {
return;
}
// 在 C 端处理字符串
const char *utfChars = (*env)->GetStringUTFChars(env, result, NULL);
// 比较签名哈希
signatureVerified = (strcmp(utfChars, APP_SIGNATURE) == 0);
if (signatureVerified == 0 ) {
signatureVerified = (strcmp(utfChars, APP_SIGNATURE2) == 0);
}
// 释放字符串
(*env)->ReleaseStringUTFChars(env, result, utfChars);
}
JNIEXPORT jstring JNICALL
Java_com_lizongying_mytv_Encryptor_encrypt(JNIEnv *env, jobject thiz, jstring t, jstring e,
jstring r, jstring n, jstring i) {
if (signatureVerified == false) {
return NULL;
}
const char *t_str = (*env)->GetStringUTFChars(env, t, NULL);
const char *e_str = (*env)->GetStringUTFChars(env, e, NULL);
const char *r_str = (*env)->GetStringUTFChars(env, r, NULL);
const char *n_str = (*env)->GetStringUTFChars(env, n, NULL);
const char *i_str = (*env)->GetStringUTFChars(env, i, NULL);
char El[256];
snprintf(El, sizeof(El),
"|%s|%s|%s|%s|%s|%s|https://www.yangshipin.c|mozilla/5.0 (macintosh; ||Mozilla|Netscape|MacIntel|",
t_str, e_str, "mg3c3b04ba", r_str, n_str, i_str);
// LOGI("EL %s", El);
unsigned int xl = 0;
for (int Ll = 0; Ll < strlen(El); Ll++) {
xl = (xl << 5) - xl + (unsigned char) El[Ll];
xl &= xl;
}
char plaintext[256];
snprintf(plaintext, sizeof(plaintext), "|%d%s", xl, El);
// Encrypt the plaintext
char *encryptedResult = aes(plaintext);
toUpperCase(encryptedResult);
char result[512];
snprintf(result, sizeof(result), "--01%s", encryptedResult);
// LOGI("result %s", result);
// Free the memory allocated in the encrypt function
free(encryptedResult);
(*env)->ReleaseStringUTFChars(env, t, t_str);
(*env)->ReleaseStringUTFChars(env, e, e_str);
(*env)->ReleaseStringUTFChars(env, r, r_str);
(*env)->ReleaseStringUTFChars(env, n, n_str);
(*env)->ReleaseStringUTFChars(env, i, i_str);
// Create a Java string from the result and return it
jstring resultString = (*env)->NewStringUTF(env, result);
return resultString;
}
@@ -1,22 +1,47 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.graphics.Color import android.content.Context
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.util.Log
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import androidx.leanback.widget.ImageCardView import androidx.leanback.widget.ImageCardView
import androidx.leanback.widget.Presenter import androidx.leanback.widget.Presenter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CardPresenter( class CardPresenter(
private val owner: LifecycleOwner, private val lifecycleScope: LifecycleCoroutineScope,
// private val owner: LifecycleOwner
) : Presenter() { ) : Presenter() {
private var tvViewModel: TVViewModel? = null
fun bindData(viewModel: TVViewModel, fragment: Context?, viewHolder: ViewHolder) {
}
fun setViewModel(model: TVViewModel) {
tvViewModel = model
}
fun unbind() {
// viewModel?.backgroundImage?.removeObservers(fragment)
}
override fun onCreateViewHolder(parent: ViewGroup): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup): ViewHolder {
val cardView = object : val cardView = object :
ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {} ImageCardView(ContextThemeWrapper(parent.context, R.style.CustomImageCardTheme)) {
override fun setSelected(selected: Boolean) {
updateCardBackgroundColor(this)
super.setSelected(selected)
}
}
cardView.isFocusable = true cardView.isFocusable = true
cardView.isFocusableInTouchMode = true cardView.isFocusableInTouchMode = true
@@ -24,39 +49,70 @@ class CardPresenter(
} }
override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) { override fun onBindViewHolder(viewHolder: ViewHolder, item: Any) {
val tvViewModel = item as TVViewModel val tv = item as TV
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view as ImageCardView
cardView.titleText = tvViewModel.title.value cardView.titleText = tv.title
cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT) cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT)
cardView.tag = tvViewModel.videoUrl.value cardView.tag = tv.videoUrl
if (tvViewModel.logo.value != null) { // lifecycleScope.launch(Dispatchers.IO) {
Glide.with(viewHolder.view.context) // val videoThumbnail = tv.videoUrl?.let { getVideoThumbnail(it) }
.load(tvViewModel.logo.value) //
.centerInside() // withContext(Dispatchers.Main) {
.into(cardView.mainImageView) // cardView.mainImageView.setImageBitmap(videoThumbnail)
// }
// }
cardView.setBackgroundColor(Color.WHITE) // tvViewModel?.update(tv)
cardView.setMainImageScaleType(ImageView.ScaleType.CENTER_INSIDE) // tvViewModel?.backgroundImage?.observe(owner) { imageUrl ->
} // if (imageUrl != "") {
// Glide.with(viewHolder.view.context)
tvViewModel.program.observe(owner) { _ -> // .load(imageUrl)
val program = tvViewModel.getProgramOne() // .centerCrop()
if (program != null) { // .into(cardView.mainImageView)
cardView.contentText = program.name // }
} // }
}
} }
override fun onUnbindViewHolder(viewHolder: ViewHolder) { override fun onUnbindViewHolder(viewHolder: ViewHolder) {
val cardView = viewHolder.view as ImageCardView val cardView = viewHolder.view as ImageCardView
cardView.badgeImage = null
cardView.mainImage = null cardView.mainImage = null
} }
private fun updateCardBackgroundColor(view: ImageCardView) {
val currentTag = view.tag
lifecycleScope.launch(Dispatchers.IO) {
delay(1000)
if (view.isSelected && view.tag != null && currentTag == view.tag) {
val videoThumbnail = view.tag.toString().let { getVideoThumbnail(it) }
withContext(Dispatchers.Main) {
if (view.isSelected && currentTag == view.tag) {
view.mainImageView.setImageBitmap(videoThumbnail)
}
}
}
}
}
private fun getVideoThumbnail(url: String): Bitmap? {
val mediaMetadataRetriever = MediaMetadataRetriever()
try {
val map = HashMap<String, String>()
mediaMetadataRetriever.setDataSource(url, map)
return mediaMetadataRetriever.frameAtTime
} catch (e: Exception) {
e.printStackTrace()
} finally {
mediaMetadataRetriever.release()
}
return null
}
companion object { companion object {
private const val TAG = "CardPresenter" private const val TAG = "CardPresenter"
private const val CARD_WIDTH = 300 private const val CARD_WIDTH = 313
private const val CARD_HEIGHT = 101 private const val CARD_HEIGHT = 176
} }
} }
@@ -0,0 +1,251 @@
package com.lizongying.mytv
import android.content.Context
import android.media.MediaPlayer
import android.net.Uri
import android.os.Handler
import android.view.SurfaceHolder
import androidx.annotation.OptIn
import androidx.leanback.media.PlaybackGlueHost
import androidx.leanback.media.PlayerAdapter
import androidx.leanback.media.SurfaceHolderGlueHost
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.hls.HlsMediaSource
import java.io.IOException
open class ExoPlayerAdapter(private var mContext: Context?) : PlayerAdapter() {
val mPlayer = mContext?.let { ExoPlayer.Builder(it).build() }
var mSurfaceHolderGlueHost: SurfaceHolderGlueHost? = null
val mRunnable: Runnable = object : Runnable {
override fun run() {
callback.onCurrentPositionChanged(this@ExoPlayerAdapter)
mHandler.postDelayed(this, getProgressUpdatingInterval().toLong())
}
};
val mHandler = Handler()
var mInitialized = false // true when the MediaPlayer is prepared/initialized
var mMediaSourceUri: Uri? = null
var mHasDisplay = false
var mBufferedProgress: Long = 0
var mBufferingStart = false
open fun notifyBufferingStartEnd() {
callback.onBufferingStateChanged(
this@ExoPlayerAdapter,
mBufferingStart || !mInitialized
)
}
override fun onAttachedToHost(host: PlaybackGlueHost?) {
if (host is SurfaceHolderGlueHost) {
mSurfaceHolderGlueHost = host
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(VideoPlayerSurfaceHolderCallback(this))
}
}
/**
* Will reset the [MediaPlayer] and the glue such that a new file can be played. You are
* not required to call this method before playing the first file. However you have to call it
* before playing a second one.
*/
open fun reset() {
changeToUnitialized()
// mPlayer.reset()
}
open fun changeToUnitialized() {
if (mInitialized) {
mInitialized = false
notifyBufferingStartEnd()
if (mHasDisplay) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
}
}
/**
* Release internal MediaPlayer. Should not use the object after call release().
*/
open fun release() {
changeToUnitialized()
mHasDisplay = false
mPlayer?.release()
}
override fun onDetachedFromHost() {
if (mSurfaceHolderGlueHost != null) {
mSurfaceHolderGlueHost!!.setSurfaceHolderCallback(null)
mSurfaceHolderGlueHost = null
}
reset()
release()
}
/**
* @see MediaPlayer.setDisplay
*/
fun setDisplay(surfaceHolder: SurfaceHolder?) {
val hadDisplay = mHasDisplay
mHasDisplay = surfaceHolder != null
if (hadDisplay == mHasDisplay) {
return
}
mPlayer?.setVideoSurfaceHolder(surfaceHolder)
if (mHasDisplay) {
if (mInitialized) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
} else {
if (mInitialized) {
callback.onPreparedStateChanged(this@ExoPlayerAdapter)
}
}
}
override fun setProgressUpdatingEnabled(enabled: Boolean) {
mHandler.removeCallbacks(mRunnable)
if (!enabled) {
return
}
mHandler.postDelayed(mRunnable, getProgressUpdatingInterval().toLong())
}
/**
* Return updating interval of progress UI in milliseconds. Subclass may override.
* @return Update interval of progress UI in milliseconds.
*/
open fun getProgressUpdatingInterval(): Int {
return 16
}
override fun isPlaying(): Boolean {
return mInitialized && mPlayer?.isPlaying ?: false
}
override fun getDuration(): Long {
if (mInitialized) {
val duration = mPlayer?.duration
if (duration != null) {
return duration.toLong()
}
}
return -1
}
override fun getCurrentPosition(): Long {
if (mInitialized) {
val currentPosition = mPlayer?.currentPosition
if (currentPosition != null) {
return currentPosition.toLong()
}
}
return -1
}
override fun play() {
if (!mInitialized || mPlayer?.isPlaying == true) {
return
}
mPlayer?.play()
callback.onPlayStateChanged(this@ExoPlayerAdapter)
callback.onCurrentPositionChanged(this@ExoPlayerAdapter)
}
override fun pause() {
if (isPlaying) {
mPlayer?.pause()
callback.onPlayStateChanged(this@ExoPlayerAdapter)
}
}
override fun seekTo(newPosition: Long) {
if (!mInitialized) {
return
}
mPlayer?.seekTo(newPosition.toInt().toLong())
}
override fun getBufferedPosition(): Long {
return mBufferedProgress
}
/**
* Sets the media source of the player witha given URI.
*
* @return Returns `true` if uri represents a new media; `false`
* otherwise.
* @see MediaPlayer.setDataSource
*/
open fun setDataSource(uri: Uri?): Boolean {
if (if (mMediaSourceUri != null) mMediaSourceUri == uri else uri == null) {
return false
}
mMediaSourceUri = uri
prepareMediaForPlaying()
return true
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
@OptIn(UnstableApi::class)
private fun prepareMediaForPlaying() {
reset()
try {
if (mMediaSourceUri != null) {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
val hlsMediaSource =
HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
mMediaSourceUri!!
)
)
mPlayer?.setMediaSource(hlsMediaSource)
} else {
return
}
} catch (e: IOException) {
e.printStackTrace()
throw RuntimeException(e)
}
mPlayer?.prepare()
mPlayer?.playWhenReady = true
callback.onPlayStateChanged(this@ExoPlayerAdapter)
}
/**
* @return True if MediaPlayer OnPreparedListener is invoked and got a SurfaceHolder if
* [PlaybackGlueHost] provides SurfaceHolder.
*/
override fun isPrepared(): Boolean {
return mInitialized && (mSurfaceHolderGlueHost == null || mHasDisplay)
}
}
internal class VideoPlayerSurfaceHolderCallback(private val playerAdapter: ExoPlayerAdapter) :
SurfaceHolder.Callback {
override fun surfaceCreated(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(surfaceHolder)
}
override fun surfaceChanged(surfaceHolder: SurfaceHolder, i: Int, i1: Int, i2: Int) {
// Handle surface changes if needed
}
override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) {
playerAdapter.setDisplay(null)
}
}
@@ -0,0 +1,9 @@
package com.lizongying.mytv
import java.io.Serializable
data class Info(
var rowPosition: Int = 0,
var itemPosition: Int = 0,
var item: TV,
) : Serializable
@@ -1,23 +1,16 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.os.Bundle import android.os.Bundle
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.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.lizongying.mytv.databinding.InfoBinding import com.lizongying.mytv.databinding.InfoBinding
import com.lizongying.mytv.models.TVViewModel
class InfoFragment : Fragment() { class InfoFragment : Fragment() {
private var _binding: InfoBinding? = null private var _binding: InfoBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private val handler = Handler()
private val delay: Long = 3000
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@@ -26,39 +19,8 @@ class InfoFragment : Fragment() {
return binding.root return binding.root
} }
override fun onResume() { fun setInfo(info: TV) {
super.onResume() binding.textView.text = info.title
// Use a Handler to delay the fragment transaction
handler.postDelayed(removeRunnable, delay)
}
fun show(tvViewModel: TVViewModel) {
Log.i(TAG, "show")
binding.textView.text = tvViewModel.title.value
Glide.with(this)
.load(tvViewModel.logo.value)
.into(binding.infoLogo)
val program = tvViewModel.getProgramOne()
if (program != null) {
binding.infoDesc.text = program.name
}
handler.removeCallbacks(removeRunnable)
view?.visibility = View.VISIBLE
handler.postDelayed(removeRunnable, delay)
}
override fun onPause() {
super.onPause()
Log.i(TAG, "onPause")
// Cancel the delayed task when the fragment is paused
handler.removeCallbacks(removeRunnable)
}
private val removeRunnable = Runnable {
Log.i(TAG, "hide")
view?.visibility = View.GONE
} }
override fun onDestroyView() { override fun onDestroyView() {
@@ -10,10 +10,7 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
import android.view.WindowManager import android.view.WindowManager
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
@@ -21,48 +18,57 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.lizongying.mytv.models.TVViewModel
import java.security.MessageDigest import java.security.MessageDigest
class MainActivity : FragmentActivity() { class MainActivity : FragmentActivity() {
var playerFragment = PlayerFragment() private val playbackFragment = PlaybackFragment()
private val mainFragment = MainFragment() private val mainFragment = MainFragment()
private val infoFragment = InfoFragment() private val infoFragment = InfoFragment()
private var doubleBackToExitPressedOnce = false private var doubleBackToExitPressedOnce = false
private lateinit var gestureDetector: GestureDetector
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 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() supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playerFragment) .add(R.id.main_browse_fragment, playbackFragment)
.add(R.id.main_browse_fragment, infoFragment)
.add(R.id.main_browse_fragment, mainFragment) .add(R.id.main_browse_fragment, mainFragment)
.add(R.id.main_browse_fragment, infoFragment)
.hide(infoFragment) .hide(infoFragment)
.commit() .commit()
mainFragment.view?.requestFocus()
} }
gestureDetector = GestureDetector(this, GestureListener())
} }
fun showInfoFragment(tvViewModel: TVViewModel) { fun switchInfoFragment(tv: TV) {
infoFragment.show(tvViewModel) infoFragment.setInfo(tv)
if (infoFragment.isHidden) {
supportFragmentManager.beginTransaction().show(infoFragment).commit()
}
} }
fun play(tvViewModel: TVViewModel) { fun showInfoFragment(tv: TV) {
Log.i(TAG, "play: ${tvViewModel.getTV()}") infoFragment.setInfo(tv)
playerFragment.play(tvViewModel) supportFragmentManager.beginTransaction()
mainFragment.view?.requestFocus() .show(infoFragment)
.commit()
}
fun hideInfoFragment() {
supportFragmentManager.beginTransaction()
.hide(infoFragment)
.commit()
}
fun play(tv: TV) {
Log.i(TAG, "play: $tv")
playbackFragment.play(tv)
} }
fun prev() { fun prev() {
@@ -73,11 +79,11 @@ class MainActivity : FragmentActivity() {
mainFragment.next() mainFragment.next()
} }
private fun prevSource() { fun prevSource() {
mainFragment.prevSource() mainFragment.prevSource()
} }
private fun nextSource() { fun nextSource() {
mainFragment.nextSource() mainFragment.nextSource()
} }
@@ -98,7 +104,7 @@ class MainActivity : FragmentActivity() {
mainFragment.focus() mainFragment.focus()
} }
private fun mainFragmentIsHidden(): Boolean { fun mainFragmentIsHidden(): Boolean {
return mainFragment.isHidden return mainFragment.isHidden
} }
@@ -110,54 +116,6 @@ class MainActivity : FragmentActivity() {
} }
} }
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (event != null) {
gestureDetector.onTouchEvent(event)
}
return super.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
Log.i(TAG, "onSingleTapConfirmed")
switchMainFragment()
return true
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (velocityY > 0) {
if (mainFragment.isHidden) {
prev()
} else {
// if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1,
// false
// )
// }
}
}
if (velocityY < 0) {
if (mainFragment.isHidden) {
next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false)
// hideMainFragment()
// return false
// }
}
}
return super.onFling(e1, e2, velocityX, velocityY)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_BACK -> { KeyEvent.KEYCODE_BACK -> {
@@ -213,58 +171,31 @@ class MainActivity : FragmentActivity() {
} }
KeyEvent.KEYCODE_DPAD_CENTER -> { KeyEvent.KEYCODE_DPAD_CENTER -> {
Log.i(TAG, "KEYCODE_DPAD_CENTER")
switchMainFragment() switchMainFragment()
} }
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
prev() prev()
} else {
// if (mainFragment.selectedPosition == 0) {
// mainFragment.setSelectedPosition(
// mainFragment.tvListViewModel.maxNum.size - 1,
// false
// )
// }
} }
} }
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> {
if (mainFragment.isHidden) { if (mainFragment.isHidden) {
next() next()
} else {
// if (mainFragment.selectedPosition == mainFragment.tvListViewModel.maxNum.size - 1) {
//// mainFragment.setSelectedPosition(0, false)
// hideMainFragment()
// return false
// }
} }
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
// if (mainFragment.isHidden) { if (mainFragment.isHidden) {
// prevSource() prevSource()
// } else { }
//// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
//// ?.getItemPosition() == 0
//// ) {
////// mainFragment.toLastPosition()
//// hideMainFragment()
//// }
// }
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
// if (mainFragment.isHidden) { if (mainFragment.isHidden) {
// nextSource() nextSource()
// } else { }
//// if (mainFragment.tvListViewModel.getTVViewModelCurrent()
//// ?.getItemPosition() == mainFragment.tvListViewModel.maxNum[mainFragment.selectedPosition] - 1
//// ) {
//// mainFragment.toFirstPosition()
//// }
// }
} }
} }
@@ -3,9 +3,9 @@ package com.lizongying.mytv
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.leanback.app.BrowseSupportFragment import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.ArrayObjectAdapter import androidx.leanback.widget.ArrayObjectAdapter
import androidx.leanback.widget.HeaderItem import androidx.leanback.widget.HeaderItem
@@ -18,173 +18,100 @@ import androidx.leanback.widget.Presenter
import androidx.leanback.widget.Row import androidx.leanback.widget.Row
import androidx.leanback.widget.RowPresenter import androidx.leanback.widget.RowPresenter
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVListViewModel import com.lizongying.mytv.models.TVListViewModel
import com.lizongying.mytv.models.TVViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : BrowseSupportFragment() { class MainFragment : BrowseSupportFragment() {
var itemPosition: Int = 0 var itemPosition: Int = 0
private val list2: MutableList<Info> = mutableListOf()
private var request: Request? = null private var request: Request? = null
private var rowsAdapter: ArrayObjectAdapter? = null private var rowsAdapter: ArrayObjectAdapter? = null
var tvListViewModel = TVListViewModel() private var tvListViewModel = TVListViewModel()
private var sharedPref: SharedPreferences? = null private var sharedPref: SharedPreferences? = null
private var lastVideoUrl: String = "" // override fun onCreateView(
// inflater: LayoutInflater,
private val handler = Handler(Looper.getMainLooper()) // container: ViewGroup?,
private lateinit var mUpdateProgramRunnable: UpdateProgramRunnable // savedInstanceState: Bundle?
// ): View? {
override fun onCreate(savedInstanceState: Bundle?) { // super.onCreate()
super.onCreate(savedInstanceState) // // 使用自定义的布局文件
headersState = HEADERS_DISABLED // return inflater.inflate(R.layout.custom_browse_fragment, container, false)
} // }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
sharedPref = activity?.getPreferences(Context.MODE_PRIVATE) sharedPref = activity?.getPreferences(Context.MODE_PRIVATE)
setupUIElements()
request = activity?.let { Request(it) } request = activity?.let { Request(it) }
loadRows() loadRows()
mUpdateProgramRunnable = UpdateProgramRunnable()
handler.post(mUpdateProgramRunnable)
setupEventListeners() setupEventListeners()
view?.post { view?.post {
// request?.fetchPage() // request?.fetchPage()
// tvListViewModel.getTVViewModel(0)?.let { request?.fetchProgram(it) }
} }
tvListViewModel.getTVListViewModel().value?.forEach { tvViewModel ->
tvViewModel.ready.observe(viewLifecycleOwner) { _ ->
// not first time && channel not change tvListViewModel.getListLiveData().value?.forEach { tvViewModel ->
if (tvViewModel.ready.value != null tvViewModel.videoUrl.observe(viewLifecycleOwner) { _ ->
&& tvViewModel.id.value == itemPosition // Log.i(TAG, "tv ${tvViewModel.getTV()}")
&& check(tvViewModel) if (tvViewModel.updateByYSP()) {
) { val tv = tvViewModel.getTV()
Log.i(TAG, "ready ${tvViewModel.title.value}") if (tv.id == itemPosition) {
(activity as? MainActivity)?.play(tvViewModel) (activity as? MainActivity)?.play(tv)
} // (activity as? MainActivity)?.switchInfoFragment(tv)
}
tvViewModel.change.observe(viewLifecycleOwner) { _ ->
if (tvViewModel.change.value != null) {
val title = tvViewModel.title.value
Log.i(TAG, "switch $title")
if (tvViewModel.pid.value != null) {
Log.i(TAG, "request $title")
lifecycleScope.launch(Dispatchers.IO) {
tvViewModel.let { request?.fetchData(it) }
}
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
} else {
if (check(tvViewModel)) {
(activity as? MainActivity)?.play(tvViewModel)
(activity as? MainActivity)?.showInfoFragment(tvViewModel)
setSelectedPosition(
tvViewModel.getRowPosition(), true,
SelectItemViewHolderTask(tvViewModel.getItemPosition())
)
}
} }
} }
} }
} }
} }
override fun onDestroy() { private fun setupUIElements() {
super.onDestroy() brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background)
handler.removeCallbacks(mUpdateProgramRunnable)
} }
fun updateProgram(tvViewModel: TVViewModel) { private fun updateRows(tv: TV) {
val timestamp = getDateTimestamp() // 获取适配器中的数据
if (timestamp - tvViewModel.programUpdateTime > 60) { val dataList = rowsAdapter?.replace(tv.id, tv)
if (tvViewModel.program.value!!.isEmpty()) { //
tvViewModel.programUpdateTime = timestamp //// 修改数据
request?.fetchProgram(tvViewModel) //// 这里假设 dataList 是一个可变的列表
} else { // dataList[position] = updatedData
if (timestamp - tvViewModel.program.value!!.last().et < 600) { //
tvViewModel.programUpdateTime = timestamp //// 刷新适配器
request?.fetchProgram(tvViewModel) // rowsAdapter.notifyItemChanged(position)
} // rowsAdapter.notifyItemRangeChanged()
}
}
}
inner class UpdateProgramRunnable : Runnable {
override fun run() {
tvListViewModel.getTVListViewModel().value?.filter { it.programId.value != null }
?.forEach { tvViewModel ->
updateProgram(
tvViewModel
)
}
handler.postDelayed(this, 60000)
}
}
fun check(tvViewModel: TVViewModel): Boolean {
val title = tvViewModel.title.value
val videoUrl = tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (videoUrl == null || videoUrl == "") {
Log.e(TAG, "$title videoUrl is empty")
return false
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "$title videoUrl is duplication")
return false
}
return true
}
fun toLastPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(tvListViewModel.maxNum[selectedPosition] - 1)
)
}
fun toFirstPosition() {
setSelectedPosition(
selectedPosition, false,
SelectItemViewHolderTask(0)
)
}
override fun startHeadersTransition(withHeaders: Boolean) {
} }
private fun loadRows() { private fun loadRows() {
val list = TVList.list
rowsAdapter = ArrayObjectAdapter(ListRowPresenter()) rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
val cardPresenter = CardPresenter(viewLifecycleOwner) val cardPresenter = CardPresenter(lifecycleScope)
var idx: Long = 0 var idx: Long = 0
for ((k, v) in TVList.list) { for ((k, v) in list) {
val listRowAdapter = ArrayObjectAdapter(cardPresenter) val listRowAdapter = ArrayObjectAdapter(cardPresenter)
for ((idx2, v1) in v.withIndex()) { var idx2 = 0
val tvViewModel = TVViewModel(v1) for ((_, v1) in v) {
tvViewModel.setRowPosition(idx.toInt()) list2.add(
tvViewModel.setItemPosition(idx2) Info(
tvListViewModel.addTVViewModel(tvViewModel) idx.toInt(), idx2, v1
listRowAdapter.add(tvViewModel) )
)
listRowAdapter.add(v1)
idx2++
tvListViewModel.addTV(v1)
} }
tvListViewModel.maxNum.add(v.size)
val header = HeaderItem(idx, k) val header = HeaderItem(idx, k)
rowsAdapter!!.add(ListRow(header, listRowAdapter)) rowsAdapter!!.add(ListRow(header, listRowAdapter))
idx++ idx++
@@ -198,9 +125,24 @@ class MainFragment : BrowseSupportFragment() {
savePosition(0) savePosition(0)
} }
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tv = list2[itemPosition].item
tvViewModel?.changed()
val tvModel = tvListViewModel.getTVModel(itemPosition)
if (tvModel?.ysp() != null) {
Log.i(TAG, "ysp ${tvModel.getTV()}")
lifecycleScope.launch(Dispatchers.IO) {
tvModel.let { request?.fetchData(it) }
}
} else {
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
}
Toast.makeText(
activity,
tv.title,
Toast.LENGTH_SHORT
).show()
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
} }
@@ -211,7 +153,6 @@ class MainFragment : BrowseSupportFragment() {
} }
fun savePosition(position: Int) { fun savePosition(position: Int) {
tvListViewModel.setItemPosition(position)
with(sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putInt("position", position) putInt("position", position)
apply() apply()
@@ -222,62 +163,95 @@ class MainFragment : BrowseSupportFragment() {
view?.post { view?.post {
itemPosition-- itemPosition--
if (itemPosition == -1) { if (itemPosition == -1) {
itemPosition = tvListViewModel.size() - 1 itemPosition = list2.size - 1
} }
savePosition(itemPosition) savePosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val l = list2[itemPosition]
tvViewModel?.changed()
val tvModel = tvListViewModel.getTVModel(itemPosition)
if (tvModel?.ysp() != null) {
lifecycleScope.launch(Dispatchers.IO) {
tvModel.let { request?.fetchData(it) }
}
} else {
l.item.let { (activity as? MainActivity)?.play(it) }
}
setSelectedPosition(
l.rowPosition, false,
SelectItemViewHolderTask(l.itemPosition)
)
Toast.makeText(
activity,
l.item.title,
Toast.LENGTH_SHORT
).show()
} }
} }
fun next() { fun next() {
view?.post { view?.post {
itemPosition++ itemPosition++
if (itemPosition == tvListViewModel.size()) { if (itemPosition == list2.size) {
itemPosition = 0 itemPosition = 0
} }
savePosition(itemPosition) savePosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val l = list2[itemPosition]
tvViewModel?.changed()
val tvModel = tvListViewModel.getTVModel(itemPosition)
if (tvModel?.ysp() != null) {
lifecycleScope.launch(Dispatchers.IO) {
tvModel.let { request?.fetchData(it) }
}
} else {
l.item.let { (activity as? MainActivity)?.play(it) }
}
setSelectedPosition(
l.rowPosition, false,
SelectItemViewHolderTask(l.itemPosition)
)
Toast.makeText(
activity,
l.item.title,
Toast.LENGTH_SHORT
).show()
} }
} }
fun prevSource() { fun prevSource() {
view?.post { view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tv = list2[itemPosition].item
if (tvViewModel != null) { if (tv.videoUrl.size > 1) {
if (tvViewModel.videoUrl.value!!.size > 1) { tv.videoIndex--
val videoIndex = tvViewModel.videoIndex.value?.minus(1) if (tv.videoIndex == -1) {
if (videoIndex == -1) { tv.videoIndex = tv.videoUrl.size - 1
tvViewModel.setVideoIndex(tvViewModel.videoUrl.value!!.size - 1)
}
tvViewModel.changed()
} }
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
} }
} }
} }
fun nextSource() { fun nextSource() {
view?.post { view?.post {
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tv = list2[itemPosition].item
if (tvViewModel != null) {
if (tvViewModel.videoUrl.value!!.size > 1) { if (tv.videoUrl.size > 1) {
val videoIndex = tvViewModel.videoIndex.value?.plus(1) tv.videoIndex++
if (videoIndex == tvViewModel.videoUrl.value!!.size) { if (tv.videoIndex == tv.videoUrl.size) {
tvViewModel.setVideoIndex(0) tv.videoIndex = 0
}
tvViewModel.changed()
} }
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
} }
} }
} }
fun tvViewModel(): TVViewModel? {
return tvListViewModel.getTVViewModel(itemPosition)
}
private fun setupEventListeners() { private fun setupEventListeners() {
onItemViewClickedListener = ItemViewClickedListener() onItemViewClickedListener = ItemViewClickedListener()
onItemViewSelectedListener = ItemViewSelectedListener() onItemViewSelectedListener = ItemViewSelectedListener()
@@ -290,14 +264,20 @@ class MainFragment : BrowseSupportFragment() {
rowViewHolder: RowPresenter.ViewHolder, rowViewHolder: RowPresenter.ViewHolder,
row: Row row: Row
) { ) {
if (item is TVViewModel) { if (item is TV) {
if (itemPosition != item.id.value!!) { itemPosition = item.id
itemPosition = item.id.value!! savePosition(itemPosition)
savePosition(itemPosition)
val tvViewModel = tvListViewModel.getTVViewModel(itemPosition) val tvModel = tvListViewModel.getTVModel(itemPosition)
tvViewModel?.changed() if (tvModel?.ysp() != null) {
lifecycleScope.launch(Dispatchers.IO) {
tvModel.let { request?.fetchData(it) }
}
} else {
(activity as? MainActivity)?.play(item)
// (activity as? MainActivity)?.switchInfoFragment(item)
} }
(activity as? MainActivity)?.switchMainFragment() (activity as? MainActivity)?.switchMainFragment()
} }
} }
@@ -308,17 +288,18 @@ class MainFragment : BrowseSupportFragment() {
itemViewHolder: Presenter.ViewHolder?, item: Any?, itemViewHolder: Presenter.ViewHolder?, item: Any?,
rowViewHolder: RowPresenter.ViewHolder, row: Row rowViewHolder: RowPresenter.ViewHolder, row: Row
) { ) {
if (item is TVViewModel) { if (itemViewHolder == null) {
tvListViewModel.setItemPositionCurrent(item.id.value!!) view?.post {
val it = list2[itemPosition]
setSelectedPosition(
it.rowPosition, false,
SelectItemViewHolderTask(it.itemPosition)
)
}
} }
} }
} }
override fun onResume() {
super.onResume()
view!!.requestFocus()
}
companion object { companion object {
private const val TAG = "MainFragment" private const val TAG = "MainFragment"
} }
@@ -0,0 +1,55 @@
package com.lizongying.mytv
import android.content.Context
import android.view.KeyEvent
import android.view.View
import androidx.leanback.media.MediaPlayerAdapter
import androidx.leanback.media.PlaybackTransportControlGlue
import androidx.leanback.media.PlayerAdapter
class PlaybackControlGlue(
context: Context?,
playerAdapter: PlayerAdapter?,
) :
PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event!!.action == KeyEvent.ACTION_DOWN) {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER -> {
(context as? MainActivity)?.switchMainFragment()
}
KeyEvent.KEYCODE_DPAD_UP -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.prev()
}
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.next()
}
}
KeyEvent.KEYCODE_DPAD_LEFT -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.prevSource()
}
}
KeyEvent.KEYCODE_DPAD_RIGHT -> {
if ((context as? MainActivity)?.mainFragmentIsHidden() == true) {
(context as? MainActivity)?.nextSource()
}
}
}
}
return super.onKey(v, keyCode, event)
}
companion object {
private const val TAG = "CustomPlaybackControlGlue"
}
}
@@ -0,0 +1,66 @@
package com.lizongying.mytv
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.leanback.app.VideoSupportFragment
import androidx.leanback.app.VideoSupportFragmentGlueHost
import androidx.leanback.media.PlayerAdapter
import androidx.leanback.media.PlaybackTransportControlGlue
import androidx.leanback.widget.PlaybackControlsRow
import java.io.IOException
class PlaybackFragment : VideoSupportFragment() {
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
private var playerAdapter: ExoPlayerAdapter? = null
private var lastVideoUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playerAdapter = ExoPlayerAdapter(context)
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
view?.isFocusable = false
view?.isFocusableInTouchMode = false
}
override fun showControlsOverlay(runAnimation: Boolean) {
}
fun play(tv: TV) {
val videoUrl = tv.videoUrl[tv.videoIndex]
if (videoUrl == "") {
Log.e(TAG, "tv $tv")
Log.e(TAG, "videoUrl is empty")
return
}
if (videoUrl == lastVideoUrl) {
Log.e(TAG, "videoUrl is duplication")
return
}
lastVideoUrl = videoUrl
playerAdapter?.reset()
val glueHost = VideoSupportFragmentGlueHost(this@PlaybackFragment)
mTransportControlGlue = PlaybackControlGlue(activity, playerAdapter)
mTransportControlGlue.host = glueHost
mTransportControlGlue.playWhenPrepared()
try {
playerAdapter?.setDataSource(Uri.parse(videoUrl))
} catch (e: IOException) {
return
}
hideControlsOverlay(false)
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
}
@@ -1,115 +0,0 @@
package com.lizongying.mytv
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.lizongying.mytv.databinding.PlayerBinding
import com.lizongying.mytv.models.TVViewModel
class PlayerFragment : Fragment() {
private var _binding: PlayerBinding? = null
private var playerView: PlayerView? = null
private var videoUrl: String? = null
private var tvViewModel: TVViewModel? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = PlayerBinding.inflate(inflater, container, false)
playerView = _binding!!.playerView
(activity as MainActivity).playerFragment = this
playerView?.viewTreeObserver?.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
playerView!!.viewTreeObserver.removeOnGlobalLayoutListener(this)
playerView!!.player = activity?.let {
ExoPlayer.Builder(it)
.build()
}
playerView!!.player?.playWhenReady = true
playerView!!.player?.addListener(object : Player.Listener {
override fun onVideoSizeChanged(videoSize: VideoSize) {
val aspectRatio = 16f / 9f
val layoutParams = playerView?.layoutParams
layoutParams?.width =
(playerView?.measuredHeight?.times(aspectRatio))?.toInt()
playerView?.layoutParams = layoutParams
}
//
// override fun onPlayerError(error: PlaybackException) {
// super.onPlayerError(error)
// }
})
if (videoUrl !== null) {
playerView!!.player?.run {
videoUrl?.let { MediaItem.fromUri(it) }?.let { setMediaItem(it) }
prepare()
}
videoUrl = null
}
}
})
Log.i(TAG, "PlayerFragment onCreateView")
return _binding!!.root
}
@OptIn(UnstableApi::class)
fun play(tvViewModel: TVViewModel) {
this.tvViewModel = tvViewModel
val videoUrlCurrent =
tvViewModel.videoIndex.value?.let { tvViewModel.videoUrl.value?.get(it) }
if (playerView == null || playerView?.player == null) {
Log.i(TAG, "playerView not ready $view}")
videoUrl = videoUrlCurrent
} else {
Log.i(TAG, "playerView ok")
playerView?.player?.run {
val mediaItem = MediaItem.Builder()
tvViewModel.id.value?.let { mediaItem.setMediaId(it.toString()) }
videoUrlCurrent?.let { mediaItem.setUri(it) }
setMediaItem(mediaItem.build())
prepare()
}
}
}
override fun onStart() {
super.onStart()
if (playerView != null) {
playerView!!.player?.play()
}
}
override fun onStop() {
super.onStop()
if (playerView != null) {
playerView!!.player?.stop()
}
}
override fun onDestroy() {
super.onDestroy()
if (playerView != null) {
playerView!!.player?.release()
}
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
}
+26 -107
View File
@@ -1,23 +1,17 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import com.lizongying.mytv.Utils.getDateFormat
import com.lizongying.mytv.api.ApiClient import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.BtraceClient
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.ProtoClient import com.lizongying.mytv.api.ProtoClient
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.YSPProtoService import com.lizongying.mytv.api.YSPProtoService
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 retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@@ -29,15 +23,10 @@ import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) { class Request(var context: Context) {
private var ysp: YSP? = null private var ysp: YSP? = null
private var yspApiService: YSPApiService? = null private var yspApiService: YSPApiService? = null
private var yspBtraceService: YSPBtraceService? = null
private var yspProtoService: YSPProtoService? = null private var yspProtoService: YSPProtoService? = null
// TODO onDestroy
private val handler = Handler(Looper.getMainLooper())
private lateinit var myRunnable: MyRunnable
private var mapping = mapOf( private var mapping = mapOf(
"CCTV4K" to "CCTV4K 超高清", "CCTV4K" to "CCTV4K",
"CCTV1" to "CCTV1 综合", "CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经", "CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际", "CCTV4" to "CCTV4 中文国际",
@@ -48,7 +37,7 @@ class Request(var context: Context) {
"CCTV10" to "CCTV10 科教", "CCTV10" to "CCTV10 科教",
"CCTV11" to "CCTV11 戏曲", "CCTV11" to "CCTV11 戏曲",
"CCTV12" to "CCTV12 社会与法", "CCTV12" to "CCTV12 社会与法",
"CCTV13" to "CCTV13 新闻", // "CCTV13" to "CCTV13",
"CCTV14" to "CCTV14 少儿", "CCTV14" to "CCTV14 少儿",
"CCTV15" to "CCTV15 音乐", "CCTV15" to "CCTV15 音乐",
"CCTV16-HD" to "CCTV16 奥林匹克", "CCTV16-HD" to "CCTV16 奥林匹克",
@@ -58,7 +47,7 @@ class Request(var context: Context) {
"CGTN俄语频道" to "CGTN 俄语频道", "CGTN俄语频道" to "CGTN 俄语频道",
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道", "CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道", "CGTN西班牙语频道" to "CGTN 西班牙语频道",
"CGTN外语纪录频道" to "CGTN 纪录频道", // "CGTN外语纪录频道" to "CGTN外语纪录频道",
"东方卫视" to "东方卫视", "东方卫视" to "东方卫视",
"湖南卫视" to "湖南卫视", "湖南卫视" to "湖南卫视",
@@ -88,18 +77,11 @@ class Request(var context: Context) {
ysp = YSP(context) ysp = YSP(context)
} }
yspApiService = ApiClient().yspApiService yspApiService = ApiClient().yspApiService
yspBtraceService = BtraceClient().yspBtraceService
yspProtoService = ProtoClient().yspProtoService yspProtoService = ProtoClient().yspProtoService
} }
fun fetchData(tvModel: TVViewModel) { fun fetchData(tvModel: TVViewModel) {
if (::myRunnable.isInitialized) {
handler.removeCallbacks(myRunnable)
}
tvModel.seq = 0
val data = ysp?.switch(tvModel) val data = ysp?.switch(tvModel)
val title = tvModel.title.value
val request = data?.let { LiveInfoRequest(it) } val request = data?.let { LiveInfoRequest(it) }
request?.let { yspApiService?.getLiveInfo(it) } request?.let { yspApiService?.getLiveInfo(it) }
@@ -107,6 +89,7 @@ class Request(var context: Context) {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) { override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) { if (response.isSuccessful) {
val liveInfo = response.body() val liveInfo = response.body()
Log.i(TAG, "liveInfo $liveInfo")
if (liveInfo?.data?.playurl != null) { if (liveInfo?.data?.playurl != null) {
val chanll = liveInfo.data.chanll val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode( val decodedBytes = Base64.decode(
@@ -116,84 +99,30 @@ class Request(var context: Context) {
val decodedString = String(decodedBytes) val decodedString = String(decodedBytes)
val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""") val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
val matchResult = regex.find(decodedString) val matchResult = regex.find(decodedString)
var keyBytes = byteArrayOf()
var ivBytes = byteArrayOf()
if (matchResult != null) { if (matchResult != null) {
val (key, iv) = matchResult.destructured val (key, iv) = matchResult.destructured
val keyBytes = Base64.decode(key, Base64.DEFAULT) keyBytes = Base64.decode(key, Base64.DEFAULT)
val ivBytes = Base64.decode(iv, Base64.DEFAULT) ivBytes = Base64.decode(iv, Base64.DEFAULT)
val url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES( }
tvModel.updateVideoUrlByYSP(
liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0), keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes ivBytes
).uppercase() ).uppercase()
Log.i(TAG, "$title url $url") )
tvModel.addVideoUrl(url)
tvModel.allReady()
myRunnable = MyRunnable(tvModel)
handler.post(myRunnable)
} else {
Log.e(TAG, "$title key error")
tvModel.firstSource()
}
} else {
Log.e(TAG, "$title url error $request")
tvModel.firstSource()
} }
} else {
Log.e(TAG, "$title status error")
tvModel.firstSource()
} }
} }
override fun onFailure(call: Call<LiveInfo>, t: Throwable) { override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title request error") Log.i(TAG, "get data error")
tvModel.firstSource()
} }
}) })
} }
inner class MyRunnable(private val tvModel: TVViewModel) : Runnable {
override fun run() {
fetchBtrace(tvModel)
handler.postDelayed(this, 60000)
}
}
fun fetchBtrace(tvModel: TVViewModel) {
val title = tvModel.title.value
val guid = ysp?.getGuid()!!
val pid = tvModel.pid.value!!
val sid = tvModel.sid.value!!
yspBtraceService?.kvcollect(
c_timestamp = ysp?.generateGuid()!!,
guid = guid,
c_guid = guid,
prog = sid,
viewid = sid,
fpid = pid,
livepid = pid,
sUrl = "https://www.yangshipin.cn/#/tv/home?pid=$pid",
playno = ysp?.getRand()!!,
ftime = getDateFormat("yyyy-MM-dd HH:mm:ss"),
seq = tvModel.seq.toString(),
)
?.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>, response: Response<Void>) {
if (response.isSuccessful) {
Log.d(TAG, "$title kvcollect success")
} else {
Log.e(TAG, "$title kvcollect status error")
tvModel.firstSource()
}
}
override fun onFailure(call: Call<Void>, t: Throwable) {
Log.e(TAG, "$title kvcollect request error")
}
})
tvModel.seq++
}
fun fetchPage() { fun fetchPage() {
yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> { yspProtoService?.getPage()?.enqueue(object : Callback<pageModel.Response> {
override fun onResponse( override fun onResponse(
@@ -219,8 +148,7 @@ class Request(var context: Context) {
if (!mapping.containsKey(item.channelName)) { if (!mapping.containsKey(item.channelName)) {
continue continue
} }
val tv = val tv = TVList.list[channelType]?.get(mapping[item.channelName])
TVList.list[channelType]?.find { it.title == mapping[item.channelName] }
if (tv != null) { if (tv != null) {
tv.logo = item.tvLogo tv.logo = item.tvLogo
tv.pid = item.pid tv.pid = item.pid
@@ -237,31 +165,22 @@ class Request(var context: Context) {
}) })
} }
fun fetchProgram(tvViewModel: TVViewModel) { fun fetchProgram(tvModel: TVViewModel) {
val title = tvViewModel.title.value yspProtoService?.getProgram("", "")?.enqueue(object : Callback<LiveInfo> {
yspProtoService?.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd")) override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
?.enqueue(object : Callback<epgProgramModel.Response> { if (response.isSuccessful) {
override fun onResponse( val liveInfo = response.body()
call: Call<epgProgramModel.Response>, Log.i(TAG, "${liveInfo?.data?.playurl}")
response: Response<epgProgramModel.Response>
) {
if (response.isSuccessful) {
val program = response.body()
if (program != null) {
tvViewModel.addProgram(program.dataListList)
Log.i(TAG, "$title program ${program.dataListList.size}")
}
}
} }
}
override fun onFailure(call: Call<epgProgramModel.Response>, t: Throwable) { override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.e(TAG, "$title program request failed $t") }
} })
})
} }
private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String { private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String {
val plaintext = var plaintext =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}""" """{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}"""
return try { return try {
val keySpec = SecretKeySpec(key, "DESede") val keySpec = SecretKeySpec(key, "DESede")
@@ -10,7 +10,6 @@ data class TV(
var logo: String = "", var logo: String = "",
var pid: String = "", var pid: String = "",
var sid: String = "", var sid: String = "",
var programId: String = "",
) : Serializable { ) : Serializable {
override fun toString(): String { override fun toString(): String {
@@ -22,7 +21,6 @@ data class TV(
", logo='" + logo + '\'' + ", logo='" + logo + '\'' +
", pid='" + pid + '\'' + ", pid='" + pid + '\'' +
", sid='" + sid + '\'' + ", sid='" + sid + '\'' +
", programId='" + programId + '\'' +
'}' '}'
} }
+204 -315
View File
@@ -1,283 +1,154 @@
package com.lizongying.mytv package com.lizongying.mytv
import android.util.Log
object TVList { object TVList {
val list: Map<String, List<TV>> by lazy { val list: Map<String, Map<String, TV>> by lazy {
setupTV() setupTV()
} }
private var mappingLogo = mapOf(
"CCTV4K" to "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp",
"CCTV4K 超高清" to "https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp",
"CCTV1" to "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp",
"CCTV1 综合" to "https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp",
"CCTV2" to "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp",
"CCTV2 财经" to "https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp",
"CCTV3" to "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp",
"CCTV3 综艺" to "https://resources.yangshipin.cn/assets/oms/image/202306/7b7a65c712450da3deb6ca66fbacf4f9aee00d3f20bd80eafb5ada01ec63eb3a.png?imageMogr2/format/webp",
"CCTV4" to "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"CCTV4 中文国际" to "https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp",
"CCTV5" to "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"CCTV5 体育" to "https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp",
"CCTV5+" to "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"CCTV5+ 体育赛事" to "https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp",
"CCTV6" to "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"CCTV6 电影" to "https://resources.yangshipin.cn/assets/oms/image/202306/741515efda91f03f455df8a7da4ee11fa9329139c276435cf0a9e2af398d5bf2.png?imageMogr2/format/webp",
"CCTV7" to "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"CCTV7 国防军事" to "https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp",
"CCTV8" to "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"CCTV8 电视剧" to "https://resources.yangshipin.cn/assets/oms/image/202306/ad51de94426a0ba039e6dd6a8534ea98ecc813a6176bde87b4f18cc34d6d7590.png?imageMogr2/format/webp",
"CCTV9" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"CCTV9 记录" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp",
"CCTV10" to "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"CCTV10 科教" to "https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp",
"CCTV11" to "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"CCTV11 戏曲" to "https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp",
"CCTV12" to "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"CCTV12 社会与法" to "https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp",
"CCTV13" to "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"CCTV13 新闻" to "https://resources.yangshipin.cn/assets/oms/image/202306/266da7b43c03e2312186b4a999e0f060e8f15b10d2cc2c9aa32171819254cf1a.png?imageMogr2/format/webp",
"CCTV14" to "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"CCTV14 少儿" to "https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp",
"CCTV15" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"CCTV15 音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp",
"CCTV16" to "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"CCTV16 奥林匹克" to "https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp",
"CCTV17" to "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"CCTV17 农业农村" to "https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp",
"CGTN" to "https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp",
"CGTN 法语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp",
"CGTN 俄语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp",
"CGTN 阿拉伯语频道" to "https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp",
"CGTN 西班牙语频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp",
"CGTN 纪录频道" to "https://resources.yangshipin.cn/assets/oms/image/202309/74d3ac436a7e374879578de1d87a941fbf566d39d5632b027c5097891ed32bd5.png?imageMogr2/format/webp",
"风云剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/4d549e53e6d0f632d5a633d1945280797b153e588f919221a07faa869812cc89.png?imageMogr2/format/webp",
"第一剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/a556bd7d93ce65e18f243a8892b5604f4faa994a4897315914216a710a706208.png?imageMogr2/format/webp",
"怀旧剧场" to "https://resources.yangshipin.cn/assets/oms/image/202306/5661bd04fecdb6e899f801147a22ab5d3a475bf2b62e30aec2c0023190ebc9b1.png?imageMogr2/format/webp",
"风云音乐" to "https://resources.yangshipin.cn/assets/oms/image/202306/bbf1d024c5228b8dd128b0e3cb1717d173fab4ee84c3a4c8a57b1a215362ca3b.png?imageMogr2/format/webp",
"兵器科技" to "https://resources.yangshipin.cn/assets/oms/image/202306/4c6b6a6d3839889f34d33db3c2f80233b26b74d3489b393487635f8704e70796.png?imageMogr2/format/webp",
"风云足球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cd1e2bb52b06a991de168733e5ff0f1d85adc8042d40c8f393f723543e5dd08a.png?imageMogr2/format/webp",
"高尔夫网球" to "https://resources.yangshipin.cn/assets/oms/image/202306/cdd1b31ede7a5ad049ed53d9a072422f829e72dd062ed2c19e077fdd01699071.png?imageMogr2/format/webp",
"女性时尚" to "https://resources.yangshipin.cn/assets/oms/image/202306/fa28955ce8b2539d728bf4c6a13a46ff57ad76eae46627f7bcfb1ed8a613d3fc.png?imageMogr2/format/webp",
"央视文化精品" to "https://resources.yangshipin.cn/assets/oms/image/202306/14ac5ce40482cacd3d4b37435222bfe86af2b452a2f04ecbfc1d13d76edd7c57.png?imageMogr2/format/webp",
"央视台球" to "https://resources.yangshipin.cn/assets/oms/image/202306/10e14a92478011aa6c3c8562e62127f3b1908e29fcd78e4b2b24b9e6d3ec2fbc.png?imageMogr2/format/webp",
"电视指南" to "https://resources.yangshipin.cn/assets/oms/image/202306/244d72c0eb1615ed7d51c2f5db5a67f306aa3f58c05bc2d34de3aa7e956dc8c9.png?imageMogr2/format/webp",
"卫生健康" to "https://resources.yangshipin.cn/assets/oms/image/202306/54a6863656fdfd8f803be193ddf22441c5000a108833889816fd2d8911715ce8.png?imageMogr2/format/webp",
"东方卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp",
"湖南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp",
"湖北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp",
"辽宁卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp",
"江苏卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp",
"江西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp",
"山东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp",
"广东卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp",
"广西卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp",
"重庆卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp",
"河南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp",
"河北卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp",
"贵州卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp",
"北京卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp",
"黑龙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp",
"浙江卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp",
"安徽卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp",
"深圳卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.png?imageMogr2/format/webp",
"四川卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp",
"东南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp",
"海南卫视" to "https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp",
)
private var mappingEPG = mapOf(
"CCTV4K" to "600002264",
"CCTV4K 超高清" to "600002264",
"CCTV1" to "600001859",
"CCTV1 综合" to "600001859",
"CCTV2" to "600001800",
"CCTV2 财经" to "600001800",
"CCTV3" to "600001801",
"CCTV3 综艺" to "600001801",
"CCTV4" to "600001814",
"CCTV4 中文国际" to "600001814",
"CCTV5" to "600001818",
"CCTV5 体育" to "600001818",
"CCTV5+" to "600001817",
"CCTV5+ 体育赛事" to "600001817",
"CCTV6" to "600001802",
"CCTV6 电影" to "600001802",
"CCTV7" to "600004092",
"CCTV7 国防军事" to "600004092",
"CCTV8" to "600001803",
"CCTV8 电视剧" to "600001803",
"CCTV9" to "600004078",
"CCTV9 记录" to "600004078",
"CCTV10" to "600001805",
"CCTV10 科教" to "600001805",
"CCTV11" to "600001806",
"CCTV11 戏曲" to "600001806",
"CCTV12" to "600001807",
"CCTV12 社会与法" to "600001807",
"CCTV13" to "600001811",
"CCTV13 新闻" to "600001811",
"CCTV14" to "600001809",
"CCTV14 少儿" to "600001809",
"CCTV15" to "600001815",
"CCTV15 音乐" to "600001815",
"CCTV16" to "600098637",
"CCTV16 奥林匹克" to "600098637",
"CCTV17" to "600001810",
"CCTV17 农业农村" to "600001810",
"CGTN" to "600014550",
"CGTN 法语频道" to "600084704",
"CGTN 俄语频道" to "600084758",
"CGTN 阿拉伯语频道" to "600084782",
"CGTN 西班牙语频道" to "600084744",
"CGTN 记录频道" to "600084781",
"风云剧场" to "600099658",
"第一剧场" to "600099655",
"怀旧剧场" to "600099620",
"风云音乐" to "600099660",
"兵器科技" to "600099649",
"风云足球" to "600099636",
"高尔夫网球" to "600099659",
"女性时尚" to "600099650",
"央视文化精品" to "600099653",
"央视台球" to "600099652",
"电视指南" to "600099656",
"卫生健康" to "600099651",
"东方卫视" to "600002483",
"湖南卫视" to "600002475",
"湖北卫视" to "600002508",
"辽宁卫视" to "600002505",
"江苏卫视" to "600002521",
"江西卫视" to "600002503",
"山东卫视" to "600002513",
"广东卫视" to "600002485",
"广西卫视" to "600002509",
"重庆卫视" to "600002531",
"河南卫视" to "600002525",
"河北卫视" to "600002493",
"贵州卫视" to "600002490",
"北京卫视" to "600002309",
"黑龙江卫视" to "600002498",
"浙江卫视" to "600002520",
"安徽卫视" to "600002532",
"深圳卫视" to "600002481",
"四川卫视" to "600002516",
"东南卫视" to "600002484",
"海南卫视" to "600002506",
)
private var mappingVideo = mapOf(
"CCTV4K" to arrayOf("600002264", "2000266303"),
"CCTV4K 超高清" to arrayOf("600002264", "2000266303"),
"CCTV1" to arrayOf("600001859", "2000210103"),
"CCTV1 综合" to arrayOf("600001859", "2000210103"),
"CCTV2" to arrayOf("600001800", "2000203603"),
"CCTV2 财经" to arrayOf("600001800", "2000203603"),
// "CCTV3" to arrayOf("600001801",""),
// "CCTV3 综艺" to arrayOf("600001801",""),
"CCTV4" to arrayOf("600001814", "2000204803"),
"CCTV4 中文国际" to arrayOf("600001814", "2000204803"),
"CCTV5" to arrayOf("600001818", "2000205103"),
"CCTV5 体育" to arrayOf("600001818", "2000205103"),
"CCTV5+" to arrayOf("600001817", "2000204503"),
"CCTV5+ 体育赛事" to arrayOf("600001817", "2000204503"),
// "CCTV6" to arrayOf("600001802",""),
// "CCTV6 电影" to arrayOf("600001802",""),
"CCTV7" to arrayOf("600004092", "2000510003"),
"CCTV7 国防军事" to arrayOf("600004092", "2000510003"),
// "CCTV8" to arrayOf("600001803",""),
// "CCTV8 电视剧" to arrayOf("600001803",""),
"CCTV9" to arrayOf("600004078", "2000499403"),
"CCTV9 记录" to arrayOf("600004078", "2000499403"),
"CCTV10" to arrayOf("600001805", "2000203503"),
"CCTV10 科教" to arrayOf("600001805", "2000203503"),
"CCTV11" to arrayOf("600001806", "2000204103"),
"CCTV11 戏曲" to arrayOf("600001806", "2000204103"),
"CCTV12" to arrayOf("600001807", "2000202603"),
"CCTV12 社会与法" to arrayOf("600001807", "2000202603"),
// "CCTV13" to arrayOf("600001811",""),
// "CCTV13 新闻" to arrayOf("600001811",""),
"CCTV14" to arrayOf("600001809", "2000204403"),
"CCTV14 少儿" to arrayOf("600001809", "2000204403"),
"CCTV15" to arrayOf("600001815", "2000205003"),
"CCTV15 音乐" to arrayOf("600001815", "2000205003"),
"CCTV16" to arrayOf("600098637", "2012375003"),
"CCTV16 奥林匹克" to arrayOf("600098637", "2012375003"),
"CCTV17" to arrayOf("600001810", "2000204203"),
"CCTV17 农业农村" to arrayOf("600001810", "2000204203"),
"CGTN" to arrayOf("600014550", "2001656803"),
"CGTN 法语频道" to arrayOf("600084704", "2010153503"),
"CGTN 俄语频道" to arrayOf("600084758", "2010152603"),
"CGTN 阿拉伯语频道" to arrayOf("600084782", "2010155203"),
"CGTN 西班牙语频道" to arrayOf("600084744", "2010152503"),
"CGTN 记录频道" to arrayOf("600084781", "2010155403"),
// "风云剧场" to arrayOf("600099658",""),
// "第一剧场" to arrayOf("600099655",""),
// "怀旧剧场" to arrayOf("600099620",""),
// "风云音乐" to arrayOf("600099660",""),
// "兵器科技" to arrayOf("600099649",""),
// "风云足球" to arrayOf("600099636",""),
// "高尔夫网球" to arrayOf("600099659",""),
// "女性时尚" to arrayOf("600099650",""),
// "央视文化精品" to arrayOf("600099653",""),
// "央视台球" to arrayOf("600099652",""),
// "电视指南" to arrayOf("600099656",""),
// "卫生健康" to arrayOf("600099651",""),
"东方卫视" to arrayOf("600002483", "2000292403"),
"湖南卫视" to arrayOf("600002475", "2000296203"),
"湖北卫视" to arrayOf("600002508", "2000294503"),
"辽宁卫视" to arrayOf("600002505", "2000281303"),
"江苏卫视" to arrayOf("600002521", "2000295603"),
"江西卫视" to arrayOf("600002503", "2000294103"),
"山东卫视" to arrayOf("600002513", "2000294803"),
"广东卫视" to arrayOf("600002485", "2000292703"),
"广西卫视" to arrayOf("600002509", "2000294203"),
"重庆卫视" to arrayOf("600002531", "2000297803"),
"河南卫视" to arrayOf("600002525", "2000296103"),
"河北卫视" to arrayOf("600002493", "2000293403"),
"贵州卫视" to arrayOf("600002490", "2000293303"),
"北京卫视" to arrayOf("600002309", "2000272103"),
"黑龙江卫视" to arrayOf("600002498", "2000293903"),
"浙江卫视" to arrayOf("600002520", "2000295503"),
"安徽卫视" to arrayOf("600002532", "2000298003"),
"深圳卫视" to arrayOf("600002481", "2000292203"),
"四川卫视" to arrayOf("600002516", "2000295003"),
"东南卫视" to arrayOf("600002484", "2000292503"),
"海南卫视" to arrayOf("600002506", "2000291503"),
)
private var count: Int = 0 private var count: Int = 0
private fun setupTV(): Map<String, List<TV>> { private fun setupTV(): Map<String, Map<String, TV>> {
val tvs = """ val tvs = """
央视频道 央视频道
CCTV4K 超高清, CCTV4K,,https://resources.yangshipin.cn/assets/oms/image/202306/3e9d06fd7244d950df5838750f1c6ac3456e172b51caca2c16d2282125b111e8.png?imageMogr2/format/webp,600002264,2000266303
CCTV1 综合,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8 CCTV1 综合,http://tvpull.dxhmt.cn/tv/11481-4.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d57905b93540bd15f0c48230dbbbff7ee0d645ff539e38866e2d15c8b9f7dfcd.png?imageMogr2/format/webp,600001859,2000210103
CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8 CCTV2 财经,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/20115388de0207131af17eac86c33049b95d69eaff064e55653a1b941810a006.png?imageMogr2/format/webp,600001800,2000203603
CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8 CCTV3 综艺,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8 CCTV4 中文国际,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8;http://hlsbkmgsplive.miguvideo.com/wd_r2/cctv/cctv4hd/1500/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/f357e58fdbcc076a3d65e1f958c942b2e14f14342c60736ceed98b092d35356a.png?imageMogr2/format/webp,600001814,2000204803
CCTV5 体育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8 CCTV4 中文国际美洲,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv4meihd/57/index.m3u8?&encrypt=
CCTV5+ 体育赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8 CCTV4 中文国际欧洲,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv4ouhd/51/index.m3u8?&encrypt=
CCTV6 电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8 CCTV5 体育,http://hlsbkmgsplive.miguvideo.com/migu/kailu/cctv5hd265/57/20191230/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/0a6a7138952675983a3d854df7688557b286d59aa06166edae51506f9204d655.png?imageMogr2/format/webp,600001818,2000205103
CCTV7 国防军事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8 CCTV5+ 体育赛事,http://hlsbkmgsplive.miguvideo.com/wd_r2/cctv/cctv5plusnew/2500/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/649ad76a90bfef55b05db9fe52e006487280f619089099d5dc971e387fc6eff0.png?imageMogr2/format/webp,600001817,2000204503
CCTV8 电视剧,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8 CCTV6 电影,http://hlsbkmgsplive.miguvideo.com/wd_r2/ocn/cctv6hd/3000/index.m3u8?&encrypt=
CCTV9 记录,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8 CCTV7 国防军事,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv7hd/51/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/b29af94e295ebdf646cefb68122c429b9cd921f498ca20d2d8070252536f9ff9.png?imageMogr2/format/webp,600004092,2000510003
CCTV10 科教,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8 CCTV8 电视剧,http://hlsbkmgsplive.miguvideo.com/wd_r2/ocn/cctv8hd/3000/index.m3u8?&encrypt=
CCTV11 戏曲,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8 CCTV9 记录,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv9hd/57/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/2ed1b4deeca179d5db806bb941790f82eb92a1b7299c1c38fe027f95a5caee5e.png?imageMogr2/format/webp,600004078,2000499403
CCTV12 社会与法,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8 CCTV10 科教,http://hlsbkmgsplive.miguvideo.com/wd_r2/2018/ocn/cctv10hd/2000/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/aa6157ec65188cd41826e5a2f088c3d6d153205f5f6428258d12c59999e221aa.png?imageMogr2/format/webp,600001805,2000203503
CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8 CCTV11 戏曲,http://hlsbkmgsplive.miguvideo.com/migu/kailu/cctv11hd/57/20200103/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/ed12ed7c7a1034dae4350011fe039284c5d5a836506b28c9e32e3c75299625c0.png?imageMogr2/format/webp,600001806,2000204103
CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8 CCTV12 社会与法,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv12hd/57/index.m3u8?&encrypt=, https://resources.yangshipin.cn/assets/oms/image/202306/484083cffaa40df7e659565e8cb4d1cc740158a185512114167aa21fa0c59240.png?imageMogr2/format/webp,600001807,2000202603
CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8 CCTV13 新闻,https://live-play.cctvnews.cctv.com/cctv/merge_cctv13.m3u8;http://hlsbkmgsplive.miguvideo.com/envivo_x/2018/SD/cctv13/2000/index.m3u8?&encrypt=
CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8 CCTV14 少儿,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8;http://hlsbkmgsplive.miguvideo.com/wd_r2/ocn/cctv14hd/3000/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/af6b603896938dc346fbb16abfc63c12cba54b0ec9d18770a15d347d115f12d5.png?imageMogr2/format/webp,600001809,2000204403
CCTV17 农业农村,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8 CCTV15 音乐,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8;http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv15hd/51/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/2ceee92188ef684efe0d8b90839c4f3ad450d179dc64d59beff417059453af47.png?imageMogr2/format/webp,600001815,2000205003
CGTN,http://live.cgtn.com/1000/prog_index.m3u8 CCTV16 奥林匹克,http://39.134.24.162/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/53793fa7bacd3a93ff6dc5d2758418985e1f952a316c335d663b572d8bdcd74d.png?imageMogr2/format/webp,600098637,2012375003
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8 CCTV17 农业农村,http://hlsbkmgsplive.miguvideo.com/migu/kailu/20200324/cctv17hd/57/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/ddef563072f8bad2bea5b9e52674cb7b4ed50efb20c26e61994dfbdf05c1e3c0.png?imageMogr2/format/webp,600001810,2000204203
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8 CGTN,http://hlsbkmgsplive.miguvideo.com/envivo_x/2018/SD/cctvnews/1000/index.m3u8?&encrypt=,https://resources.yangshipin.cn/assets/oms/image/202306/a72dff758ca1c17cd0ecc8cedc11b893d208f409d5e6302faa0e9d298848abc3.png?imageMogr2/format/webp,600014550,2001656803
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8 CGTN 新闻频道,http://live.cgtn.com/1000/prog_index.m3u8
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8;http://livees.cgtn.com/1000e/prog_index.m3u8
CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8 CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
CGTN 法语频道,https://livefr.cgtn.com/1000f/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/a8d0046a47433d952bf6ed17062deb8bd2184ba9aec0f7781df6bf9487a3ffcf.png?imageMogr2/format/webp,600084704,2010153503
CGTN 俄语频道,http://liveru.cgtn.com/1000r/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/bf0a820893cbaf20dd0333e27042e1ef9c8806e5b602b6a8c95af399db0bc77a.png?imageMogr2/format/webp,600084758,2010152603
CGTN 西班牙语频道,http://livees.cgtn.com/500e/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202309/7c337e3dbe64402ec7e4678a619a4a6d95144e42f35161181ff78e143b7cf67a.png?imageMogr2/format/webp,600084744,2010152503
CGTN 阿拉伯语频道,http://livear.cgtn.com/1000a/prog_index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/2e44e2aa3e7a1cedf07fd0ae59fe69e86a60a2632660a006e3e9e7397b2d107e.png?imageMogr2/format/webp,600084782,2010155203
CGTN 拉丁美洲频道,http://livees.cgtn.com/1000e/prog_index.m3u8
书画频道,http://211.103.180.178:8234/live_hls/hdmi.m3u8
地方频道
东方卫视,,https://resources.yangshipin.cn/assets/oms/image/202306/9bd372ca292a82ce3aa08772b07efc4af1f85c21d1f268ea33440c49e9a0a488.png?imageMogr2/format/webp,600002483,2000292403
内蒙古卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226389/index.m3u8
湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/4120e89d3079d08aa17d382f69a2308ec70839b278367763c34a34666c75cb88.png?imageMogr2/format/webp,600002475,2000296203
湖北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/7a6be5a2bb1dc53a945c016ff1f525dc4a84c51db371c15c89aa55404b0ba784.png?imageMogr2/format/webp,600002508,2000294503
辽宁卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/ac4ed6058a87c101ae7147ebc38905d0cae047fb73fd277ee5049b84f52bda36.png?imageMogr2/format/webp,600002505,2000281303
江苏卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/380ad685c0c1d5b2c902246b8d2df6d3f9b45e2837abcfe493075bbded597a31.png?imageMogr2/format/webp,600002521,2000295603
江西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3c760d0d00463855890e8a1864ea4a6b6dd66b90c29b4ac714a4b17c16519871.png?imageMogr2/format/webp,600002503,2000294103
西藏卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/22d403f07a7cf5410b3ad3ddb65a11aa229a32475fac213f5344c9f0ec330ca1.png?imageMogr2/format/webp,600002513,2000294803
山西卫视,http://39.134.24.161/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225763/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/28886880a4dc0f06fb7e0a528a1def0591d61a65870e29176ede0cc92033bbfd.png?imageMogr2/format/webp,600002485,2000292703
广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/54b7e97cb816bb223fe05f3fc44da2c7820eb66e8550c19d23100f2c414ecc38.png?imageMogr2/format/webp,600002509,2000294203
重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/657651f411de2673d1770d9a78b44c1265704f7468cc41d4be7f51d630768494.png?imageMogr2/format/webp,600002531,2000297803
甘肃卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225633/index.m3u8
青海卫视,http://stream.qhbtv.com/qhws/sd/live.m3u8
陕西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226457/index.m3u8
河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/74925962148a6d31c85808b6cd4e444c2a54bab393d2c5fc85e960b50e22fa86.png?imageMogr2/format/webp,600002525,2000296103
河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d545becdc81c60197b08c7f47380705e4665ed3fe55efc8b855e486f6e655378.png?imageMogr2/format/webp,600002493,2000293403
云南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226444/index.m3u8
贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/4eb45f4781d33d872af027dc01c941559aab55667dd99cc5c22bef7037807b13.png?imageMogr2/format/webp,600002490,2000293303
新疆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226460/index.m3u8
北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/f4f23633c578beea49a3841d88d3490100f029ee349059fa532869db889872c5.png?imageMogr2/format/webp,600002309,2000272103
天津卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225740/index.m3u8
黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d8273ae9be698ce2db21f5b886ecac95a73429593f93713c60ed8c12c38bf0d3.png?imageMogr2/format/webp,600002498,2000293903
吉林卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226397/index.m3u8
浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/a66c836bd98ba3e41a2e9a570d4b9c50dedc6839e9de333e2e78212ad505f37e.png?imageMogr2/format/webp,600002520,2000295503
安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/f35fa04b51b1ee4984b03578b65403570868ebca03c6c01e11b097f999a58d9b.png?imageMogr2/format/webp,600002532,2000298003
深圳卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/d59fec04c902e3581c617136d02d4b9b8c4cbe64272781ddd3525e80c823edb7.png?imageMogr2/format/webp,600002481,2000292203
四川卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3276a414ae0eaa0f116f2045cd913367967d0c7c1e978e8621ac3879436c6ed7.png?imageMogr2/format/webp,600002516,2000295003
东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/3208fe6564a293c21b711333fb3edb05bb5b406cff840573c9a8d839680a1579.png?imageMogr2/format/webp,600002484,2000292503
海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8,https://resources.yangshipin.cn/assets/oms/image/202306/6e060391fde0469801fc3d84dbf204b4f8d650d251f17d7595a6964c0bb99e81.png?imageMogr2/format/webp,600002506,2000291503
宁夏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225748/index.m3u8
安多卫视,http://stream.qhbtv.com/adws/sd/live.m3u8
三沙卫视,https://pullsstv90080111.ssws.tv/live/SSTV20220729.m3u8
延边卫视,http://live.ybtvyun.com/video/s10006-44f040627ca1/index.m3u8
浙江少儿,http://hw-m-l.cztv.com/channels/lantian/channel008/1080p.m3u8
南京少儿,http://live.nbs.cn/channels/njtv/sepd/500k.m3u8
移动专区
CCTV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226231/index.m3u8
CCTV2,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226195/index.m3u8
CCTV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226397/index.m3u8
CCTV4,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226191/index.m3u8
CCTV5,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226395/index.m3u8
CCTV5+,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226221/index.m3u8
CCTV6,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226393/index.m3u8
CCTV7,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226192/index.m3u8
CCTV8,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226391/index.m3u8
CCTV9,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226197/index.m3u8
CCTV10,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226189/index.m3u8
CCTV11,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226240/index.m3u8
CCTV12,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226190/index.m3u8
CCTV13,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226233/index.m3u8
CCTV14,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226193/index.m3u8
CCTV15,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225785/index.m3u8
CCTV16,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226921/index.m3u8
CCTV17,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226198/index.m3u8
CETV1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225753/index.m3u8
CETV2,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225756/index.m3u8
CETV3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226226/index.m3u8
CETV4,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226225/index.m3u8
浙江卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8
四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8
重庆卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8
安徽卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8
天津卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226204/index.m3u8
山西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225763/index.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8
山东教育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226238/index.m3u8
东南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8
海南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8
厦门卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226219/index.m3u8
河南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8
湖北卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8
河北卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8
湖南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8
金鹰卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225744/index.m3u8
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8
哈哈炫动,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226213/index.m3u8
辽宁卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8
黑龙江卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8
吉林卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225792/index.m3u8
广西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8
江西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8
江苏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8
优漫卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225795/index.m3u8
深圳卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8
大湾区卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226218/index.m3u8
北京卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8
北京卡酷,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225743/index.m3u8
冬奥纪实,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226232/index.m3u8
嘉佳卡通,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226227/index.m3u8
云南卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225751/index.m3u8
贵州卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8
宁夏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225748/index.m3u8
甘肃卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225754/index.m3u8
西藏卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226212/index.m3u8
安多卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226228/index.m3u8
康巴卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226234/index.m3u8
新疆卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225747/index.m3u8
兵团卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226214/index.m3u8
延边卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226220/index.m3u8
内蒙古卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225786/index.m3u8
康巴卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226234/index.m3u8
电视指南,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8 电视指南,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226987/index.m3u8
风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8 风云足球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226984/index.m3u8
风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8 风云剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226950/index.m3u8
@@ -289,32 +160,58 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
兵器科技,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8 兵器科技,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226975/index.m3u8
高尔夫网球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8 高尔夫网球,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226978/index.m3u8
央视文化精品,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8 央视文化精品,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226981/index.m3u8
哒啵电竞,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226951/index.m3u8
地方频道 哒啵赛事,http://dbiptv.sn.chinamobile.com/PLTV/88888893/224/3221226954/index.m3u8
东方卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226217/index.m3u8 CHC高清电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226463/index.m3u8
湖南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226307/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226211/index.m3u8 CHC家庭影院,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226462/index.m3u8
湖北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226477/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226194/index.m3u8 CHC动作电影,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226465/index.m3u8
辽宁卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226546/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226201/index.m3u8 陕西卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225821/index.m3u8
江苏卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226200/index.m3u8 农林卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226229/index.m3u8
江西卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226344/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225764/index.m3u8 陕西一套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226357/1.m3u8
山东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226209/index.m3u8 陕西二套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226358/1.m3u8
广东卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226216/index.m3u8 陕西三套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226359/1.m3u8
广西卫视,http://live.gxrb.com.cn/tv/gxtvlive03/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225770/index.m3u8 陕西四套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226360/1.m3u8
重庆卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226409/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226202/index.m3u8 陕西五套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226361/1.m3u8
河南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226480/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225767/index.m3u8 陕西六套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226362/1.m3u8
河北卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226406/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225750/index.m3u8 陕西七套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226363/1.m3u8
贵州卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226474/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225793/index.m3u8 陕西八套,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226364/1.m3u8
北京卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221225728/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226222/index.m3u8 西安新闻综合,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226366/1.m3u8
黑龙江卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226327/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226215/index.m3u8 西安都市,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226367/1.m3u8
浙江卫视,http://hw-m-l.cztv.com/channels/lantian/channel01/1080p.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226199/index.m3u8 西安商务资讯,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226368/1.m3u8
安徽卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226391/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226203/index.m3u8 西安影视,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226369/1.m3u8
深圳卫视,http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226205/index.m3u8 西安丝路,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226370/1.m3u8
四川卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8;http://39.134.24.166/dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225768/index.m3u8 西安教育,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226371/index.m3u8
东南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226341/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225766/index.m3u8 咸阳-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226372/index.m3u8
海南卫视,http://ottrrs.hl.chinamobile.com/PLTV/88888888/224/3221226465/index.m3u8;http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225769/index.m3u8 杨凌-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226373/index.m3u8
延安-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226374/1.m3u8
延安-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226375/1.m3u8
铜川-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226379/1.m3u8
铜川-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226380/1.m3u8
宝鸡-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226383/1.m3u8
宝鸡-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226384/1.m3u8
宁强-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226390/1.m3u8
宁强-2,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226389/1.m3u8
汉中-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226331/1.m3u8
汉中-3,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221225991/index.m3u8
佛坪-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226454/1.m3u8
镇巴-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226457/1.m3u8
略阳-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226322/1.m3u8
西乡-1,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226556/index.m3u8
榆林-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226377/1.m3u8
商洛-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226378/1.m3u8
渭南-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226376/1.m3u8
安康-1,http://dbiptv.sn.chinamobile.com/PLTV/88888888/224/3221226385/1.m3u8
纪实人文,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226230/index.m3u8
山东教育卫视,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226238/index.m3u8
置业频道,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226241/index.m3u8
京视剧场,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226242/index.m3u8
家庭理财,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226244/index.m3u8
奕坦春秋,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226245/index.m3u8
发现之旅,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226235/index.m3u8
老故事,http://dbiptv.sn.chinamobile.com/PLTV/88888890/224/3221226236/index.m3u8
""".trimIndent() """.trimIndent()
val map: MutableMap<String, MutableList<TV>> = mutableMapOf() val map: MutableMap<String, MutableMap<String, TV>> = mutableMapOf()
var channel = "" var channel = ""
for (i in tvs.split("\n")) { for (i in tvs.split("\n")) {
@@ -323,38 +220,30 @@ CGTN 纪录频道,https://livedoc.cgtn.com/500d/prog_index.m3u8
} }
if (!i.contains(",")) { if (!i.contains(",")) {
channel = i.trim() channel = i.trim()
if (channel == "移动专区") {
break
}
continue continue
} }
val p = i.split(",") val p = i.split(",")
val titleMap = map[channel] ?: mutableListOf() val titleMap = map[channel] ?: mutableMapOf()
val tv = TV( val tv = titleMap[p[0]] ?: TV(
count, count,
p[0], p[0],
p[1].split(";").map { it.trim() } p[1].split(";").map { it.trim() }
) )
val logo = mappingLogo[tv.title]
if (logo != null && logo != "") {
tv.logo = logo
}
val programId = mappingEPG[tv.title]
if (programId != null && programId != "") {
tv.programId = programId
}
val video = mappingVideo[tv.title]
if (video != null) {
tv.pid = video[0]
tv.sid = video[1]
}
titleMap.add(tv)
map[channel] = titleMap
count++ count++
if (p.size > 2) {
tv.logo = p[2]
}
if (p.size > 3) {
tv.pid = p[3]
}
if (p.size > 4) {
tv.sid = p[4]
}
titleMap[p[0]] = tv
map[channel] = titleMap
} }
return map return map
@@ -1,15 +0,0 @@
package com.lizongying.mytv
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object Utils {
fun getDateFormat(format: String): String {
return SimpleDateFormat(format, Locale.CHINA).format(Date())
}
fun getDateTimestamp(): Long {
return Date().time / 1000
}
}
@@ -1,17 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class BtraceClient {
private val yspUrl = "https://btrace.yangshipin.cn/"
val yspBtraceService: YSPBtraceService by lazy {
Retrofit.Builder()
.baseUrl(yspUrl)
.addConverterFactory(GsonConverterFactory.create())
.build().create(YSPBtraceService::class.java)
}
}
@@ -0,0 +1,24 @@
package com.lizongying.mytv.api
import okhttp3.MediaType
import okhttp3.RequestBody
import okio.BufferedSink
import java.io.IOException
data class Page(
val code: Int,
val msg: String,
val data: Data,
)
data class Data(
val feedModuleList: List<Module>
)
data class Module(
val dataTvChannelList: List<TvChannelListData>
)
data class TvChannelListData(
val dataTvChannelList: List<TvChannelListData>
)
@@ -4,10 +4,9 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import com.lizongying.mytv.Encryptor import com.lizongying.mytv.Encryptor
import com.lizongying.mytv.MainActivity import com.lizongying.mytv.MainActivity
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.models.TVViewModel import com.lizongying.mytv.models.TVViewModel
import java.util.Date
import kotlin.math.floor import kotlin.math.floor
import kotlin.random.Random
class YSP(var context: Context) { class YSP(var context: Context) {
private var cnlid = "" private var cnlid = ""
@@ -65,10 +64,11 @@ class YSP(var context: Context) {
} }
fun switch(tvModel: TVViewModel): String { fun switch(tvModel: TVViewModel): String {
livepid = tvModel.pid.value!! livepid = tvModel.getTV().pid
cnlid = tvModel.sid.value!! cnlid = tvModel.getTV().sid
defn = "fhd" defn = "fhd"
// guid = getGuid()
randStr = getRand() randStr = getRand()
timeStr = getTimeStr() timeStr = getTimeStr()
@@ -83,18 +83,24 @@ class YSP(var context: Context) {
} }
private fun getTimeStr(): String { private fun getTimeStr(): String {
return getDateTimestamp().toString() return (Date().time / 1000).toString()
} }
fun generateGuid(): String { private fun generateGuid(): String {
val timestamp = (System.currentTimeMillis()).toString(36) val timestamp = (System.currentTimeMillis()).toString(36)
val randomPart = Random.nextLong().toString(36).take(11) val originalString = Math.random().toString()
return timestamp + "_" + "0".repeat(11 - randomPart.length) + randomPart val resultString = if (originalString.startsWith("0.")) {
originalString.substring(2)
} else {
originalString
}
val randomPart = resultString.toLong().toString(36)
return timestamp + "_" + randomPart
} }
fun getGuid(): String { private fun getGuid(): String {
var guid = sharedPref?.getString("guid", "") var guid = sharedPref?.getString("guid", "").toString()
if (guid == null || guid.length < 18) { if (guid == "") {
guid = generateGuid() guid = generateGuid()
with(sharedPref!!.edit()) { with(sharedPref!!.edit()) {
putString("guid", guid) putString("guid", guid)
@@ -104,7 +110,7 @@ class YSP(var context: Context) {
return guid return guid
} }
fun getRand(): String { private fun getRand(): String {
var n = "" var n = ""
val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" val e = "ABCDEFGHIJKlMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
val r = e.length val r = e.length
@@ -1,71 +0,0 @@
package com.lizongying.mytv.api
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
interface YSPBtraceService {
@FormUrlEncoded
@POST("kvcollect")
@Headers(
"content-type: application/x-www-form-urlencoded",
"referer: https://www.yangshipin.cn/",
)
fun kvcollect(
@Query("BossId") BossId: String = "2727",
@Query("c_timestamp") c_timestamp: String = "",
@Field("Pwd") Pwd: String = "1424084450",
@Field("fpid") fpid: String = "",
@Field("livepid") livepid: String = "",
@Field("prd") prd: String = "60000",
@Field("ftime") ftime: String = "",
@Field("prog") prog: String = "",
@Field("playno") playno: String = "",
@Field("guid") guid: String = "",
@Field("hh_ua") hh_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("cdn") cdn: String = "waibao",
@Field("platform") platform: String = "5910204",
@Field("errcode") errcode: String = "-",
@Field("sUrl") sUrl: String = "",
@Field("seq") seq: String = "",
@Field("login_type") login_type: String = "undefined",
@Field("open_id") open_id: String = "undefined",
@Field("openid") openid: String = "undefined",
@Field("defn") defn: String = "fhd",
@Field("durl") durl: String = "-",
@Field("sdtfrom") sdtfrom: String = "ysp_pc_01",
@Field("firstreport") firstreport: String = "0",
@Field("fplayerver") fplayerver: String = "89",
@Field("cmd") cmd: String = "263",
@Field("fact1") fact1: String = "ysp_pc_live_b",
@Field("sRef") sRef: String = "-",
@Field("viewid") viewid: String = "",
@Field("geturltime") geturltime: String = "0",
@Field("hc_openid") hc_openid: String = "undefined",
@Field("downspeed") downspeed: String = "10",
@Field("c_host") c_host: String = "www.yangshipin.cn",
@Field("c_pathname") c_pathname: String = "www.yangshipin.cn/",
@Field("c_url") c_url: String = "www.yangshipin.cn/",
@Field("c_channel") c_channel: String = "-",
@Field("c_referrer") c_referrer: String = "-",
@Field("c_ssize") c_ssize: String = "618",
@Field("c_ua") c_ua: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
@Field("c_os") c_os: String = "mac os",
@Field("c_osv") c_osv: String = "os10.15.7",
@Field("c_browser") c_browser: String = "chrome",
@Field("c_browserv") c_browserv: String = "chrome119",
@Field("c_dvendor") c_dvendor: String = "apple",
@Field("c_dmodel") c_dmodel: String = "macintosh",
@Field("c_dtype") c_dtype: String = "unkown",
@Field("c_city") c_city: String = "disabled",
@Field("c_nation") c_nation: String = "disabled",
@Field("c_province") c_province: String = "disabled",
@Field("c_guid") c_guid: String = "",
@Field("c_vuid") c_vuid: String = "-",
): Call<Void>
}
@@ -1,7 +1,6 @@
package com.lizongying.mytv.api package com.lizongying.mytv.api
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 retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Headers import retrofit2.http.Headers
@@ -16,13 +15,13 @@ interface YSPProtoService {
fun getPage( fun getPage(
): Call<pageModel.Response> ): Call<pageModel.Response>
@GET("api/yspepg/program/{livepid}/{date}") @GET("api/yspepg/program/{livepid}/{cnlid}")
@Headers( @Headers(
"yspappid: 519748109", "yspappid: 519748109",
"platform: 109", "platform: 109",
) )
fun getProgram( fun getProgram(
@Path("livepid") livepid: String, @Path("livepid") livepid: String,
@Path("date") date: String, @Path("cnlid") cnlid: String,
): Call<epgProgramModel.Response> ): Call<LiveInfo>
} }
@@ -1,61 +1,47 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.content.SharedPreferences
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
class TVListViewModel : ViewModel() { class TVListViewModel : ViewModel() {
private val tvListLiveData = MutableLiveData<MutableList<TV>>()
var maxNum = mutableListOf<Int>() private val tvModelListLiveData = MutableLiveData<MutableList<TVViewModel>>()
private var sharedPref: SharedPreferences? = null fun getListLiveData(): MutableLiveData<MutableList<TVViewModel>> {
return tvModelListLiveData
private val tvListViewModel = MutableLiveData<MutableList<TVViewModel>>()
private val _itemPosition = MutableLiveData<Int>()
val itemPosition: LiveData<Int>
get() = _itemPosition
private val _itemPositionCurrent = MutableLiveData<Int>()
val itemPositionCurrent: LiveData<Int>
get() = _itemPositionCurrent
fun getTVListViewModel(): MutableLiveData<MutableList<TVViewModel>> {
return tvListViewModel
} }
fun addTVViewModel(tvViewModel: TVViewModel) { fun addTV(tv: TV) {
val currentTVModelList = tvListViewModel.value ?: mutableListOf() val currentList = tvListLiveData.value ?: mutableListOf()
currentTVModelList.add(tvViewModel) currentList.add(tv)
tvListViewModel.value = currentTVModelList tvListLiveData.value = currentList
val currentModelList = tvModelListLiveData.value ?: mutableListOf()
currentModelList.add(TVViewModel(tv))
tvModelListLiveData.value = currentModelList
} }
fun getTVViewModel(id: Int): TVViewModel? { fun updateTV(tv: TV) {
return tvListViewModel.value?.get(id) val currentList = tvListLiveData.value ?: mutableListOf()
currentList[tv.id] = tv
tvListLiveData.value = currentList
val currentModelList = tvModelListLiveData.value ?: mutableListOf()
currentModelList[tv.id].update(tv)
tvModelListLiveData.value = currentModelList
} }
fun getTVViewModelCurrent(): TVViewModel? { fun getTV(id: Int): TV? {
return _itemPositionCurrent.value?.let { tvListViewModel.value?.get(it) } return tvListLiveData.value?.get(id)
} }
fun setItemPosition(position: Int) { fun getTVModel(id: Int): TVViewModel? {
_itemPosition.value = position return tvModelListLiveData.value?.get(id)
_itemPositionCurrent.value = position
}
fun setItemPositionCurrent(position: Int) {
_itemPositionCurrent.value = position
}
fun savePosition(position: Int) {
with(sharedPref!!.edit()) {
putInt("position", position)
apply()
}
} }
fun size(): Int { fun size(): Int {
return tvListViewModel.value!!.size return tvListLiveData.value!!.size
} }
} }
@@ -1,54 +1,64 @@
package com.lizongying.mytv.models package com.lizongying.mytv.models
import android.net.Uri
import android.util.Log
import androidx.annotation.OptIn
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 androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import com.lizongying.mytv.TV import com.lizongying.mytv.TV
import com.lizongying.mytv.Utils.getDateTimestamp
import com.lizongying.mytv.proto.Ysp.cn.yangshipin.omstv.common.proto.programModel.Program
class TVViewModel(private var tv: TV) : ViewModel() { class TVViewModel(private var tv: TV) : ViewModel() {
private var mapping = mapOf(
"CCTV4K" to "CCTV4K",
// "CCTV1" to "CCTV1 综合",
"CCTV2" to "CCTV2 财经",
"CCTV4" to "CCTV4 中文国际",
"CCTV5" to "CCTV5 体育",
"CCTV5+" to "CCTV5+ 体育赛事",
"CCTV7" to "CCTV7 国防军事",
"CCTV9" to "CCTV9 记录",
"CCTV10" to "CCTV10 科教",
"CCTV11" to "CCTV11 戏曲",
"CCTV12" to "CCTV12 社会与法",
// "CCTV13" to "CCTV13",
"CCTV14" to "CCTV14 少儿",
"CCTV15" to "CCTV15 音乐",
"CCTV16-HD" to "CCTV16 奥林匹克",
"CCTV17" to "CCTV17 农业农村",
"CGTN" to "CGTN",
"CGTN法语频道" to "CGTN 法语频道",
"CGTN俄语频道" to "CGTN 俄语频道",
"CGTN阿拉伯语频道" to "CGTN 阿拉伯语频道",
"CGTN西班牙语频道" to "CGTN 西班牙语频道",
// "CGTN外语纪录频道" to "CGTN外语纪录频道",
private var rowPosition: Int = 0 "东方卫视" to "东方卫视",
private var itemPosition: Int = 0 "湖南卫视" to "湖南卫视",
"湖北卫视" to "湖北卫视",
"辽宁卫视" to "辽宁卫视",
"江苏卫视" to "江苏卫视",
"江西卫视" to "江西卫视",
"山东卫视" to "山东卫视",
"广东卫视" to "广东卫视",
"广西卫视" to "广西卫视",
"重庆卫视" to "重庆卫视",
"河南卫视" to "河南卫视",
"河北卫视" to "河北卫视",
"贵州卫视" to "贵州卫视",
"北京卫视" to "北京卫视",
"黑龙江卫视" to "黑龙江卫视",
"浙江卫视" to "浙江卫视",
"安徽卫视" to "安徽卫视",
"深圳卫视" to "深圳卫视",
"四川卫视" to "四川卫视",
"福建东南卫视" to "东南卫视",
"海南卫视" to "海南卫视",
).entries.associate { (key, value) -> value to key }
var programUpdateTime: Long = 0 private var updateByYSP = false
private val _programId = MutableLiveData<String>()
val programId: LiveData<String>
get() = _programId
private var _program = MutableLiveData<MutableList<Program>>()
val program: LiveData<MutableList<Program>>
get() = _program
private val _id = MutableLiveData<Int>()
val id: LiveData<Int>
get() = _id
private val _title = MutableLiveData<String>()
val title: LiveData<String>
get() = _title
private val _videoUrl = MutableLiveData<List<String>>() private val _videoUrl = MutableLiveData<List<String>>()
val videoUrl: LiveData<List<String>> val videoUrl: LiveData<List<String>>
get() = _videoUrl get() = _videoUrl
private val _videoIndex = MutableLiveData<Int>()
val videoIndex: LiveData<Int>
get() = _videoIndex
private val _logo = MutableLiveData<String>()
val logo: LiveData<String>
get() = _logo
private val _pid = MutableLiveData<String>() private val _pid = MutableLiveData<String>()
val pid: LiveData<String> val pid: LiveData<String>
get() = _pid get() = _pid
@@ -57,84 +67,37 @@ class TVViewModel(private var tv: TV) : ViewModel() {
val sid: LiveData<String> val sid: LiveData<String>
get() = _sid get() = _sid
private val _change = MutableLiveData<Boolean>() private val _backgroundImage = MutableLiveData<String>()
val change: LiveData<Boolean> val backgroundImage: LiveData<String>
get() = _change get() = _backgroundImage
private val _ready = MutableLiveData<Boolean>() fun getBackgroundImage(): String {
val ready: LiveData<Boolean> return tv.logo ?: ""
get() = _ready
private var mMinimumLoadableRetryCount = 3
var seq = 0
fun addVideoUrl(url: String) {
if (_videoUrl.value?.isNotEmpty() == true) {
if (_videoUrl.value!!.last().contains("cctv.cn")) {
tv.videoUrl = tv.videoUrl.subList(0, tv.videoUrl.lastIndex) + listOf(url)
} else {
tv.videoUrl = tv.videoUrl + listOf(url)
}
} else {
tv.videoUrl = tv.videoUrl + listOf(url)
}
tv.videoIndex = tv.videoUrl.lastIndex
_videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex
} }
fun firstSource() { fun updateBackgroundImage(url: String) {
if (_videoUrl.value!!.isNotEmpty()) { _backgroundImage.value = url
setVideoIndex(0)
allReady()
} else {
Log.e(TAG, "no first")
}
} }
fun changed() { private fun updateVideoUrl(url: String) {
_change.value = true tv.videoUrl = listOf(url)
tv.videoIndex = 0
_videoUrl.value = listOf(url)
} }
fun allReady() { fun updateVideoUrlByYSP(url: String) {
_ready.value = true updateByYSP = true
} updateVideoUrl(url)
fun setVideoIndex(videoIndex: Int) {
_videoIndex.value = videoIndex
}
fun setLogo(url: String) {
_logo.value = url
} }
init { init {
_id.value = tv.id
_title.value = tv.title
_videoUrl.value = tv.videoUrl _videoUrl.value = tv.videoUrl
_videoIndex.value = tv.videoIndex
_logo.value = tv.logo
_programId.value = tv.programId
_pid.value = tv.pid _pid.value = tv.pid
_sid.value = tv.sid _sid.value = tv.sid
_program.value = mutableListOf()
} }
fun getRowPosition(): Int { fun updateByYSP(): Boolean {
return rowPosition return updateByYSP
}
fun getItemPosition(): Int {
return itemPosition
}
fun setRowPosition(position: Int) {
rowPosition = position
}
fun setItemPosition(position: Int) {
itemPosition = position
} }
fun update(t: TV) { fun update(t: TV) {
@@ -145,61 +108,10 @@ class TVViewModel(private var tv: TV) : ViewModel() {
return tv return tv
} }
fun getProgramOne(): Program? { fun ysp(): String? {
val programNew = if (tv.pid == "") {
(_program.value?.filter { it.et > getDateTimestamp() })?.toMutableList()
if (programNew != null && _program.value != programNew) {
_program.value = programNew
}
if (_program.value!!.isEmpty()) {
return null return null
} }
return _program.value?.first() return mapping[tv.title]
}
fun addProgram(p: MutableList<Program>) {
val timestamp = getDateTimestamp()
// after now & not empty & different
val p1 = (p.filter { it.et > timestamp }).toMutableList()
if (p1.isEmpty() || _program.value == p1) {
return
}
if (_program.value!!.isEmpty()) {
_program.value = p1
} else {
_program.value =
((_program.value?.filter { it.et > timestamp && it.st < p1.first().st })?.plus(
p1
))?.toMutableList()
}
}
private var mHeaders: Map<String, String>? = mapOf()
fun setHeaders(headers: Map<String, String>) {
mHeaders = headers
}
fun setMinimumLoadableRetryCount(minimumLoadableRetryCount: Int) {
mMinimumLoadableRetryCount = minimumLoadableRetryCount
}
@OptIn(UnstableApi::class)
fun buildSource(videoUrl: String, mHeaders: Map<String, String>?): HlsMediaSource {
val httpDataSource = DefaultHttpDataSource.Factory()
mHeaders?.let { httpDataSource.setDefaultRequestProperties(it) }
return HlsMediaSource.Factory(httpDataSource).createMediaSource(
MediaItem.fromUri(
Uri.parse(videoUrl)
)
)
}
companion object {
private const val TAG = "TVViewModel"
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

@@ -1,4 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners android:radius="4dp" /> <!-- Adjust the radius as needed -->
</shape>
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- res/layout/custom_browse_fragment.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/right_content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/browse_headers" />
<fragment
android:id="@+id/browse_headers"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true" />
</RelativeLayout>
+9 -44
View File
@@ -5,50 +5,15 @@
android:layout_height="80dp" android:layout_height="80dp"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="20dp" android:layout_marginBottom="20dp"
android:background="@drawable/rounded_background"> android:background="@color/fastlane_background"
>
<ImageView <TextView
android:id="@+id/info_logo" android:id="@+id/textView"
android:layout_width="100dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_gravity="center_horizontal|top"
android:contentDescription="@string/logo" android:layout_marginTop="10dp"
android:padding="10dp" /> android:textSize="40sp"
/>
<LinearLayout
android:layout_width="216dp"
android:layout_height="match_parent"
android:layout_marginStart="100dp"
android:background="#FF263238"
android:gravity="start|center_vertical"
android:orientation="vertical"
android:padding="7dp">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="0dp"
android:gravity="start"
android:textColor="#FFEEEEEE"
android:textSize="22sp" />
<TextView
android:id="@+id/info_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="16sp" />
</LinearLayout>
<View
android:layout_width="8dp"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:background="@drawable/rounded_background2" />
</FrameLayout> </FrameLayout>
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/player_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:keepScreenOn="true">
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:resize_mode="fill"
app:use_controller="false" />
</FrameLayout>
-202
View File
@@ -1,202 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Must be kept in sync with AspectRatioFrameLayout -->
<attr name="resize_mode" format="enum">
<enum name="fit" value="0"/>
<enum name="fixed_width" value="1"/>
<enum name="fixed_height" value="2"/>
<enum name="fill" value="3"/>
<enum name="zoom" value="4"/>
</attr>
<!-- Must be kept in sync with LegacyPlayerView and PlayerView -->
<attr name="surface_type" format="enum">
<enum name="none" value="0"/>
<enum name="surface_view" value="1"/>
<enum name="texture_view" value="2"/>
<enum name="spherical_gl_surface_view" value="3"/>
<enum name="video_decoder_gl_surface_view" value="4"/>
</attr>
<!-- Must be kept in sync with RepeatModeUtil -->
<attr name="repeat_toggle_modes">
<flag name="none" value="0"/>
<flag name="one" value="1"/>
<flag name="all" value="2"/>
</attr>
<!-- LegacyPlayerView and PlayerView attributes -->
<attr name="use_artwork" format="boolean"/>
<attr name="artwork_display_mode" format="enum">
<enum name="off" value="0"/>
<enum name="fit" value="1"/>
<enum name="fill" value="2"/>
</attr>
<attr name="shutter_background_color" format="color"/>
<attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/>
<attr name="hide_on_touch" format="boolean"/>
<attr name="hide_during_ads" format="boolean"/>
<attr name="auto_show" format="boolean"/>
<attr name="show_buffering" format="enum">
<enum name="never" value="0"/>
<enum name="when_playing" value="1"/>
<enum name="always" value="2"/>
</attr>
<attr name="keep_content_on_player_reset" format="boolean"/>
<attr name="player_layout_id" format="reference"/>
<!-- LegacyPlayerControlView and PlayerControlView attributes -->
<attr name="show_timeout" format="integer"/>
<attr name="show_rewind_button" format="boolean"/>
<attr name="show_fastforward_button" format="boolean"/>
<attr name="show_previous_button" format="boolean"/>
<attr name="show_next_button" format="boolean"/>
<attr name="show_shuffle_button" format="boolean"/>
<attr name="show_subtitle_button" format="boolean"/>
<attr name="show_vr_button" format="boolean"/>
<attr name="time_bar_min_update_interval" format="integer"/>
<attr name="controller_layout_id" format="reference"/>
<attr name="animation_enabled" format="boolean"/>
<!-- Needed for https://github.com/google/ExoPlayer/issues/7898 -->
<attr name="backgroundTint" format="color"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height" format="dimension"/>
<attr name="bar_gravity" format="enum">
<enum name="center" value="0"/>
<enum name="bottom" value="1"/>
</attr>
<attr name="touch_target_height" format="dimension"/>
<attr name="ad_marker_width" format="dimension"/>
<attr name="scrubber_enabled_size" format="dimension"/>
<attr name="scrubber_disabled_size" format="dimension"/>
<attr name="scrubber_dragged_size" format="dimension"/>
<attr name="scrubber_drawable" format="reference"/>
<attr name="played_color" format="color"/>
<attr name="scrubber_color" format="color"/>
<attr name="buffered_color" format="color"/>
<attr name="unplayed_color" format="color"/>
<attr name="ad_marker_color" format="color"/>
<attr name="played_ad_marker_color" format="color"/>
<declare-styleable name="PlayerView">
<attr name="use_artwork"/>
<attr name="artwork_display_mode"/>
<attr name="shutter_background_color"/>
<attr name="default_artwork"/>
<attr name="use_controller"/>
<attr name="hide_on_touch"/>
<attr name="hide_during_ads"/>
<attr name="auto_show"/>
<attr name="show_buffering"/>
<attr name="keep_content_on_player_reset"/>
<attr name="player_layout_id"/>
<attr name="surface_type"/>
<!-- AspectRatioFrameLayout attributes -->
<attr name="resize_mode"/>
<!-- PlayerControlView attributes -->
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_shuffle_button"/>
<attr name="show_subtitle_button"/>
<attr name="show_vr_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<attr name="animation_enabled"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="AspectRatioFrameLayout">
<attr name="resize_mode"/>
</declare-styleable>
<declare-styleable name="LegacyPlayerControlView">
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_rewind_button"/>
<attr name="show_fastforward_button"/>
<attr name="show_previous_button"/>
<attr name="show_next_button"/>
<attr name="show_shuffle_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="PlayerControlView">
<attr name="show_timeout"/>
<attr name="repeat_toggle_modes"/>
<attr name="show_rewind_button"/>
<attr name="show_fastforward_button"/>
<attr name="show_previous_button"/>
<attr name="show_next_button"/>
<attr name="show_shuffle_button"/>
<attr name="show_subtitle_button"/>
<attr name="show_vr_button"/>
<attr name="time_bar_min_update_interval"/>
<attr name="controller_layout_id"/>
<attr name="animation_enabled"/>
<!-- DefaultTimeBar attributes -->
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
<declare-styleable name="DefaultTimeBar">
<attr name="bar_height"/>
<attr name="bar_gravity"/>
<attr name="touch_target_height"/>
<attr name="ad_marker_width"/>
<attr name="scrubber_enabled_size"/>
<attr name="scrubber_disabled_size"/>
<attr name="scrubber_dragged_size"/>
<attr name="scrubber_drawable"/>
<attr name="played_color"/>
<attr name="scrubber_color"/>
<attr name="buffered_color" />
<attr name="unplayed_color"/>
<attr name="ad_marker_color"/>
<attr name="played_ad_marker_color"/>
</declare-styleable>
</resources>
-2
View File
@@ -1,5 +1,3 @@
<resources> <resources>
<color name="fastlane_background">#30000000</color> <color name="fastlane_background">#30000000</color>
<color name="black">#000</color>
<color name="white">#FFF</color>
</resources> </resources>
+1 -2
View File
@@ -1,4 +1,3 @@
<resources> <resources>
<string name="app_name">我的电视</string> <string name="app_name">My TV</string>
<string name="logo">logo</string>
</resources> </resources>
+1 -2
View File
@@ -2,8 +2,7 @@
<style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle"> <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<!-- <item name="infoVisibility">activated</item>--> <item name="lbImageCardViewType">Title</item>
<item name="lbImageCardViewType">Title|Content</item>
</style> </style>
<style name="CustomImageCardTheme" parent="Theme.Leanback"> <style name="CustomImageCardTheme" parent="Theme.Leanback">
+1 -5
View File
@@ -1,8 +1,4 @@
<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="browsePaddingEnd">11dp</item>
<item name="browseRowsMarginTop">54dp</item>
</style>
</resources> </resources>
+1 -1
View File
@@ -21,5 +21,5 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
#android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false android.nonFinalResIds=false
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB