package com.videoapp.tv import android.os.Bundle import android.view.KeyEvent import android.view.View import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView import com.videoapp.tv.data.ConfigRepository import com.videoapp.tv.engine.Episode import com.videoapp.tv.engine.VideoExtractor import com.videoapp.tv.engine.WebViewPlayer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class PlayerActivity : AppCompatActivity() { private lateinit var playerView: PlayerView private lateinit var playerWebView: WebView private lateinit var episodePanel: View private lateinit var episodeList: android.widget.LinearLayout private lateinit var loadingIndicator: View private lateinit var errorText: android.widget.TextView private var exoPlayer: ExoPlayer? = null private val videoExtractor = VideoExtractor() private val configRepo by lazy { ConfigRepository(this) } private val webViewPlayer by lazy { WebViewPlayer(this) } private var episodes: List = emptyList() private var currentEpisodeIndex = 0 private var isPlayerReady = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_player) playerView = findViewById(R.id.player_view) playerWebView = findViewById(R.id.player_webview) episodePanel = findViewById(R.id.episode_panel) episodeList = findViewById(R.id.episode_list) loadingIndicator = findViewById(R.id.loading_indicator) errorText = findViewById(R.id.error_text) val detailUrl = intent.getStringExtra("detail_url") ?: "" val title = intent.getStringExtra("title") ?: "" initExoPlayer() loadVideos(detailUrl, title) setupFullscreenListener() } private fun initExoPlayer() { exoPlayer = ExoPlayer.Builder(this).build().also { player -> playerView.player = player } } private fun loadVideos(detailUrl: String, title: String) { showLoading(true) val config = configRepo.getConfig() lifecycleScope.launch { try { val videoInfo = videoExtractor.extractVideos(detailUrl, config) episodes = videoInfo.episodes if (videoInfo.episodes.isNotEmpty()) { buildEpisodeUI(videoInfo.episodes) playEpisode(videoInfo.episodes.first()) } else { episodePanel.visibility = View.GONE tryPlayDirectly(detailUrl, config) } } catch (e: Exception) { showError("加载视频失败: ${e.message}") showLoading(false) } } } private fun buildEpisodeUI(eps: List) { episodeList.removeAllViews() episodePanel.visibility = View.VISIBLE eps.forEachIndexed { index, ep -> val btn = Button(this).apply { text = ep.title setBackgroundResource(R.drawable.episode_selector) setTextColor(resources.getColor(R.color.text_primary, null)) textSize = 13f minWidth = 0 setPadding(16, 8, 16, 8) isFocusable = true isFocusableInTouchMode = true setOnClickListener { currentEpisodeIndex = index highlightEpisode(it) playEpisode(ep) } } episodeList.addView(btn) } } private fun playEpisode(ep: Episode) { showLoading(true) val config = configRepo.getConfig() lifecycleScope.launch { val (directUrl, iframeUrl) = videoExtractor.extractFromPlayPage(ep.playUrl, config) if (directUrl != null) { // Play with ExoPlayer playWithExoPlayer(directUrl) } else if (iframeUrl != null) { // Play iframe in WebView playWithWebView(iframeUrl) } else { // Fallback: load play page in WebView playWithWebView(ep.playUrl) } } } private fun tryPlayDirectly(detailUrl: String, config: com.videoapp.tv.data.SiteConfig) { lifecycleScope.launch { val (directUrl, iframeUrl) = videoExtractor.extractFromPlayPage(detailUrl, config) if (directUrl != null) { playWithExoPlayer(directUrl) } else if (iframeUrl != null) { playWithWebView(iframeUrl) } else { playWithWebView(detailUrl) } } } private fun playWithExoPlayer(url: String) { playerWebView.visibility = View.GONE playerView.visibility = View.VISIBLE showLoading(false) val mediaItem = MediaItem.fromUri(url) exoPlayer?.apply { setMediaItem(mediaItem) prepare() playWhenReady = true } } private fun playWithWebView(url: String) { playerView.visibility = View.GONE playerWebView.visibility = View.VISIBLE showLoading(false) playerWebView.settings.javaScriptEnabled = true playerWebView.settings.domStorageEnabled = true playerWebView.settings.mediaPlaybackRequiresUserGesture = false playerWebView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView, url: String) { super.onPageFinished(view, url) showLoading(false) } } playerWebView.loadUrl(url) } private fun highlightEpisode(view: View) { for (i in 0 until episodeList.childCount) { episodeList.getChildAt(i).isSelected = (episodeList.getChildAt(i) == view) } } private fun showLoading(show: Boolean) { loadingIndicator.visibility = if (show) View.VISIBLE else View.GONE } private fun showError(msg: String) { errorText.text = msg errorText.visibility = View.VISIBLE showLoading(false) } private fun setupFullscreenListener() { // Toggle episode panel visibility on dpad up/down var panelVisible = true playerView.setOnClickListener { panelVisible = !panelVisible episodePanel.visibility = if (panelVisible) View.VISIBLE else View.GONE } } override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { return when (keyCode) { KeyEvent.KEYCODE_DPAD_UP -> { episodePanel.visibility = View.VISIBLE true } KeyEvent.KEYCODE_DPAD_DOWN -> { if (episodePanel.visibility == View.VISIBLE) { episodePanel.visibility = View.GONE } true } else -> super.onKeyDown(keyCode, event) } } override fun onPause() { super.onPause() exoPlayer?.playWhenReady = false } override fun onDestroy() { super.onDestroy() exoPlayer?.release() exoPlayer = null } }