diff --git a/example/libs/sellycloudsdk-1.0.0.aar b/example/libs/sellycloudsdk-1.0.0.aar index 667fb32..230780c 100644 Binary files a/example/libs/sellycloudsdk-1.0.0.aar and b/example/libs/sellycloudsdk-1.0.0.aar differ diff --git a/example/src/main/java/com/demo/SellyCloudSDK/FeatureHubActivity.kt b/example/src/main/java/com/demo/SellyCloudSDK/FeatureHubActivity.kt index 082424d..8970b5f 100644 --- a/example/src/main/java/com/demo/SellyCloudSDK/FeatureHubActivity.kt +++ b/example/src/main/java/com/demo/SellyCloudSDK/FeatureHubActivity.kt @@ -3,11 +3,17 @@ package com.demo.SellyCloudSDK import android.app.Dialog import android.content.Intent import android.graphics.Rect +import android.graphics.Typeface import android.os.Bundle +import android.util.TypedValue import android.view.Gravity import android.view.View import android.view.ViewGroup +import android.widget.GridLayout +import android.widget.TextView import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.GridLayoutManager @@ -34,6 +40,8 @@ import com.demo.SellyCloudSDK.live.square.AliveStreamItem import com.demo.SellyCloudSDK.live.square.isPkStream import com.demo.SellyCloudSDK.login.DemoLoginStore import com.demo.SellyCloudSDK.login.LoginActivity +import com.demo.SellyCloudSDK.vod.VodListRepository +import com.demo.SellyCloudSDK.vod.VodListResult import com.demo.SellyCloudSDK.vod.VodPlayActivity import com.sellycloud.sellycloudsdk.SellyLiveMode import kotlinx.coroutines.CoroutineScope @@ -499,9 +507,96 @@ class FeatureHubActivity : AppCompatActivity() { startActivity(VodPlayActivity.createIntent(this, "asset:///vod/sample.mp4")) } + loadVodList(dialogBinding) + dialog.show() } + private fun loadVodList(dialogBinding: DialogVodInputBinding) { + dialogBinding.pbVodListFull.isVisible = true + dialogBinding.pbVodList.isVisible = true + dialogBinding.tvVodListError.isVisible = false + dialogBinding.gridVodFormats.isVisible = false + + uiScope.launch { + val result = VodListRepository.fetchVodList() + dialogBinding.pbVodListFull.isVisible = false + dialogBinding.pbVodList.isVisible = false + + when (result) { + is VodListResult.Success -> { + populateVodChips(dialogBinding, result.formats) + } + is VodListResult.Error -> { + dialogBinding.tvVodListError.text = result.message + dialogBinding.tvVodListError.isVisible = true + dialogBinding.tvVodListError.setOnClickListener { + loadVodList(dialogBinding) + } + } + } + } + } + + private fun populateVodChips( + dialogBinding: DialogVodInputBinding, + formats: Map + ) { + val grid = dialogBinding.gridVodFormats + grid.removeAllViews() + grid.isVisible = true + + val dp3 = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 3f, resources.displayMetrics + ).toInt() + val chipHeightPx = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 34f, resources.displayMetrics + ).toInt() + + var selectedChip: TextView? = null + + formats.entries.forEachIndexed { index, (format, url) -> + val chip = TextView(this).apply { + text = format + gravity = Gravity.CENTER + setTextSize(TypedValue.COMPLEX_UNIT_SP, 13f) + typeface = Typeface.DEFAULT_BOLD + setTextColor(ContextCompat.getColor(context, R.color.av_text_primary)) + setBackgroundResource(R.drawable.selector_av_vod_chip) + isSelected = false + } + + val row = index / 4 + val col = index % 4 + val param = GridLayout.LayoutParams( + GridLayout.spec(row, 1f), + GridLayout.spec(col, 1f) + ).apply { + width = 0 + height = chipHeightPx + setMargins( + if (col > 0) dp3 else 0, + if (row > 0) dp3 else 0, + 0, 0 + ) + } + + chip.setOnClickListener { + if (selectedChip == chip) return@setOnClickListener + selectedChip?.let { prev -> + prev.isSelected = false + prev.setTextColor(ContextCompat.getColor(this, R.color.av_text_primary)) + } + chip.isSelected = true + chip.setTextColor(ContextCompat.getColor(this, R.color.brand_primary_text_on)) + selectedChip = chip + dialogBinding.etVodUrl.setText(url) + } + + grid.addView(chip, param) + } + } + private fun setupSettingsSave() { binding.btnSaveSettings.setOnClickListener { val settings = uiToSettingsOrNull() ?: return@setOnClickListener diff --git a/example/src/main/java/com/demo/SellyCloudSDK/vod/VodListRepository.kt b/example/src/main/java/com/demo/SellyCloudSDK/vod/VodListRepository.kt new file mode 100644 index 0000000..27b9df7 --- /dev/null +++ b/example/src/main/java/com/demo/SellyCloudSDK/vod/VodListRepository.kt @@ -0,0 +1,51 @@ +package com.demo.SellyCloudSDK.vod + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import org.json.JSONObject + +private const val VOD_LIST_URL = "http://rtmp.sellycloud.io:8089/live/sdk/demo/vodlist" + +sealed class VodListResult { + data class Success(val formats: Map) : VodListResult() + data class Error(val message: String) : VodListResult() +} + +object VodListRepository { + private val client = OkHttpClient() + + suspend fun fetchVodList(): VodListResult = withContext(Dispatchers.IO) { + val request = Request.Builder() + .url(VOD_LIST_URL) + .get() + .build() + + try { + client.newCall(request).execute().use { response -> + val body = response.body?.string().orEmpty() + if (!response.isSuccessful) { + return@withContext VodListResult.Error("网络错误: ${response.code}") + } + if (body.isBlank()) { + return@withContext VodListResult.Error("服务返回为空") + } + val json = JSONObject(body) + val formats = linkedMapOf() + val keys = json.keys() + while (keys.hasNext()) { + val key = keys.next() + val url = json.optString(key).takeIf { it.isNotBlank() } ?: continue + formats[key.uppercase()] = url + } + if (formats.isEmpty()) { + return@withContext VodListResult.Error("暂无在线资源") + } + return@withContext VodListResult.Success(formats) + } + } catch (e: Exception) { + return@withContext VodListResult.Error(e.message ?: "网络请求失败") + } + } +} diff --git a/example/src/main/res/drawable/bg_av_vod_chip_selected.xml b/example/src/main/res/drawable/bg_av_vod_chip_selected.xml new file mode 100644 index 0000000..749cdf7 --- /dev/null +++ b/example/src/main/res/drawable/bg_av_vod_chip_selected.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/example/src/main/res/drawable/selector_av_vod_chip.xml b/example/src/main/res/drawable/selector_av_vod_chip.xml new file mode 100644 index 0000000..b39bcbb --- /dev/null +++ b/example/src/main/res/drawable/selector_av_vod_chip.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/example/src/main/res/layout/dialog_vod_input.xml b/example/src/main/res/layout/dialog_vod_input.xml index af2f36a..86090c0 100644 --- a/example/src/main/res/layout/dialog_vod_input.xml +++ b/example/src/main/res/layout/dialog_vod_input.xml @@ -17,78 +17,142 @@ android:src="@drawable/ic_av_close" app:tint="@color/av_text_primary" /> - + android:scrollbars="none"> - + android:background="@drawable/bg_av_dialog_card_gray" + android:orientation="vertical" + android:paddingStart="18dp" + android:paddingTop="16dp" + android:paddingEnd="18dp" + android:paddingBottom="18dp"> - + - + -