Files
dacard-test-miniapp/app/src/main/java/com/pukka/mediaapp/sdk/JetsenxSdkUtil.kt
zhaogd 9fd12c5eae 初始提交:第三方合作伙伴 App 测试项目
- 支持 EPG Intent 传参启动(deviceUid + programCode 自动播放)
- 代销模式 init/checkBenefits/SDK初始化/获取播放地址/播放
- Android 4.2 (API 17) 兼容
- OkHttp 3.12.x + TLS 1.2
- ExoPlayer 2.19.1 视频播放
2026-07-26 13:05:04 +08:00

270 lines
11 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.pukka.mediaapp.sdk
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.jetsenxsdk.api.JetsenxSdk
import com.jetsenxsdk.bean.VideoInfo
import com.jetsenxsdk.callback.JetsenxInitCallback
import com.jetsenxsdk.callback.JetsenxPricesCallback
import com.jetsenxsdk.callback.JetsenxUrlCallback
import com.pukka.mediaapp.utils.FileLogger
/**
* JetsenxSdk Kotlin 封装
*
* 视频地址获取流程:
* 1. init() 初始化SDK(用 thirdCardNo 作为 uid)
* 2. getAllVideoUrl() -> getAllAssetPrices(获取productCode) -> getVideoUrls/getFreeVideoUrls
*/
object JetsenxSdkUtil {
private const val TAG = "JetsenxSdkUtil"
// 渠道配置
private const val AGCODE = "1100101010200492358120169"
private const val TENANT = "huashitv"
private const val ACCESSER = "JSTASJD"
private const val SECRET = "kMeWsBu0QsTXpnft"
private const val SERVICE_COMBO_CODE = "1100121010200494755850172"
private const val FALLBACK_MAC = "CC:8C:DA:C6:E9:9B"
private var productCodeSp: String? = null
private var isInitialized = false
interface InitCallback {
fun onSuccess(memberCode: String)
fun onFail(message: String)
}
interface VideoUrlCallback {
fun onSuccess(videoUrl1080: String, videoUrl720: String, isFree: Boolean)
fun onError(code: String, message: String)
}
/**
* 初始化 SDK
* @param context 上下文
* @param uid thirdCardNo(天卡权益接口返回)
* @param mac 设备 MAC(由调用方传入,为空则从 DeviceInfoUtil 获取)
* @param callback 初始化回调
*/
fun init(context: Context, uid: String = "test_user", mac: String? = null, callback: InitCallback? = null) {
// 优先用传入的 mac,其次从 DeviceInfoUtil 获取,最后用 FALLBACK_MAC
val finalMac = when {
!mac.isNullOrEmpty() -> mac
else -> {
val identity = com.pukka.mediaapp.utils.DeviceInfoUtil.getDeviceIdentity(context)
identity.mac ?: FALLBACK_MAC
}
}
val finalUid = if (TextUtils.isEmpty(uid)) finalMac else uid
val utype = if (finalUid.matches(Regex("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"))) "3" else "2"
Log.d(TAG, "init: agcode=$AGCODE mac=$finalMac uid=$finalUid tenant=$TENANT utype=$utype")
FileLogger.log(TAG, "init: agcode=$AGCODE mac=$finalMac uid=$finalUid tenant=$TENANT utype=$utype")
try {
JetsenxSdk.init(
context, AGCODE, finalMac, finalUid,
TENANT, SECRET, ACCESSER, utype, "",
SERVICE_COMBO_CODE,
object : JetsenxInitCallback {
override fun onInitSuccess(memberCode: String, p1: Boolean) {
Log.d(TAG, "onInitSuccess: memberCode=$memberCode")
FileLogger.log(TAG, "SDK init成功: memberCode=$memberCode")
isInitialized = true
callback?.onSuccess(memberCode)
}
override fun onFail(code: String, msg: String) {
Log.e(TAG, "onInitFail: code=$code msg=$msg")
FileLogger.error(TAG, "SDK init失败: code=$code msg=$msg")
isInitialized = false
callback?.onFail(if (msg.isNotEmpty()) msg else "SDK初始化失败")
}
}
)
} catch (e: Exception) {
Log.e(TAG, "init exception", e)
callback?.onFail("SDK初始化异常: ${e.message}")
}
}
/**
* 获取视频播放地址
* @param parentContentType "8"=免费, 其他=付费
* @param programCode 节目编码
*/
fun getAllVideoUrl(
parentContentType: String,
programCode: String,
callback: VideoUrlCallback
) {
Log.d(TAG, "getAllVideoUrl: parentContentType=$parentContentType programCode=$programCode")
FileLogger.log(TAG, "getAllVideoUrl: parentContentType=$parentContentType programCode=$programCode")
if (TextUtils.isEmpty(programCode)) {
callback.onError("-1", "programCode is empty")
return
}
getAllVideoUrlNext(parentContentType, programCode, callback)
}
private fun getAllVideoUrlNext(
parentContentType: String,
programCode: String,
callback: VideoUrlCallback
) {
val cachedProductCode = productCodeSp
if (!cachedProductCode.isNullOrEmpty()) {
getAllVideoUrl2(parentContentType, cachedProductCode, programCode, callback)
} else {
JetsenxSdk.getAllAssetPrices(ACCESSER, SECRET, object : JetsenxPricesCallback {
override fun onAllAssetPrices(list: MutableList<Map<String, Any>>?) {
FileLogger.log(TAG, "getAllAssetPrices返回: count=${list?.size}")
if (list != null && list.isNotEmpty()) {
list.forEachIndexed { index, map ->
FileLogger.log(TAG, " assetPrice[$index]: $map")
}
val productCode = list[0]["packageCode"]?.toString() ?: ""
Log.d(TAG, "onAllAssetPrices: productCode=$productCode")
FileLogger.log(TAG, "使用productCode=$productCode")
productCodeSp = productCode
getAllVideoUrl2(parentContentType, productCode, programCode, callback)
} else {
callback.onError("-2", "productCode is NULL")
}
}
override fun onFail(code: String, msg: String) {
Log.e(TAG, "getAllAssetPrices onFail: code=$code msg=$msg")
FileLogger.error(TAG, "getAllAssetPrices失败: code=$code msg=$msg")
callback.onError(code, msg)
}
})
}
}
private fun getAllVideoUrl2(
parentContentType: String,
productCode: String,
programCode: String,
callback: VideoUrlCallback
) {
if (TextUtils.isEmpty(parentContentType)) {
callback.onError("-1", "parentContentType is NULL")
return
}
if (parentContentType != "8") {
getVideoUrls2(productCode, programCode, callback)
} else {
getFreeVideoUrls2(programCode, callback)
}
}
private fun getVideoUrls2(productCode: String, programCode: String, callback: VideoUrlCallback) {
JetsenxSdk.getVideoUrls(productCode, programCode, object : JetsenxUrlCallback {
override fun onVideoUrls(videoInfos: Array<VideoInfo>?) {
FileLogger.log(TAG, "getVideoUrls返回: count=${videoInfos?.size}")
if (videoInfos != null && videoInfos.isNotEmpty()) {
val (url1080, url720) = extractUrls(videoInfos)
if (url1080.isNotEmpty() || url720.isNotEmpty()) {
callback.onSuccess(url1080, url720, false)
} else {
callback.onError("411", "未返回播放地址")
}
} else {
callback.onError("411", "未返回播放地址")
}
}
override fun onFail(code: String, msg: String) {
FileLogger.error(TAG, "getVideoUrls(付费)失败: code=$code msg=$msg")
if (msg.contains("未订购") || code == "-110") {
getFreeVideoUrls2(programCode, callback)
} else {
callback.onError(code, msg)
}
}
})
}
private fun getFreeVideoUrls2(programCode: String, callback: VideoUrlCallback) {
JetsenxSdk.getFreeVideoUrls(programCode, object : JetsenxUrlCallback {
override fun onVideoUrls(videoInfos: Array<VideoInfo>?) {
FileLogger.log(TAG, "getFreeVideoUrls返回: count=${videoInfos?.size}")
if (videoInfos != null && videoInfos.isNotEmpty()) {
val (url1080, url720) = extractUrls(videoInfos)
if (url1080.isNotEmpty() || url720.isNotEmpty()) {
callback.onSuccess(url1080, url720, true)
} else {
callback.onError("410", "未返回播放地址")
}
} else {
callback.onError("410", "未返回播放地址")
}
}
override fun onFail(code: String, msg: String) {
FileLogger.error(TAG, "getFreeVideoUrls(免费)失败: code=$code msg=$msg")
// 免费接口失败时,回退尝试付费接口
val cachedProductCode = productCodeSp
if (!cachedProductCode.isNullOrEmpty()) {
FileLogger.log(TAG, "免费接口失败,回退尝试付费接口 getVideoUrls")
JetsenxSdk.getVideoUrls(cachedProductCode, programCode, object : JetsenxUrlCallback {
override fun onVideoUrls(videoInfos: Array<VideoInfo>?) {
FileLogger.log(TAG, "回退getVideoUrls返回: count=${videoInfos?.size}")
if (videoInfos != null && videoInfos.isNotEmpty()) {
val (url1080, url720) = extractUrls(videoInfos)
if (url1080.isNotEmpty() || url720.isNotEmpty()) {
callback.onSuccess(url1080, url720, false)
} else {
callback.onError(code, msg)
}
} else {
callback.onError(code, msg)
}
}
override fun onFail(code2: String, msg2: String) {
FileLogger.error(TAG, "回退getVideoUrls也失败: code=$code2 msg=$msg2")
callback.onError(code, msg)
}
})
} else {
callback.onError(code, msg)
}
}
})
}
private fun extractUrls(videoInfos: Array<VideoInfo>): Pair<String, String> {
var url1080 = ""
var url720 = ""
for (info in videoInfos) {
val format = info.format ?: ""
if (format.contains("720") && url720.isEmpty()) {
url720 = info.videoUrl ?: ""
} else if (format.contains("1080") && url1080.isEmpty()) {
url1080 = info.videoUrl ?: ""
}
}
FileLogger.log(TAG, "extractUrls: 1080=$url1080 720=$url720")
return Pair(url1080, url720)
}
private fun getMacAddress(context: Context): String {
return try {
val wifiManager = context.applicationContext
.getSystemService(Context.WIFI_SERVICE) as android.net.wifi.WifiManager
val wifiInfo = wifiManager.connectionInfo
val mac = wifiInfo.macAddress
if (mac.isNullOrEmpty() || mac == "02:00:00:00:00:00") "" else mac
} catch (e: Exception) { "" }
}
fun isReady(): Boolean = isInitialized
}