버그 수정 및 최적화

This commit is contained in:
tvmon-dev
2026-04-16 19:55:32 +09:00
parent 626517c9ef
commit f864ea6d1c
5 changed files with 148 additions and 196 deletions

View File

@@ -16,7 +16,11 @@ import com.example.tvmon.data.scraper.TvmonScraper
import com.example.tvmon.ui.detail.DetailsActivity import com.example.tvmon.ui.detail.DetailsActivity
import com.example.tvmon.ui.presenter.ContentCardPresenter import com.example.tvmon.ui.presenter.ContentCardPresenter
import com.example.tvmon.ui.settings.SettingsActivity import com.example.tvmon.ui.settings.SettingsActivity
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemViewSelectedListener { class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemViewSelectedListener {
@@ -96,23 +100,67 @@ private fun setupUI() {
var successCount = 0 var successCount = 0
var failCount = 0 var failCount = 0
for (category in TvmonScraper.CATEGORIES.values) { val deferredRows = coroutineScope {
Log.w(TAG, "Loading category: ${category.key} - ${category.name}") TvmonScraper.CATEGORIES.values.map { category ->
val success = loadCategoryWithCache(category) Log.w(TAG, "Loading category: ${category.key} - ${category.name}")
if (success) successCount++ else failCount++ async {
val row = loadCategoryRow(category)
category.key to row
}
}
} }
Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===") for (deferred in deferredRows) {
isDataLoaded = true val (key, row) = deferred.await()
if (row != null) {
activity?.runOnUiThread {
rowsAdapter.add(row)
}
successCount++
} else {
failCount++
}
}
if (rowsAdapter.size() == 0) { Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===")
Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show() isDataLoaded = true
}
activity?.runOnUiThread { if (rowsAdapter.size() == 0) {
addSettingsRow() Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show()
} }
}
activity?.runOnUiThread {
addSettingsRow()
}
}
private suspend fun loadCategoryRow(category: com.example.tvmon.data.model.Category): ListRow? {
return try {
Log.w(TAG, "loadCategoryRow: ${category.key}")
val result = categoryCacheRepository.getCategoryWithCache(category.key, page = 1)
if (result.success && result.items.isNotEmpty()) {
val listRowAdapter = ArrayObjectAdapter(ContentCardPresenter())
result.items.forEach { content ->
listRowAdapter.add(content)
}
val header = HeaderItem(category.name)
val row = ListRow(header, listRowAdapter)
categoryRowAdapters[category.key] = listRowAdapter
Log.w(TAG, "Prepared ROW: ${category.name} with ${result.items.size} items (fromCache=${result.fromCache})")
row
} else {
Log.w(TAG, "No items for ${category.key}")
null
}
} catch (e: Exception) {
Log.e(TAG, "ERROR loading ${category.key}: ${e.javaClass.simpleName}: ${e.message}", e)
null
}
}
private fun addSettingsRow() { private fun addSettingsRow() {
val settingsAdapter = ArrayObjectAdapter(ContentCardPresenter()) val settingsAdapter = ArrayObjectAdapter(ContentCardPresenter())
@@ -186,11 +234,17 @@ Log.w(TAG, "Unknown item type: ${item?.javaClass?.simpleName}")
rowViewHolder: RowPresenter.ViewHolder?, rowViewHolder: RowPresenter.ViewHolder?,
row: Row? row: Row?
) { ) {
// Early return for null items
if (item == null) return
// Early return for non-Content items (e.g., SettingsItem)
if (item !is Content) return
if (row is ListRow) { if (row is ListRow) {
val rowIndex = rowsAdapter.indexOf(row) val rowIndex = rowsAdapter.indexOf(row)
if (rowIndex >= 0) { if (rowIndex >= 0) {
val categoryKey = findCategoryKeyForRow(rowIndex) val categoryKey = findCategoryKeyForRow(rowIndex)
if (categoryKey != null && item is Content) { if (categoryKey != null) {
checkAndLoadMore(categoryKey) checkAndLoadMore(categoryKey)
} }
} }

View File

@@ -177,44 +177,12 @@ webSettings.mediaPlaybackRequiresUserGesture = false
webSettings.blockNetworkImage = false webSettings.blockNetworkImage = false
webView.addJavascriptInterface(object { webView.addJavascriptInterface(object {
@android.webkit.JavascriptInterface @android.webkit.JavascriptInterface
fun hideLoadingOverlay() { fun hideLoadingOverlay() {
runOnUiThread { runOnUiThread {
loadingOverlay.visibility = View.GONE loadingOverlay.visibility = View.GONE
} }
} }
@android.webkit.JavascriptInterface
fun isVideoPlaying() {
webView.evaluateJavascript(
"""
(function() {
var videos = document.querySelectorAll('video');
var playing = false;
videos.forEach(function(v) {
if (!v.paused && v.currentTime > 0) {
playing = true;
}
});
return playing ? 'playing' : 'not_playing';
})();
""".trimIndent()
) { result ->
android.util.Log.i("PlaybackActivity", "Video playing status: $result")
if (result == "\"playing\"") {
runOnUiThread {
loadingOverlay.visibility = View.GONE
}
}
}
}
@android.webkit.JavascriptInterface
fun onVideoPlay() {
runOnUiThread {
loadingOverlay.visibility = View.GONE
android.util.Log.d("PlaybackActivity", "Video started playing")
}
}
}, "Android") }, "Android")
val cookieManager = CookieManager.getInstance() val cookieManager = CookieManager.getInstance()
@@ -277,30 +245,29 @@ android.util.Log.w("PlaybackActivity", "Intercept failed for $url: ${e.message}"
return super.shouldInterceptRequest(view, request) return super.shouldInterceptRequest(view, request)
} }
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) { override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
super.onPageStarted(view, url, favicon) super.onPageStarted(view, url, favicon)
runOnUiThread { handler.removeCallbacksAndMessages(null)
loadingOverlay.visibility = View.VISIBLE runOnUiThread {
} loadingOverlay.visibility = View.VISIBLE
} }
}
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
android.util.Log.i("PlaybackActivity", "Page finished: $url") android.util.Log.i("PlaybackActivity", "Page finished: $url")
// Reduced delays and calls to minimize memory pressure handler.postDelayed({
handler.postDelayed({ injectFullscreenScript()
injectFullscreenScript() }, 300)
injectEnhancedAutoPlayScript()
}, 300)
handler.postDelayed({ handler.postDelayed({
runOnUiThread { runOnUiThread {
loadingOverlay.visibility = View.GONE loadingOverlay.visibility = View.GONE
} }
}, 1500) }, 1500)
} }
} }
// Use hardware layer only when needed, not always // Use hardware layer only when needed, not always
webView.setLayerType(View.LAYER_TYPE_NONE, null) webView.setLayerType(View.LAYER_TYPE_NONE, null)
@@ -455,73 +422,8 @@ webView.setLayerType(View.LAYER_TYPE_NONE, null)
}); });
}; };
makePlayerFullscreen(); makePlayerFullscreen();
setTimeout(makePlayerFullscreen, 500); })();
setTimeout(makePlayerFullscreen, 2000);
})();
""".trimIndent(),
null
)
}
private fun injectEnhancedAutoPlayScript() {
webView.evaluateJavascript(
"""
(function() {
console.log('AutoPlay: Starting...');
var tryPlay = function(v) {
console.log('AutoPlay: Trying to play video...');
v.muted = true;
v.setAttribute('playsinline', 'true');
v.play().then(function() {
console.log('AutoPlay: Video play() succeeded');
if (window.Android) {
window.Android.onVideoPlay();
}
}).catch(function(e) {
console.log('AutoPlay: Video play() failed: ' + e.message);
});
};
var initVideo = function(v) {
v.muted = true;
v.setAttribute('playsinline', 'true');
v.setAttribute('webkit-playsinline', 'true');
v.addEventListener('canplaythrough', function() {
console.log('AutoPlay: canplaythrough event fired');
tryPlay(v);
}, {once: true});
v.addEventListener('playing', function() {
console.log('AutoPlay: Video is now playing');
if (window.Android) {
window.Android.onVideoPlay();
}
}, {once: true});
if (v.readyState >= 2) {
tryPlay(v);
}
};
var videos = document.querySelectorAll('video');
console.log('AutoPlay: Found ' + videos.length + ' videos');
videos.forEach(initVideo);
var iframes = document.querySelectorAll('iframe');
iframes.forEach(function(f) {
try {
var innerDoc = f.contentDocument || f.contentWindow.document;
var innerVideos = innerDoc.querySelectorAll('video');
innerVideos.forEach(initVideo);
} catch(e) {
console.log('AutoPlay: Cannot access iframe');
}
});
})();
""".trimIndent(), """.trimIndent(),
null null
) )
@@ -531,14 +433,13 @@ setTimeout(makePlayerFullscreen, 2000);
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, triggering enhanced autoplay") android.util.Log.d("PlaybackActivity", "OK button pressed")
simulateCenterClick() simulateCenterClick()
injectEnhancedAutoPlayScript() 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()) {

View File

@@ -17,8 +17,8 @@ data class VersionInfo(
object UpdateChecker { object UpdateChecker {
private const val TAG = "UpdateChecker" private const val TAG = "UpdateChecker"
private const val VERSION_JSON_URL = "https://tvmon.site/version.json" private const val VERSION_JSON_URL = "https://git.webpluss.net/sanjeok77/tvmon_release/releases/download/version/version.json"
private const val APK_BASE_URL = "https://tvmon.site/releases" private const val APK_BASE_URL = "https://git.webpluss.net/sanjeok77/tvmon_release/releases/download"
fun getCurrentVersionCode(context: Context): Int { fun getCurrentVersionCode(context: Context): Int {
return try { return try {

View File

@@ -1,47 +1,40 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<!-- TV Screen with white color --> <!-- TV Screen outline - white stroke style for visibility -->
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M34,38h40v28h-40z"/> android:pathData="M26,26h56v40h-56z" />
<!-- TV Stand --> <!-- Inner screen area (transparent to show background) -->
<path <path
android:fillColor="#FFFFFF" android:fillColor="#00000000"
android:pathData="M44,70h20v4h-20z"/> android:strokeColor="#E50914"
<path android:strokeWidth="2"
android:fillColor="#FFFFFF" android:pathData="M30,30h48v32h-48z" />
android:pathData="M40,74h28v2h-28z"/>
<!-- Play Button - using Netflix red --> <!-- Play Button - white triangle centered in screen -->
<path <path
android:fillColor="#E50914" android:fillColor="#FFFFFF"
android:pathData="M50,44l12,8l-12,8z"/> android:pathData="M46,38 L46,56 L62,47 Z" />
<!-- tvmon Text - white --> <!-- TV Stand - left leg -->
<!-- Letter 't' --> <path
<path android:fillColor="#FFFFFF"
android:fillColor="#FFFFFF" android:pathData="M38,66 L42,66 L42,76 L38,76 Z" />
android:pathData="M38,82h4v-2h-4v-2h6v10h-2v-6h-4z"/>
<!-- Letter 'v' --> <!-- TV Stand - right leg -->
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FFFFFF"
android:pathData="M45,78h2l2,6l2,-6h2l-3,8h-2z"/> android:pathData="M66,66 L70,66 L70,76 L70,76 Z" />
<!-- Letter 'm' -->
<path <!-- TV Stand base -->
android:fillColor="#FFFFFF" <path
android:pathData="M54,86v-8h2v1c0.5,-0.7 1.2,-1 2,-1c0.9,0 1.6,0.4 2,1.1c0.5,-0.7 1.3,-1.1 2.2,-1.1c1.6,0 2.8,1.2 2.8,2.8v5.2h-2v-5c0,-0.9 -0.7,-1.5 -1.5,-1.5c-0.9,0 -1.5,0.6 -1.5,1.5v5h-2v-5c0,-0.9 -0.7,-1.5 -1.5,-1.5c-0.9,0 -1.5,0.6 -1.5,1.5v5z"/> android:fillColor="#FFFFFF"
<!-- Letter 'o' --> android:pathData="M34,76 L74,76 L74,80 L34,80 Z" />
<path
android:fillColor="#FFFFFF" </vector>
android:pathData="M66,82c0,-2.2 1.8,-4 4,-4s4,1.8 4,4s-1.8,4 -4,4s-4,-1.8 -4,-4zm2,0c0,1.1 0.9,2 2,2s2,-0.9 2,-2s-0.9,-2 -2,-2s-2,0.9 -2,2z"/>
<!-- Letter 'n' -->
<path
android:fillColor="#FFFFFF"
android:pathData="M76,86v-8h2v1c0.6,-0.7 1.4,-1 2.3,-1c1.9,0 3.2,1.3 3.2,3.2v4.8h-2v-4.5c0,-1 -0.7,-1.7 -1.7,-1.7c-1,0 -1.8,0.7 -1.8,1.7v4.5z"/>
</vector>

View File

@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<base-config cleartextTrafficPermitted="true"> <base-config cleartextTrafficPermitted="true">
<trust-anchors> <trust-anchors>
<certificates src="system" /> <certificates src="system" />
</trust-anchors> </trust-anchors>
</base-config> </base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">tvmon.site</domain>
<domain includeSubdomains="true">git.webpluss.net</domain>
</domain-config>
</network-security-config> </network-security-config>