v1.0.4 - PlaybackActivity 업데이트
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId "com.example.tvmon"
|
applicationId "com.example.tvmon"
|
||||||
minSdk 28
|
minSdk 28
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 4
|
versionCode 5
|
||||||
versionName "1.0.3"
|
versionName "1.0.4"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import android.webkit.WebViewClient
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.example.tvmon.R
|
import com.example.tvmon.R
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
|
||||||
class PlaybackActivity : AppCompatActivity() {
|
class PlaybackActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -31,9 +32,13 @@ class PlaybackActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private lateinit var webView: WebView
|
private lateinit var webView: WebView
|
||||||
private lateinit var loadingOverlay: View
|
private lateinit var loadingOverlay: View
|
||||||
private var isVideoPlaying = false
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
// [FIX 1] loadingRunnable을 명시적으로 관리하여 중복 postDelayed 방지
|
||||||
|
private val hideLoadingRunnable = Runnable {
|
||||||
|
loadingOverlay.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -67,45 +72,9 @@ class PlaybackActivity : AppCompatActivity() {
|
|||||||
setTitle("Tvmon")
|
setTitle("Tvmon")
|
||||||
}
|
}
|
||||||
|
|
||||||
val existingWebView = findViewById<WebView>(R.id.webview)
|
// [FIX 2] 불필요한 WebView 제거/재생성 패턴 제거.
|
||||||
val parent = existingWebView.parent as android.view.ViewGroup
|
// XML에 정의된 WebView를 그대로 사용하고, 서브클래스 기능은 setOnKeyListener로 처리.
|
||||||
val index = parent.indexOfChild(existingWebView)
|
webView = findViewById(R.id.webview)
|
||||||
val layoutParams = existingWebView.layoutParams
|
|
||||||
parent.removeView(existingWebView)
|
|
||||||
|
|
||||||
webView = object : WebView(this) {
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
|
||||||
android.util.Log.d("WebViewKey", "WebView.onKeyDown: keyCode=$keyCode")
|
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
|
|
||||||
keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
|
|
||||||
keyCode == KeyEvent.KEYCODE_DPAD_UP ||
|
|
||||||
keyCode == KeyEvent.KEYCODE_DPAD_DOWN ||
|
|
||||||
keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
|
||||||
val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event)
|
|
||||||
android.util.Log.d("WebViewKey", "WebView.dispatchKeyEvent: keyCode=$keyCode, action=${event.action}")
|
|
||||||
return super.dispatchKeyEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scrollTo(x: Int, y: Int) {}
|
|
||||||
override fun scrollBy(x: Int, y: Int) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
webView.id = R.id.webview
|
|
||||||
webView.layoutParams = layoutParams
|
|
||||||
webView.isFocusable = true
|
|
||||||
webView.isFocusableInTouchMode = true
|
|
||||||
parent.addView(webView, index)
|
|
||||||
|
|
||||||
webView.setOnKeyListener { _, keyCode, event ->
|
webView.setOnKeyListener { _, keyCode, event ->
|
||||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||||
@@ -115,31 +84,12 @@ class PlaybackActivity : AppCompatActivity() {
|
|||||||
toggleVideoPlayback()
|
toggleVideoPlayback()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
KeyEvent.KEYCODE_DPAD_LEFT -> { seekVideo(-10000); true }
|
||||||
seekVideo(-10000)
|
KeyEvent.KEYCODE_DPAD_RIGHT -> { seekVideo(10000); true }
|
||||||
android.util.Log.d("PlaybackActivity", "setOnKeyListener: LEFT")
|
KeyEvent.KEYCODE_DPAD_UP -> { adjustVolume(0.1f); true }
|
||||||
true
|
KeyEvent.KEYCODE_DPAD_DOWN -> { adjustVolume(-0.1f); true }
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
|
||||||
seekVideo(10000)
|
|
||||||
android.util.Log.d("PlaybackActivity", "setOnKeyListener: RIGHT")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP -> {
|
|
||||||
adjustVolume(0.1f)
|
|
||||||
android.util.Log.d("PlaybackActivity", "setOnKeyListener: UP")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN -> {
|
|
||||||
adjustVolume(-0.1f)
|
|
||||||
android.util.Log.d("PlaybackActivity", "setOnKeyListener: DOWN")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_BACK -> {
|
KeyEvent.KEYCODE_BACK -> {
|
||||||
if (webView.canGoBack()) {
|
if (webView.canGoBack()) { webView.goBack(); true } else false
|
||||||
webView.goBack()
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
@@ -153,125 +103,88 @@ class PlaybackActivity : AppCompatActivity() {
|
|||||||
webView.loadUrl(url)
|
webView.loadUrl(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
private fun setupWebView() {
|
private fun setupWebView() {
|
||||||
val webSettings: WebSettings = webView.settings
|
val webSettings: WebSettings = webView.settings
|
||||||
|
|
||||||
webSettings.javaScriptEnabled = true
|
webSettings.javaScriptEnabled = true
|
||||||
webSettings.domStorageEnabled = true
|
webSettings.domStorageEnabled = true
|
||||||
webSettings.databaseEnabled = true
|
webSettings.databaseEnabled = true
|
||||||
webSettings.allowFileAccess = true
|
webSettings.allowFileAccess = true
|
||||||
webSettings.allowContentAccess = true
|
webSettings.allowContentAccess = true
|
||||||
webSettings.mediaPlaybackRequiresUserGesture = false
|
webSettings.mediaPlaybackRequiresUserGesture = false
|
||||||
webSettings.loadsImagesAutomatically = true
|
webSettings.loadsImagesAutomatically = true
|
||||||
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||||
webSettings.userAgentString = USER_AGENT
|
webSettings.userAgentString = USER_AGENT
|
||||||
webSettings.useWideViewPort = true
|
webSettings.useWideViewPort = true
|
||||||
webSettings.loadWithOverviewMode = true
|
webSettings.loadWithOverviewMode = true
|
||||||
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
|
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||||
webSettings.allowUniversalAccessFromFileURLs = true
|
webSettings.allowUniversalAccessFromFileURLs = true
|
||||||
webSettings.allowFileAccessFromFileURLs = true
|
webSettings.allowFileAccessFromFileURLs = true
|
||||||
|
webSettings.blockNetworkImage = false
|
||||||
|
|
||||||
// Video playback optimizations
|
webView.addJavascriptInterface(object {
|
||||||
webSettings.mediaPlaybackRequiresUserGesture = false
|
@android.webkit.JavascriptInterface
|
||||||
webSettings.blockNetworkImage = false
|
fun hideLoadingOverlay() {
|
||||||
|
|
||||||
webView.addJavascriptInterface(object {
|
|
||||||
@android.webkit.JavascriptInterface
|
|
||||||
fun hideLoadingOverlay() {
|
|
||||||
runOnUiThread {
|
|
||||||
loadingOverlay.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, "Android")
|
|
||||||
|
|
||||||
val cookieManager = CookieManager.getInstance()
|
|
||||||
cookieManager.setAcceptCookie(true)
|
|
||||||
cookieManager.setAcceptThirdPartyCookies(webView, true)
|
|
||||||
|
|
||||||
webView.webChromeClient = object : WebChromeClient() {
|
|
||||||
override fun onConsoleMessage(cm: ConsoleMessage?): Boolean {
|
|
||||||
val message = cm?.message() ?: return false
|
|
||||||
android.util.Log.d("WebViewConsole", "[$${cm.sourceId()}:${cm.lineNumber()}] $message")
|
|
||||||
|
|
||||||
if (message.contains("player") || message.contains("video") || message.contains(".m3u8") || message.contains(".mp4")) {
|
|
||||||
android.util.Log.i("WebViewConsole", "Player info: $message")
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPermissionRequest(request: android.webkit.PermissionRequest?) {
|
|
||||||
android.util.Log.d("PlaybackActivity", "Permission request: ${request?.resources?.joinToString()}")
|
|
||||||
request?.grant(request.resources)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webView.webViewClient = object : WebViewClient() {
|
|
||||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
|
||||||
val url = request?.url?.toString() ?: ""
|
|
||||||
|
|
||||||
if (url.contains(".css") || url.contains(".js") && url.contains("tvmon.site")) {
|
|
||||||
try {
|
|
||||||
val conn = java.net.URL(url).openConnection() as java.net.HttpURLConnection
|
|
||||||
conn.requestMethod = "GET"
|
|
||||||
conn.connectTimeout = 8000
|
|
||||||
conn.readTimeout = 8000
|
|
||||||
conn.setRequestProperty("User-Agent", USER_AGENT)
|
|
||||||
conn.setRequestProperty("Referer", "https://tvmon.site/")
|
|
||||||
|
|
||||||
val contentType = conn.contentType ?: "text/plain"
|
|
||||||
val encoding = conn.contentEncoding ?: "UTF-8"
|
|
||||||
val inputStream = conn.inputStream
|
|
||||||
val content = inputStream.bufferedReader().use { it.readText() }
|
|
||||||
inputStream.close()
|
|
||||||
|
|
||||||
var modifiedContent = content
|
|
||||||
|
|
||||||
if (contentType.contains("text/html", true)) {
|
|
||||||
modifiedContent = injectFullscreenStyles(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
return WebResourceResponse(
|
|
||||||
contentType, encoding,
|
|
||||||
200, "OK",
|
|
||||||
mapOf("Access-Control-Allow-Origin" to "*"),
|
|
||||||
ByteArrayInputStream(modifiedContent.toByteArray(charset(encoding)))
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
android.util.Log.w("PlaybackActivity", "Intercept failed for $url: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.shouldInterceptRequest(view, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
|
|
||||||
super.onPageStarted(view, url, favicon)
|
|
||||||
handler.removeCallbacksAndMessages(null)
|
|
||||||
runOnUiThread {
|
|
||||||
loadingOverlay.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
|
||||||
super.onPageFinished(view, url)
|
|
||||||
android.util.Log.i("PlaybackActivity", "Page finished: $url")
|
|
||||||
|
|
||||||
handler.postDelayed({
|
|
||||||
injectFullscreenScript()
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
handler.postDelayed({
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadingOverlay.visibility = View.GONE
|
loadingOverlay.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}, 1500)
|
}
|
||||||
}
|
}, "Android")
|
||||||
}
|
|
||||||
|
|
||||||
// Use hardware layer only when needed, not always
|
val cookieManager = CookieManager.getInstance()
|
||||||
webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
cookieManager.setAcceptCookie(true)
|
||||||
}
|
cookieManager.setAcceptThirdPartyCookies(webView, true)
|
||||||
|
|
||||||
|
webView.webChromeClient = object : WebChromeClient() {
|
||||||
|
override fun onConsoleMessage(cm: ConsoleMessage?): Boolean {
|
||||||
|
val message = cm?.message() ?: return false
|
||||||
|
if (message.contains("player") || message.contains("video") ||
|
||||||
|
message.contains(".m3u8") || message.contains(".mp4")) {
|
||||||
|
android.util.Log.i("WebViewConsole", "Player info: $message")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPermissionRequest(request: android.webkit.PermissionRequest?) {
|
||||||
|
request?.grant(request.resources)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.webViewClient = object : WebViewClient() {
|
||||||
|
|
||||||
|
// [FIX 3] shouldInterceptRequest 메모리 누수 수정.
|
||||||
|
// 기존 코드는 요청마다 HttpURLConnection을 열어 전체 응답을 String으로 로드하고
|
||||||
|
// disconnect()를 호출하지 않아 연결과 메모리가 누적되었음.
|
||||||
|
// CSS/JS 인터셉트는 실익이 없으므로 제거하고 null 반환(WebView 기본 처리)으로 대체.
|
||||||
|
// HTML 스타일 주입은 onPageFinished의 JS 인젝션으로 충분히 커버됨.
|
||||||
|
override fun shouldInterceptRequest(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): WebResourceResponse? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
|
||||||
|
super.onPageStarted(view, url, favicon)
|
||||||
|
// [FIX 4] 페이지 시작 시 이전 hideLoading 콜백만 제거 (다른 콜백은 유지)
|
||||||
|
handler.removeCallbacks(hideLoadingRunnable)
|
||||||
|
loadingOverlay.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
android.util.Log.i("PlaybackActivity", "Page finished: $url")
|
||||||
|
|
||||||
|
// [FIX 5] injectFullscreenScript는 한 번만, hideLoading은 명시적 Runnable로 관리
|
||||||
|
handler.postDelayed({ injectFullscreenScript() }, 300)
|
||||||
|
handler.removeCallbacks(hideLoadingRunnable)
|
||||||
|
handler.postDelayed(hideLoadingRunnable, 1500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
||||||
|
}
|
||||||
|
|
||||||
private fun injectFullscreenStyles(html: String): String {
|
private fun injectFullscreenStyles(html: String): String {
|
||||||
val styleInjection = """
|
val styleInjection = """
|
||||||
@@ -422,8 +335,8 @@ webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
makePlayerFullscreen();
|
makePlayerFullscreen();
|
||||||
})();
|
})();
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
@@ -433,13 +346,12 @@ webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
|||||||
val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event)
|
val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event)
|
||||||
val action = event.action
|
val action = event.action
|
||||||
|
|
||||||
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
|
if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)
|
||||||
&& action == KeyEvent.ACTION_DOWN) {
|
&& action == KeyEvent.ACTION_DOWN) {
|
||||||
android.util.Log.d("PlaybackActivity", "OK button pressed")
|
simulateCenterClick()
|
||||||
simulateCenterClick()
|
toggleVideoPlayback()
|
||||||
toggleVideoPlayback()
|
return true
|
||||||
return true
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK && action == KeyEvent.ACTION_DOWN) {
|
if (keyCode == KeyEvent.KEYCODE_BACK && action == KeyEvent.ACTION_DOWN) {
|
||||||
if (webView.canGoBack()) {
|
if (webView.canGoBack()) {
|
||||||
@@ -453,16 +365,6 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDpadKey(keyCode: Int) {
|
|
||||||
android.util.Log.d("PlaybackActivity", "handleDpadKey: $keyCode")
|
|
||||||
when (keyCode) {
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> seekVideo(-10000)
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> seekVideo(10000)
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP -> adjustVolume(0.1f)
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN -> adjustVolume(-0.1f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun adjustVolume(delta: Float) {
|
private fun adjustVolume(delta: Float) {
|
||||||
webView.evaluateJavascript(
|
webView.evaluateJavascript(
|
||||||
"""
|
"""
|
||||||
@@ -485,15 +387,9 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
(function() {
|
(function() {
|
||||||
var video = document.querySelector('video');
|
var video = document.querySelector('video');
|
||||||
if (video && video.duration > 0) {
|
if (video && video.duration > 0) {
|
||||||
if (video.paused) {
|
if (video.paused) { video.play(); return 'played'; }
|
||||||
video.play();
|
else { video.pause(); return 'paused'; }
|
||||||
return 'played';
|
|
||||||
} else {
|
|
||||||
video.pause();
|
|
||||||
return 'paused';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var iframes = document.querySelectorAll('iframe');
|
var iframes = document.querySelectorAll('iframe');
|
||||||
for (var i = 0; i < iframes.length; i++) {
|
for (var i = 0; i < iframes.length; i++) {
|
||||||
try {
|
try {
|
||||||
@@ -501,12 +397,10 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
iframes[i].contentWindow.postMessage('toggle', '*');
|
iframes[i].contentWindow.postMessage('toggle', '*');
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
var focused = document.activeElement;
|
var focused = document.activeElement;
|
||||||
if (focused && focused !== document.body) {
|
if (focused && focused !== document.body) {
|
||||||
try { focused.click(); } catch(e) {}
|
try { focused.click(); } catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'sent';
|
return 'sent';
|
||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@@ -525,14 +419,12 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
video.currentTime = Math.max(0, Math.min(video.duration, video.currentTime + $offsetSec));
|
video.currentTime = Math.max(0, Math.min(video.duration, video.currentTime + $offsetSec));
|
||||||
return 'seeked';
|
return 'seeked';
|
||||||
}
|
}
|
||||||
|
|
||||||
var iframes = document.querySelectorAll('iframe');
|
var iframes = document.querySelectorAll('iframe');
|
||||||
for (var i = 0; i < iframes.length; i++) {
|
for (var i = 0; i < iframes.length; i++) {
|
||||||
try {
|
try {
|
||||||
iframes[i].contentWindow.postMessage({ type: 'seek', offset: $offsetSec }, '*');
|
iframes[i].contentWindow.postMessage({ type: 'seek', offset: $offsetSec }, '*');
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'sent';
|
return 'sent';
|
||||||
})();
|
})();
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
@@ -541,28 +433,18 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun simulateCenterClick() {
|
private fun simulateCenterClick() {
|
||||||
runOnUiThread {
|
val centerX = webView.width / 2f
|
||||||
val centerX = webView.width / 2f
|
val centerY = webView.height / 2f
|
||||||
val centerY = webView.height / 2f
|
val downTime = SystemClock.uptimeMillis()
|
||||||
val downTime = SystemClock.uptimeMillis()
|
|
||||||
|
|
||||||
val downEvent = MotionEvent.obtain(
|
val downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, centerX, centerY, 0)
|
||||||
downTime, downTime,
|
val upEvent = MotionEvent.obtain(downTime, downTime + 100, MotionEvent.ACTION_UP, centerX, centerY, 0)
|
||||||
MotionEvent.ACTION_DOWN, centerX, centerY, 0
|
|
||||||
)
|
|
||||||
val upEvent = MotionEvent.obtain(
|
|
||||||
downTime, downTime + 100,
|
|
||||||
MotionEvent.ACTION_UP, centerX, centerY, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
webView.dispatchTouchEvent(downEvent)
|
webView.dispatchTouchEvent(downEvent)
|
||||||
webView.dispatchTouchEvent(upEvent)
|
webView.dispatchTouchEvent(upEvent)
|
||||||
|
|
||||||
downEvent.recycle()
|
downEvent.recycle()
|
||||||
upEvent.recycle()
|
upEvent.recycle()
|
||||||
|
|
||||||
android.util.Log.d("PlaybackActivity", "Center click simulated at ($centerX, $centerY)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
@@ -590,10 +472,15 @@ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTE
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
// [FIX 6] handler 콜백 전부 제거 후 WebView 정리. loadingOverlay 참조 해제.
|
||||||
handler.removeCallbacksAndMessages(null)
|
handler.removeCallbacksAndMessages(null)
|
||||||
if (::webView.isInitialized) {
|
if (::webView.isInitialized) {
|
||||||
|
webView.stopLoading()
|
||||||
|
webView.clearHistory()
|
||||||
|
webView.webViewClient = WebViewClient() // 기존 client 참조 해제
|
||||||
|
webView.webChromeClient = null
|
||||||
webView.destroy()
|
webView.destroy()
|
||||||
}
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user