diff --git a/README.md b/README.md index a5be109..e8764c4 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ adb install my-tv.apk * 获取系统时间 * 选中的图标比例能否相差更大 * 自动重连 +* 节目增加预告 ## 赞赏 diff --git a/app/src/main/cpp/arm64-v8a/libnative.so b/app/src/main/cpp/arm64-v8a/libnative.so index aff771d..9901230 100755 Binary files a/app/src/main/cpp/arm64-v8a/libnative.so and b/app/src/main/cpp/arm64-v8a/libnative.so differ diff --git a/app/src/main/cpp/armeabi-v7a/libnative.so b/app/src/main/cpp/armeabi-v7a/libnative.so index 07a9fec..ece0596 100755 Binary files a/app/src/main/cpp/armeabi-v7a/libnative.so and b/app/src/main/cpp/armeabi-v7a/libnative.so differ diff --git a/app/src/main/java/com/lizongying/mytv/Encryptor.kt b/app/src/main/java/com/lizongying/mytv/Encryptor.kt index 525a651..3d3fe99 100644 --- a/app/src/main/java/com/lizongying/mytv/Encryptor.kt +++ b/app/src/main/java/com/lizongying/mytv/Encryptor.kt @@ -9,6 +9,8 @@ class Encryptor { external fun hash(data: ByteArray): ByteArray? + external fun hash2(data: ByteArray): ByteArray? + companion object { init { System.loadLibrary("native") diff --git a/app/src/main/java/com/lizongying/mytv/Request.kt b/app/src/main/java/com/lizongying/mytv/Request.kt index 7e2b522..cd652ef 100644 --- a/app/src/main/java/com/lizongying/mytv/Request.kt +++ b/app/src/main/java/com/lizongying/mytv/Request.kt @@ -7,6 +7,8 @@ import android.util.Base64 import android.util.Log import com.lizongying.mytv.Utils.getDateFormat import com.lizongying.mytv.api.ApiClient +import com.lizongying.mytv.api.Auth +import com.lizongying.mytv.api.AuthRequest import com.lizongying.mytv.api.Info import com.lizongying.mytv.api.LiveInfo import com.lizongying.mytv.api.LiveInfoRequest @@ -52,6 +54,84 @@ class Request { } var call: Call? = null + var callAuth: Call? = null + + private fun fetchAuth(tvModel: TVViewModel, cookie: String) { + callAuth?.cancel() + + val title = tvModel.title.value + + val data = ysp?.getAuthData(tvModel) + val request = data?.let { AuthRequest(it) } + callAuth = request?.let { yspApiService.getAuth("guid=${ysp?.getGuid()}; $cookie", it) } + + callAuth?.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val liveInfo = response.body() + + if (liveInfo?.data?.token != null) { + Log.i(TAG, "token ${liveInfo.data.token}") + ysp?.token = liveInfo.data.token + fetchVideo(tvModel, cookie) + } else { + Log.e(TAG, "$title token error") + if (tvModel.retryTimes < tvModel.retryMaxTimes) { + tvModel.retryTimes++ + if (tvModel.getTV().needToken) { + if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) { + if (!tvModel.getTV().mustToken) { + fetchAuth(tvModel, cookie) + } + } else { + token = "" + fetchAuth(tvModel) + } + } else { + fetchAuth(tvModel, cookie) + } + } + } + } else { + Log.e(TAG, "$title auth status error") + if (tvModel.retryTimes < tvModel.retryMaxTimes) { + tvModel.retryTimes++ + if (tvModel.getTV().needToken) { + if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) { + if (!tvModel.getTV().mustToken) { + fetchAuth(tvModel, cookie) + } + } else { + token = "" + fetchAuth(tvModel) + } + } else { + fetchAuth(tvModel, cookie) + } + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "$title auth request error $t") + if (tvModel.retryTimes < tvModel.retryMaxTimes) { + tvModel.retryTimes++ + if (tvModel.getTV().needToken) { + if (tvModel.tokenRetryTimes == tvModel.tokenRetryMaxTimes) { + if (!tvModel.getTV().mustToken) { + fetchVideo(tvModel, cookie) + } + } else { + token = "" + fetchAuth(tvModel) + } + } else { + fetchAuth(tvModel, cookie) + } + } + } + }) + } fun fetchVideo(tvModel: TVViewModel, cookie: String) { call?.cancel() @@ -64,7 +144,7 @@ class Request { tvModel.seq = 0 val data = ysp?.switch(tvModel) val request = data?.let { LiveInfoRequest(it) } - call = request?.let { yspApiService.getLiveInfo("guid=${ysp?.getGuid()}; $cookie", it) } + call = request?.let { yspApiService.getLiveInfo("guid=${ysp?.getGuid()}; $cookie", ysp!!.token, it) } call?.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { @@ -175,6 +255,53 @@ class Request { }) } + fun fetchAuth(tvModel: TVViewModel) { + if (token == "") { + yspTokenService.getInfo() + .enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + token = response.body()?.data?.token!! + Log.i(TAG, "info success $token") + val cookie = + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" + fetchAuth(tvModel, cookie) + } else { + Log.e(TAG, "info status error") + if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { + tvModel.tokenRetryTimes++ + fetchAuth(tvModel) + } else { + if (!tvModel.getTV().mustToken) { + val cookie = + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" + fetchAuth(tvModel, cookie) + } + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.e(TAG, "info request error $t") + if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) { + tvModel.tokenRetryTimes++ + fetchVideo(tvModel) + } else { + if (!tvModel.getTV().mustToken) { + val cookie = + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" + fetchAuth(tvModel, cookie) + } + } + } + }) + } else { + val cookie = + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" + fetchAuth(tvModel, cookie) + } + } + fun fetchVideo(tvModel: TVViewModel) { if (token == "") { yspTokenService.getInfo() @@ -184,7 +311,7 @@ class Request { token = response.body()?.data?.token!! Log.i(TAG, "info success $token") val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" fetchVideo(tvModel, cookie) } else { Log.e(TAG, "info status error") @@ -194,7 +321,7 @@ class Request { } else { if (!tvModel.getTV().mustToken) { val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109" + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" fetchVideo(tvModel, cookie) } } @@ -209,7 +336,7 @@ class Request { } else { if (!tvModel.getTV().mustToken) { val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109" + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109" fetchVideo(tvModel, cookie) } } @@ -217,18 +344,20 @@ class Request { }) } else { val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=vu0-8lgGV2LW9QjDeuBFsX8yMnzs37Q3_HZF6XyVDpGR_I; vusession=$token" fetchVideo(tvModel, cookie) } } fun fetchData(tvModel: TVViewModel) { if (tvModel.getTV().needToken) { - fetchVideo(tvModel) +// fetchVideo(tvModel) + fetchAuth(tvModel) } else { val cookie = - "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; yspappid=519748109" - fetchVideo(tvModel, cookie) + "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205" +// fetchVideo(tvModel, cookie) + fetchAuth(tvModel, cookie) } } diff --git a/app/src/main/java/com/lizongying/mytv/UpdateManager.kt b/app/src/main/java/com/lizongying/mytv/UpdateManager.kt index a18b538..ac1b852 100644 --- a/app/src/main/java/com/lizongying/mytv/UpdateManager.kt +++ b/app/src/main/java/com/lizongying/mytv/UpdateManager.kt @@ -6,9 +6,12 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.database.Cursor import android.net.Uri import android.os.Build import android.os.Environment +import android.os.Handler +import android.os.Looper import android.util.Log import android.widget.Toast import com.lizongying.mytv.api.Release @@ -66,6 +69,7 @@ class UpdateManager( val downloadManager = context!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val request = Request(Uri.parse(release.data.downloadUrl)) + Log.i(TAG, "url ${Uri.parse(release.data.downloadUrl)}") request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, apkFileName) request.setTitle("New Version Download") request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) @@ -87,6 +91,48 @@ class UpdateManager( IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ) } + + getDownloadProgress(context!!, downloadReference) { progress -> + println("Download progress: $progress%") + } + } + + private fun getDownloadProgress(context: Context, downloadId: Long, progressListener: (Int) -> Unit) { + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val handler = Handler(Looper.getMainLooper()) + val intervalMillis: Long = 1000 + + handler.post(object : Runnable { + override fun run() { + Log.i(TAG, "search") + val query = DownloadManager.Query().setFilterById(downloadId) + val cursor: Cursor = downloadManager.query(query) + cursor.use { + if (it.moveToFirst()) { + val bytesDownloadedIndex = + it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + val bytesTotalIndex = + it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) + + // 检查列名是否存在 + if (bytesDownloadedIndex != -1 && bytesTotalIndex != -1) { + val bytesDownloaded = it.getInt(bytesDownloadedIndex) + val bytesTotal = it.getInt(bytesTotalIndex) + + if (bytesTotal != -1) { + val progress = (bytesDownloaded * 100L / bytesTotal).toInt() + progressListener(progress) + if (progress == 100) { + return + } + } + } + } + } + +// handler.postDelayed(this, intervalMillis) + } + }) } private class DownloadReceiver( diff --git a/app/src/main/java/com/lizongying/mytv/api/ApiClient.kt b/app/src/main/java/com/lizongying/mytv/api/ApiClient.kt index 25ce453..556f6b3 100644 --- a/app/src/main/java/com/lizongying/mytv/api/ApiClient.kt +++ b/app/src/main/java/com/lizongying/mytv/api/ApiClient.kt @@ -5,6 +5,8 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.protobuf.ProtoConverterFactory +import java.net.InetSocketAddress +import java.net.Proxy import javax.net.ssl.SSLContext import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager diff --git a/app/src/main/java/com/lizongying/mytv/api/Auth.kt b/app/src/main/java/com/lizongying/mytv/api/Auth.kt new file mode 100644 index 0000000..095516c --- /dev/null +++ b/app/src/main/java/com/lizongying/mytv/api/Auth.kt @@ -0,0 +1,37 @@ +package com.lizongying.mytv.api + +import android.util.Log +import okhttp3.MediaType +import okhttp3.RequestBody +import okio.BufferedSink +import java.io.IOException + +data class Auth( + val code: Int, + val msg: String, + val data: AuthData, +) + +data class AuthData( + val token: String, +) + +data class AuthRequest( + var data: String, +) : RequestBody() { + override fun contentType(): MediaType? { + return MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8") + } + + override fun writeTo(sink: BufferedSink) { + try { + sink.writeUtf8(data) + } catch (e: IOException) { + Log.e(TAG, "$e") + } + } + + companion object { + private const val TAG = "AuthRequest" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lizongying/mytv/api/YSP.kt b/app/src/main/java/com/lizongying/mytv/api/YSP.kt index 413db75..23349cc 100644 --- a/app/src/main/java/com/lizongying/mytv/api/YSP.kt +++ b/app/src/main/java/com/lizongying/mytv/api/YSP.kt @@ -50,6 +50,9 @@ class YSP(var context: Context) { private var signature = "" + private var appid = "ysp_pc" + var token = "" + private var encryptor: Encryptor? = null private lateinit var sharedPref: SharedPreferences @@ -83,6 +86,19 @@ class YSP(var context: Context) { return """{"cnlid":"$cnlid","livepid":"$livepid","stream":"$stream","guid":"$guid","cKey":"$cKey","adjust":$adjust,"sphttps":"$sphttps","platform":"$platform","cmd":"$cmd","encryptVer":"$encryptVer","dtype":"$dtype","devid":"$devid","otype":"$otype","appVer":"$appVer","app_version":"$appVersion","rand_str":"$randStr","channel":"$channel","defn":"$defn","signature":"$signature"}""" } + fun getAuthData(tvModel: TVViewModel): String { + livepid = tvModel.pid.value!! + + randStr = getRand() + + if (tvModel.retryTimes > 0) { + guid = newGuid() + } + + signature = getAuthSignature() + return """pid=$livepid&guid=$guid&appid=$appid&rand_str=$randStr&signature=$signature""" + } + private fun getTimeStr(): String { return getDateTimestamp().toString() } @@ -131,6 +147,13 @@ class YSP(var context: Context) { return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } } } + private fun getAuthSignature(): String { + val e = + "appid=${appid}&guid=${guid}&pid=${livepid}&rand_str=${randStr}".toByteArray() + val hashedData = encryptor?.hash2(e) ?: return "" + return hashedData.let { it -> it.joinToString("") { "%02x".format(it) } } + } + companion object { private const val TAG = "YSP" } diff --git a/app/src/main/java/com/lizongying/mytv/api/YSPApiService.kt b/app/src/main/java/com/lizongying/mytv/api/YSPApiService.kt index b121d07..e9bbee2 100644 --- a/app/src/main/java/com/lizongying/mytv/api/YSPApiService.kt +++ b/app/src/main/java/com/lizongying/mytv/api/YSPApiService.kt @@ -13,9 +13,23 @@ interface YSPApiService { "content-type: application/json;charset=UTF-8", "referer: https://www.yangshipin.cn/", "yspappid: 519748109", + "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ) fun getLiveInfo( @Header("cookie") cookie: String, + @Header("Yspplayertoken") token: String, @Body request: LiveInfoRequest, ): Call + + @POST("v1/player/auth") + @Headers( + "content-type: application/x-www-form-urlencoded;charset=UTF-8", + "referer: https://www.yangshipin.cn/", + "yspappid: 519748109", + "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ) + fun getAuth( + @Header("cookie") cookie: String, + @Body request: AuthRequest, + ): Call } \ No newline at end of file