Files
android-tv/app/src/main/java/com/videoapp/tv/PlayerActivity.kt

228 lines
7.4 KiB
Kotlin
Raw Normal View History

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.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.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<Episode> = 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<Episode>) {
episodeList.removeAllViews()
episodePanel.visibility = View.VISIBLE
eps.forEachIndexed { index, ep ->
val btn = Button(this).apply {
text = ep.title
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 {
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
}
}