package com.videoapp.tv import android.os.Bundle import android.os.Handler import android.os.Looper import android.view.View import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button import android.widget.ImageButton import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.PlayerView import com.videoapp.tv.data.AppDatabase import com.videoapp.tv.data.ConfigRepository import com.videoapp.tv.data.PlayHistory import com.videoapp.tv.engine.Episode import com.videoapp.tv.engine.PlaySource import com.videoapp.tv.engine.VideoExtractor import kotlinx.coroutines.launch class PlayerActivity : AppCompatActivity() { private lateinit var playerView: PlayerView private lateinit var playerWebView: WebView private lateinit var controlPanel: View private lateinit var sourceList: LinearLayout private lateinit var episodeList: LinearLayout private lateinit var loadingIndicator: View private lateinit var errorText: android.widget.TextView private lateinit var btnClose: ImageButton private var exoPlayer: ExoPlayer? = null private val videoExtractor = VideoExtractor() private val configRepo by lazy { ConfigRepository(this) } private val playHistoryDao by lazy { AppDatabase.getInstance(this).playHistoryDao() } private var sources: List = emptyList() private var currentSourceIndex = 0 private var currentEpisode: Episode? = null private val hideHandler = Handler(Looper.getMainLooper()) private val hideRunnable = Runnable { hideControls() } private var controlsVisible = true // 用于保存播放历史的信息 private var videoTitle: String = "" private var videoCategory: String? = null private var coverUrl: String? = null private var detailUrl: String = "" 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) controlPanel = findViewById(R.id.control_panel) sourceList = findViewById(R.id.source_list) episodeList = findViewById(R.id.episode_list) loadingIndicator = findViewById(R.id.loading_indicator) errorText = findViewById(R.id.error_text) btnClose = findViewById(R.id.btn_close) detailUrl = intent.getStringExtra("detail_url") ?: "" videoTitle = intent.getStringExtra("title") ?: "" videoCategory = intent.getStringExtra("category") coverUrl = intent.getStringExtra("cover_url") btnClose.setOnClickListener { finish() } initExoPlayer() loadSources(detailUrl) setupTouchListeners() } private fun initExoPlayer() { exoPlayer = ExoPlayer.Builder(this).build().also { player -> playerView.player = player } } private fun loadSources(detailUrl: String) { showLoading(true) val config = configRepo.getConfig() lifecycleScope.launch { try { sources = videoExtractor.extractVideos(detailUrl, config) if (sources.isNotEmpty()) { buildSourceUI() selectSource(0) resetAutoHide() } else { tryPlayDirectly(detailUrl, config) } } catch (e: Exception) { showError("加载失败: ${e.message}") showLoading(false) } } } private fun buildSourceUI() { sourceList.removeAllViews() sources.forEachIndexed { index, source -> val btn = Button(this).apply { text = source.name setBackgroundResource(R.drawable.episode_selector) setTextColor(ContextCompat.getColor(this@PlayerActivity, R.color.text_primary)) textSize = 13f minWidth = 0 setPadding(16, 8, 16, 8) isFocusable = true isFocusableInTouchMode = true setOnClickListener { selectSource(index) resetAutoHide() } } sourceList.addView(btn) } } private fun selectSource(index: Int) { if (index !in sources.indices) return currentSourceIndex = index // Highlight selected source for (i in 0 until sourceList.childCount) { sourceList.getChildAt(i).isSelected = (i == index) } // Build episode list for this source val source = sources[index] buildEpisodeUI(source.episodes) // Auto-play first episode if (source.episodes.isNotEmpty()) { playEpisode(source.episodes.first()) } } private fun buildEpisodeUI(eps: List) { episodeList.removeAllViews() eps.forEach { ep -> val btn = Button(this).apply { text = ep.title setBackgroundResource(R.drawable.episode_selector) setTextColor(ContextCompat.getColor(this@PlayerActivity, R.color.text_primary)) textSize = 12f minWidth = 0 setPadding(14, 6, 14, 6) isFocusable = true isFocusableInTouchMode = true setOnClickListener { highlightEpisode(it) playEpisode(ep) resetAutoHide() } } episodeList.addView(btn) } } private fun playEpisode(ep: Episode) { currentEpisode = ep showLoading(true) val config = configRepo.getConfig() // 保存播放历史 savePlayHistory(ep.title) lifecycleScope.launch { val (directUrl, iframeUrl) = videoExtractor.extractFromPlayPage(ep.playUrl, config) if (directUrl != null) { playWithExoPlayer(directUrl) } else if (iframeUrl != null) { playWithWebView(iframeUrl) } else { playWithWebView(ep.playUrl) } } } private fun savePlayHistory(episodeName: String?) { lifecycleScope.launch { try { // 检查是否已存在相同的播放记录 val existing = playHistoryDao.findByDetailUrl(detailUrl) if (existing != null) { // 更新现有记录的播放时间和剧集 playHistoryDao.updatePlayTime(existing.id, System.currentTimeMillis(), episodeName) } else { // 插入新记录 val playHistory = PlayHistory( title = videoTitle, episodeName = episodeName, detailUrl = detailUrl, coverUrl = coverUrl, category = videoCategory, playTime = System.currentTimeMillis() ) playHistoryDao.insert(playHistory) } } catch (e: Exception) { // 保存播放历史失败不影响播放功能 e.printStackTrace() } } } private fun tryPlayDirectly(detailUrl: String, config: com.videoapp.tv.data.SiteConfig) { // 保存播放历史(直接播放时使用标题作为剧集名) savePlayHistory(videoTitle) lifecycleScope.launch { val (directUrl, iframeUrl) = videoExtractor.extractFromPlayPage(detailUrl, config) if (directUrl != null) { controlPanel.visibility = View.GONE playWithExoPlayer(directUrl) } else if (iframeUrl != null) { controlPanel.visibility = View.GONE playWithWebView(iframeUrl) } else { controlPanel.visibility = View.GONE 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 setupTouchListeners() { val listener = View.OnClickListener { toggleControls() } playerView.setOnClickListener(listener) playerWebView.setOnClickListener(listener) } private fun toggleControls() { if (controlsVisible) { hideControls() } else { showControls() resetAutoHide() } } private fun showControls() { controlsVisible = true btnClose.visibility = View.VISIBLE controlPanel.visibility = View.VISIBLE } private fun hideControls() { controlsVisible = false btnClose.visibility = View.GONE controlPanel.visibility = View.GONE } private fun resetAutoHide() { hideHandler.removeCallbacks(hideRunnable) if (!controlsVisible) { showControls() } hideHandler.postDelayed(hideRunnable, 4000) } override fun onPause() { super.onPause() exoPlayer?.playWhenReady = false } override fun onDestroy() { super.onDestroy() hideHandler.removeCallbacks(hideRunnable) exoPlayer?.release() exoPlayer = null } }