- 支持 EPG Intent 传参启动(deviceUid + programCode 自动播放) - 代销模式 init/checkBenefits/SDK初始化/获取播放地址/播放 - Android 4.2 (API 17) 兼容 - OkHttp 3.12.x + TLS 1.2 - ExoPlayer 2.19.1 视频播放
270 lines
11 KiB
Kotlin
270 lines
11 KiB
Kotlin
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
|
||
}
|