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>?) { 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?) { 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?) { 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?) { 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): Pair { 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 }