fix: restore Android PiP/fullscreen and remove stale media caching

Fixes #142
This commit is contained in:
kuekhaoyang
2026-04-08 13:49:24 +08:00
parent 4db08dbf72
commit 372e60c4e5
6 changed files with 227 additions and 117 deletions
@@ -2,18 +2,27 @@ package com.kvideo.tv
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.util.Rational
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.webkit.JavascriptInterface
import android.webkit.WebChromeClient
import android.webkit.WebChromeClient.CustomViewCallback
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.TextView
import androidx.activity.ComponentActivity
@@ -22,36 +31,33 @@ class MainActivity : ComponentActivity() {
companion object {
private const val PREFS_NAME = "kvideo_tv_settings"
private const val PREF_SERVER_URL = "server_url"
private const val TAG = "KVideoMainActivity"
}
private lateinit var webView: WebView
private lateinit var setupContainer: View
private lateinit var fullscreenContainer: FrameLayout
private lateinit var urlInput: EditText
private lateinit var statusText: TextView
private lateinit var openButton: Button
private lateinit var saveButton: Button
private lateinit var prefs: android.content.SharedPreferences
private var customView: View? = null
private var customViewCallback: CustomViewCallback? = null
private var wasSetupVisibleBeforeFullscreen = false
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
// Fullscreen immersive mode
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
)
applyImmersiveMode()
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webview)
setupContainer = findViewById(R.id.setup_container)
fullscreenContainer = findViewById(R.id.fullscreen_container)
urlInput = findViewById(R.id.url_input)
statusText = findViewById(R.id.status_text)
openButton = findViewById(R.id.open_button)
@@ -94,7 +100,41 @@ class MainActivity : ComponentActivity() {
}
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
webChromeClient = object : WebChromeClient() {
override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {
if (view == null || callback == null) {
callback?.onCustomViewHidden()
return
}
if (customView != null) {
callback.onCustomViewHidden()
return
}
wasSetupVisibleBeforeFullscreen = isSetupVisible()
customView = view
customViewCallback = callback
fullscreenContainer.removeAllViews()
fullscreenContainer.addView(
view,
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
fullscreenContainer.visibility = View.VISIBLE
webView.visibility = View.GONE
setupContainer.visibility = View.GONE
applyImmersiveMode()
}
override fun onHideCustomView() {
exitCustomFullscreen()
}
}
addJavascriptInterface(AndroidPlayerBridge(), "KVideoAndroid")
}
val configuredUrl = getConfiguredUrl()
@@ -107,6 +147,11 @@ class MainActivity : ComponentActivity() {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (!isSetupVisible() && keyCode == KeyEvent.KEYCODE_BACK && customView != null) {
exitCustomFullscreen()
return true
}
if (!isSetupVisible() && (keyCode == KeyEvent.KEYCODE_MENU || keyCode == KeyEvent.KEYCODE_SETTINGS)) {
showSetup(getString(R.string.status_settings_hint))
return true
@@ -130,6 +175,11 @@ class MainActivity : ComponentActivity() {
@Deprecated("Use OnBackPressedDispatcher")
override fun onBackPressed() {
if (customView != null) {
exitCustomFullscreen()
return
}
if (isSetupVisible()) {
@Suppress("DEPRECATION")
super.onBackPressed()
@@ -143,7 +193,30 @@ class MainActivity : ComponentActivity() {
}
}
override fun onResume() {
super.onResume()
applyImmersiveMode()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
applyImmersiveMode()
}
}
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (!isInPictureInPictureMode) {
applyImmersiveMode()
}
}
override fun onDestroy() {
exitCustomFullscreen()
webView.destroy()
super.onDestroy()
}
@@ -229,4 +302,66 @@ class MainActivity : ComponentActivity() {
val scheme = uri.scheme?.lowercase()
return (scheme == "http" || scheme == "https") && !uri.host.isNullOrBlank()
}
private fun applyImmersiveMode() {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
)
}
private fun exitCustomFullscreen() {
val currentCustomView = customView ?: return
fullscreenContainer.removeView(currentCustomView)
fullscreenContainer.visibility = View.GONE
customView = null
webView.visibility = View.VISIBLE
if (wasSetupVisibleBeforeFullscreen) {
setupContainer.visibility = View.VISIBLE
}
customViewCallback?.onCustomViewHidden()
customViewCallback = null
wasSetupVisibleBeforeFullscreen = false
applyImmersiveMode()
}
private fun isPictureInPictureSupported(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false
}
return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
}
private inner class AndroidPlayerBridge {
@JavascriptInterface
fun isPictureInPictureSupported(): Boolean = isPictureInPictureSupported()
@JavascriptInterface
fun enterPictureInPicture(width: Int, height: Int): Boolean {
if (!isPictureInPictureSupported()) {
return false
}
runOnUiThread {
try {
exitCustomFullscreen()
val builder = android.app.PictureInPictureParams.Builder()
if (width > 0 && height > 0) {
builder.setAspectRatio(Rational(width, height))
}
enterPictureInPictureMode(builder.build())
} catch (error: IllegalStateException) {
Log.w(TAG, "Failed to enter Picture-in-Picture mode", error)
}
}
return true
}
}
}