Compare commits

...
4 Commits
Author SHA1 Message Date
Li ZongYing 52c7887951 keep screen on 2023-12-16 14:26:31 +08:00
Li ZongYing d8fffbb402 optimize ysp 2023-12-16 08:19:02 +08:00
Li ZongYing cc2dc25e35 optimize ysp 2023-12-15 18:12:11 +08:00
Li ZongYing aef165504f change to exoplayer 2023-12-15 15:41:59 +08:00
19 changed files with 704 additions and 37 deletions
+8 -8
View File
@@ -15,17 +15,17 @@ if (IS_SO_BUILD)
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.
add_library( # Specifies the name of the library.
native-lib
native
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.c)
src/main/cpp/native.c)
# 设置编译输出路径
set_target_properties(
native-lib
native
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY
${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}
@@ -36,18 +36,18 @@ else ()
SHARED
src/main/cpp/nothing.c)
add_library(native-lib
add_library(native
SHARED
IMPORTED)
set_target_properties( # Specifies the target library.
native-lib
native
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libnative-lib.so)
${CMAKE_SOURCE_DIR}/src/main/cpp/${ANDROID_ABI}/libnative.so)
endif ()
add_library(libssl
@@ -87,7 +87,7 @@ find_library( # Defines the name of the path variable that stores the
if (IS_SO_BUILD)
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
native-lib
native
libssl
libcrypto
${log-lib})
@@ -96,6 +96,6 @@ else ()
nothing
libssl
libcrypto
native-lib
native
${log-lib})
endif ()
+9 -1
View File
@@ -90,13 +90,21 @@ static def VersionName() {
}
dependencies {
def media3_version = "1.1.1"
// For media playback using ExoPlayer
implementation "androidx.media3:media3-exoplayer:$media3_version"
// For HLS playback support with ExoPlayer
implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
implementation 'com.google.protobuf:protobuf-kotlin:3.25.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-protobuf:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation 'androidx.core:core-ktx:1.11.0-beta02'
implementation 'androidx.leanback:leanback:1.0.0'
implementation 'androidx.leanback:leanback:1.2.0-alpha02'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC")
+1
View File
@@ -14,6 +14,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.MyTV">
<activity
android:keepScreenOn="true"
android:name=".MainActivity"
android:banner="@drawable/tv"
android:exported="true"
Binary file not shown.
Binary file not shown.
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;
}
@@ -11,7 +11,7 @@ class Encryptor {
companion object {
init {
System.loadLibrary("native-lib")
System.loadLibrary("native")
}
}
}
@@ -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)
}
}
@@ -11,6 +11,7 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.KeyEvent
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
@@ -32,6 +33,8 @@ class MainActivity : FragmentActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.main_browse_fragment, playbackFragment)
@@ -36,6 +36,16 @@ class MainFragment : BrowseSupportFragment() {
private var sharedPref: SharedPreferences? = null
// override fun onCreateView(
// inflater: LayoutInflater,
// container: ViewGroup?,
// savedInstanceState: Bundle?
// ): View? {
// super.onCreate()
// // 使用自定义的布局文件
// return inflater.inflate(R.layout.custom_browse_fragment, container, false)
// }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
@@ -47,12 +57,12 @@ class MainFragment : BrowseSupportFragment() {
setupEventListeners()
view?.post {
request?.fetchPage()
// request?.fetchPage()
}
tvListViewModel.getListLiveData().value?.forEach { tvViewModel ->
tvViewModel.videoUrl.observe(viewLifecycleOwner) { _ ->
Log.i(TAG, "tv ${tvViewModel.getTV()}")
// Log.i(TAG, "tv ${tvViewModel.getTV()}")
if (tvViewModel.updateByYSP()) {
val tv = tvViewModel.getTV()
if (tv.id == itemPosition) {
@@ -66,7 +76,6 @@ class MainFragment : BrowseSupportFragment() {
private fun setupUIElements() {
brandColor = ContextCompat.getColor(context!!, R.color.fastlane_background)
// headersState = HEADERS_DISABLED
}
private fun updateRows(tv: TV) {
@@ -87,7 +96,6 @@ class MainFragment : BrowseSupportFragment() {
rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
// val cardPresenter = CardPresenter(lifecycleScope, viewLifecycleOwner)
val cardPresenter = CardPresenter(lifecycleScope)
var idx: Long = 0
@@ -112,17 +120,29 @@ class MainFragment : BrowseSupportFragment() {
adapter = rowsAdapter
itemPosition = sharedPref?.getInt("position", 0)!!
if (itemPosition >= tvListViewModel.size()) {
itemPosition = 0
savePosition(0)
}
val tv = list2[itemPosition].item
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(list2[itemPosition].item)
// (activity as? MainActivity)?.switchInfoFragment(list2[itemPosition].item)
(activity as? MainActivity)?.play(tv)
// (activity as? MainActivity)?.switchInfoFragment(tv)
}
Toast.makeText(
activity,
tv.title,
Toast.LENGTH_SHORT
).show()
(activity as? MainActivity)?.switchMainFragment()
}
@@ -5,12 +5,13 @@ 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: MediaPlayerAdapter?,
playerAdapter: PlayerAdapter?,
) :
PlaybackTransportControlGlue<MediaPlayerAdapter>(context, playerAdapter) {
PlaybackTransportControlGlue<PlayerAdapter>(context, playerAdapter) {
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
if (event!!.action == KeyEvent.ACTION_DOWN) {
@@ -5,21 +5,21 @@ import android.os.Bundle
import android.util.Log
import androidx.leanback.app.VideoSupportFragment
import androidx.leanback.app.VideoSupportFragmentGlueHost
import androidx.leanback.media.MediaPlayerAdapter
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<MediaPlayerAdapter>
private var playerAdapter: MediaPlayerAdapter? = null
private lateinit var mTransportControlGlue: PlaybackTransportControlGlue<PlayerAdapter>
private var playerAdapter: ExoPlayerAdapter? = null
private var lastVideoUrl: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playerAdapter = MediaPlayerAdapter(context)
playerAdapter = ExoPlayerAdapter(context)
playerAdapter?.setRepeatAction(PlaybackControlsRow.RepeatAction.INDEX_NONE)
view?.isFocusable = false
@@ -60,15 +60,6 @@ class PlaybackFragment : VideoSupportFragment() {
hideControlsOverlay(false)
}
override fun onDestroy() {
if (playerAdapter?.mediaPlayer != null) {
playerAdapter?.release()
Log.d(TAG, "playerAdapter released")
}
super.onDestroy()
}
companion object {
private const val TAG = "PlaybackVideoFragment"
}
@@ -1,6 +1,7 @@
package com.lizongying.mytv
import android.content.Context
import android.util.Base64
import android.util.Log
import com.lizongying.mytv.api.ApiClient
import com.lizongying.mytv.api.LiveInfo
@@ -14,6 +15,9 @@ import com.lizongying.mytv.proto.Ysp.cn.yangshipin.oms.common.proto.pageModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
class Request(var context: Context) {
@@ -85,13 +89,36 @@ class Request(var context: Context) {
override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
if (response.isSuccessful) {
val liveInfo = response.body()
Log.i(TAG, "liveInfo $liveInfo")
if (liveInfo?.data?.playurl != null) {
tvModel.updateVideoUrlByYSP(liveInfo.data.playurl)
val chanll = liveInfo.data.chanll
val decodedBytes = Base64.decode(
chanll.substring(9, chanll.length - 3),
Base64.DEFAULT
)
val decodedString = String(decodedBytes)
val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
val matchResult = regex.find(decodedString)
var keyBytes = byteArrayOf()
var ivBytes = byteArrayOf()
if (matchResult != null) {
val (key, iv) = matchResult.destructured
keyBytes = Base64.decode(key, Base64.DEFAULT)
ivBytes = Base64.decode(iv, Base64.DEFAULT)
}
tvModel.updateVideoUrlByYSP(
liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0),
ivBytes
).uppercase()
)
}
}
}
override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
Log.i(TAG, "get data error")
}
})
}
@@ -152,6 +179,22 @@ class Request(var context: Context) {
})
}
private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String {
var plaintext =
"""{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,AppleM1Pro,OpenGL4.1)"}"""
return try {
val keySpec = SecretKeySpec(key, "DESede")
val ivSpec = IvParameterSpec(iv)
val cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
val encryptedBytes = cipher.doFinal(plaintext.toByteArray())
return encryptedBytes.let { it -> it.joinToString("") { "%02x".format(it) } }
} catch (e: Exception) {
e.printStackTrace()
""
}
}
companion object {
private const val TAG = "Request"
}
@@ -13,7 +13,8 @@ data class LiveInfo(
)
data class LiveInfoData(
val playurl: String
val chanll: String,
val playurl: String,
)
data class LiveInfoRequest(
@@ -68,7 +68,7 @@ class YSP(var context: Context) {
cnlid = tvModel.getTV().sid
defn = "fhd"
guid = getGuid()
// guid = getGuid()
randStr = getRand()
timeStr = getTimeStr()
@@ -80,7 +80,6 @@ class YSP(var context: Context) {
encryptor!!.encrypt(cnlid, timeStr, appVer, guid, platform)
signature = getSignature()
return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}"""
}
private fun getTimeStr(): String {
@@ -100,7 +99,7 @@ class YSP(var context: Context) {
}
private fun getGuid(): String {
// var guid = sharedPref?.getString("guid", "").toString()
var guid = sharedPref?.getString("guid", "").toString()
if (guid == "") {
guid = generateGuid()
with(sharedPref!!.edit()) {
@@ -1,6 +1,5 @@
package com.lizongying.mytv.models
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.lizongying.mytv.TV
@@ -41,4 +40,8 @@ class TVListViewModel : ViewModel() {
fun getTVModel(id: Int): TVViewModel? {
return tvModelListLiveData.value?.get(id)
}
fun size(): Int {
return tvListLiveData.value!!.size
}
}
@@ -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>