...
This commit is contained in:
parent
fd0ee96c04
commit
ac1c0b12b3
@ -13,6 +13,10 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@ -46,6 +50,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import bums.lunatic.launcher.LunaticLauncher.Companion.isWifiConnected
|
||||
import bums.lunatic.launcher.apps.AppDrawerBottomSheet
|
||||
import bums.lunatic.launcher.common.CommonActivity
|
||||
import bums.lunatic.launcher.databinding.LauncherActivityBinding
|
||||
@ -64,6 +69,7 @@ import bums.lunatic.launcher.receiver.NLService
|
||||
import bums.lunatic.launcher.receiver.SmsReceiver
|
||||
import bums.lunatic.launcher.settings.SettingsActivity
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.BaseGetter
|
||||
import bums.lunatic.launcher.workers.TorrentService
|
||||
import bums.lunatic.launcher.workers.UsageLogType
|
||||
import bums.lunatic.launcher.workers.UsageUpdateType
|
||||
@ -580,8 +586,54 @@ open class LauncherActivity : CommonActivity() {
|
||||
))
|
||||
|
||||
handleSharedIntent(intent)
|
||||
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetwork = connectivityManager.activeNetwork
|
||||
val caps = connectivityManager.getNetworkCapabilities(activeNetwork)
|
||||
LunaticLauncher.isWifiConnected = caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
registerNetworkCallback()
|
||||
}
|
||||
private fun registerNetworkCallback() {
|
||||
val request = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build()
|
||||
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||
// 단순히 caps만 보지 말고, 현재 활성화된 기본 네트워크의 상태를 직접 다시 조회합니다.
|
||||
val activeNet = connectivityManager.activeNetwork
|
||||
val activeCaps = connectivityManager.getNetworkCapabilities(activeNet)
|
||||
|
||||
val wifiNow = activeCaps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
|
||||
// 상태가 실제로 변했을 때만 업데이트하여 불필요한 로그와 로직 실행을 방지합니다.
|
||||
if (isWifiConnected != wifiNow) {
|
||||
isWifiConnected = wifiNow
|
||||
|
||||
}
|
||||
val intent = Intent(this@LauncherActivity, TorrentService::class.java).apply {
|
||||
putExtra("WIFI_STATE", isWifiConnected)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent)
|
||||
} else {
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
// 네트워크가 완전히 끊겼을 때도 처리해주는 것이 안전합니다.
|
||||
override fun onLost(network: Network) {
|
||||
val intent = Intent(this@LauncherActivity, TorrentService::class.java).apply {
|
||||
putExtra("WIFI_STATE", false)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(intent)
|
||||
} else {
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
||||
}
|
||||
private var smsReceiver: SmsReceiver? = null
|
||||
|
||||
// 권한 요청 결과 처리기
|
||||
@ -911,12 +963,15 @@ open class LauncherActivity : CommonActivity() {
|
||||
appWidgetHost?.stopListening() // [필수] 여기서 리스닝 중지 (onDestroy 대신 여기 추천)
|
||||
|
||||
}
|
||||
|
||||
private lateinit var connectivityManager: ConnectivityManager
|
||||
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
||||
override fun onDestroy() {
|
||||
smsReceiver?.let {
|
||||
unregisterReceiver(it)
|
||||
smsReceiver = null
|
||||
}
|
||||
|
||||
networkCallback?.let { connectivityManager.unregisterNetworkCallback(it) }
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ internal class LunaticLauncher : Application() {
|
||||
companion object {
|
||||
var appContext : LunaticLauncher? = null
|
||||
var mHourlyLogWriter : HourlyLogWriter? = null
|
||||
var isWifiConnected : Boolean = false
|
||||
private var sRuntime: GeckoRuntime? = null
|
||||
fun getRuntime() : GeckoRuntime? {
|
||||
appContext?.initGeckoRuntime()
|
||||
|
||||
@ -77,6 +77,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.util.Calendar
|
||||
import kotlin.math.abs
|
||||
|
||||
data class ZenQuoteResponse(val q: String, val a: String)
|
||||
data class KorAdviceResponse(val author: String, val message: String)
|
||||
@ -515,6 +516,7 @@ class ForeGroundService : Service() {
|
||||
}
|
||||
|
||||
fun addToTargetYtubeUrl(url : String? = null, forMusic: Boolean = false) {
|
||||
Blog.LOGE("url $url")
|
||||
url?.let { url ->
|
||||
if (url.length > 0) {
|
||||
targetUrls.add(url to forMusic)
|
||||
@ -537,6 +539,7 @@ class ForeGroundService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
var lastProGress = -1f
|
||||
fun downloadVideo(url: String?, forMusic: Boolean = false) {
|
||||
url?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
@ -547,35 +550,42 @@ class ForeGroundService : Service() {
|
||||
val youtubeDLDir = File(baseDir, "Youtube")
|
||||
if (!youtubeDLDir.exists()) youtubeDLDir.mkdirs()
|
||||
val command = YoutubeDLRequest(url).apply {
|
||||
addOption("-q") // 로그 최소화
|
||||
addOption("--newline") // 줄바꿈 단위로 출력 (콜백 트리거 핵심)
|
||||
addOption("--progress") // 진행 상태 강제 출력 // 로그 최소화
|
||||
addOption("--no-warnings") // 경고 숨김
|
||||
|
||||
val cookieFile = File(this@ForeGroundService.cacheDir, "cookies.txt")
|
||||
addOption("--cookies", "${cookieFile.absolutePath}")
|
||||
|
||||
// 출력 경로
|
||||
addOption("-o", "${youtubeDLDir.absolutePath}/%(title)s.%(ext)s")
|
||||
addOption("-o", "${youtubeDLDir.absolutePath}/%(title)s [%(id)s].%(ext)s")
|
||||
|
||||
if (forMusic) {
|
||||
// 음악 전용 옵션
|
||||
addOption("-x") // 오디오 추출
|
||||
addOption("--audio-format", "mp3")
|
||||
addOption("--extractor-args", "youtube:player_client=web_music,android")
|
||||
addOption("-f", "bestaudio[ext=m4a]/bestaudio/best")
|
||||
} else {
|
||||
// if (forMusic) {
|
||||
// // 음악 전용 옵션
|
||||
// addOption("-x") // 오디오 추출
|
||||
// addOption("--audio-format", "mp3")
|
||||
// addOption("--extractor-args", "youtube:player_client=web_music,android")
|
||||
// addOption("-f", "bestaudio[ext=m4a]/bestaudio/best")
|
||||
// } else {
|
||||
// 일반 영상용
|
||||
addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best")
|
||||
}
|
||||
|
||||
// 안정성 향상 옵션 (모두에 적용)
|
||||
// }
|
||||
addOption("--no-check-certificates")
|
||||
addOption("--verbose")
|
||||
addOption("--no-mtime") // 파일 수정시간 안 건드림
|
||||
addOption("--no-continue") // 이어받기 하지 않음
|
||||
addOption("--force-overwrites") // 강제 덮어쓰기
|
||||
addOption("--restrict-filenames") // 특수문자 제한
|
||||
}
|
||||
|
||||
currentProcessId = UUID.randomUUID().toString()
|
||||
YoutubeDL.getInstance()
|
||||
var response = YoutubeDL.getInstance()
|
||||
.execute(command, currentProcessId) { progress, est, str ->
|
||||
startForeGround(100, progress.toInt(), str, false)
|
||||
// Blog.LOGE("progress $progress est $est str $str" )
|
||||
if(progress == 100.0f || progress == 0.0f || abs(lastProGress - progress) > 3) {
|
||||
startForeGround(100, progress.toInt(), str, false)
|
||||
lastProGress = progress
|
||||
}
|
||||
if (progress >= 100) {
|
||||
currentProcessId = null
|
||||
if ((targetUrls?.size ?: 0) > 0) {
|
||||
@ -583,6 +593,10 @@ class ForeGroundService : Service() {
|
||||
}
|
||||
}
|
||||
}
|
||||
Blog.LOGE("url $url $currentProcessId")
|
||||
Blog.LOGE("Exit Code: ${response.exitCode}")
|
||||
Blog.LOGE("Out: ${response.out}")
|
||||
Blog.LOGE("Error: ${response.err}")
|
||||
} catch (e: Exception) {
|
||||
Blog.LOGE("Download Error", e)
|
||||
currentProcessId = null
|
||||
@ -710,7 +724,7 @@ class ForeGroundService : Service() {
|
||||
.build()
|
||||
|
||||
// 2. 뉴스 피드: 사용자가 설정한 간격 (예: 1시간)
|
||||
val newsRequest = PeriodicWorkRequestBuilder<AggregatedNewsWorker>(PrefLong.shortTimePeriod.get(20L), TimeUnit.MINUTES)
|
||||
val newsRequest = PeriodicWorkRequestBuilder<AggregatedNewsWorker>(PrefLong.longTimePeriod.get(120L), TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
// 기존의 수많은 enqueue 코드를 이 두 개로 대체
|
||||
|
||||
@ -891,12 +891,9 @@ class CompletedFilesFragment : Fragment() {
|
||||
val innerFiles = folder.listFiles() ?: return@forEach
|
||||
|
||||
// 특수 조건(1GB 영상) 확인용 데이터
|
||||
val videoFiles = innerFiles.filter { extVideos.contains(it.extension.lowercase()) }
|
||||
val videoFiles = innerFiles.filter { extVideos.contains(it.extension.lowercase()) }.filter { it.length() >= 1024 * 1024 * 1024 }
|
||||
val potentialSubtitles = innerFiles.filter { subtitleExts.contains(it.extension.lowercase()) }
|
||||
val hasLargeVideo = videoFiles.any { it.length() >= 1024 * 1024 * 1024 }
|
||||
val hasTinyText = potentialSubtitles.any { it.length() <= 1024 }
|
||||
|
||||
if (hasLargeVideo) {
|
||||
if (videoFiles.isNotEmpty()) {
|
||||
// 조건 만족 시 영상+자막 이동
|
||||
videoFiles.forEach { videoFile ->
|
||||
if (videoFile.renameTo(File(videoTargetDir, videoFile.name))) {
|
||||
|
||||
@ -282,17 +282,17 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
val cookieFile = File(context.cacheDir, "cookies.txt")
|
||||
addOption("--cookies", "${cookieFile.absolutePath}")
|
||||
|
||||
if (forMusic) {
|
||||
addOption("-x") // 오디오 추출
|
||||
addOption("--audio-format", "mp3")
|
||||
// YouTube Music 전용 extractor arg 추가
|
||||
addOption("--extractor-args", "youtube:player_client=web_music,android")
|
||||
// 음악 스트림 우선 선택
|
||||
addOption("-f", "bestaudio[ext=m4a]/bestaudio/best")
|
||||
} else {
|
||||
// if (forMusic) {
|
||||
// addOption("-x") // 오디오 추출
|
||||
// addOption("--audio-format", "mp3")
|
||||
// // YouTube Music 전용 extractor arg 추가
|
||||
// addOption("--extractor-args", "youtube:player_client=web_music,android")
|
||||
// // 음악 스트림 우선 선택
|
||||
// addOption("-f", "bestaudio[ext=m4a]/bestaudio/best")
|
||||
// } else {
|
||||
// 일반 영상용 기본 포맷
|
||||
addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best")
|
||||
}
|
||||
// }
|
||||
|
||||
|
||||
// 디버깅용 (테스트 후 제거)
|
||||
@ -476,6 +476,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
override fun onSessionStateChange(session: GeckoSession, sessionState: GeckoSession.SessionState) {
|
||||
lastSessionState = sessionState
|
||||
onSessionStateChangeCallback?.invoke(sessionState)
|
||||
saveCurrentSessionState()
|
||||
// Blog.LOGE("onSessionStateChange $sessionState ${session}")
|
||||
}
|
||||
}
|
||||
@ -661,7 +662,7 @@ open class GeckoWeb @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
"COOKIES_REPORT"-> {
|
||||
// Blog.LOGE("${msg.value} -> ${msg.url}")
|
||||
Blog.LOGE("${msg.value} -> ${msg.url}")
|
||||
currentCookieString = msg.value ?: ""
|
||||
currentCookieUrlString = msg.url ?: ""
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
@ -496,6 +496,7 @@ open class NeoRssActivity : CommonActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
YoutubeDL.getInstance().init(this)
|
||||
|
||||
FFmpeg.getInstance().init(this)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
|
||||
@ -149,8 +149,11 @@ class PlayerActivity : AppCompatActivity(), TextureView.SurfaceTextureListener {
|
||||
if (allSubtitleTracks.size > 1) {
|
||||
showSubtitleSelectionDialog()
|
||||
} else {
|
||||
|
||||
showSubtitleSearchConfirmDialog()
|
||||
if (videoPath.contains("Youtube")) {
|
||||
play()
|
||||
} else {
|
||||
showSubtitleSearchConfirmDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,10 +371,28 @@ class PlayerActivity : AppCompatActivity(), TextureView.SurfaceTextureListener {
|
||||
val center = gestureLayer.getChildAt(1)
|
||||
val right = gestureLayer.getChildAt(2)
|
||||
|
||||
center.setOnClickListener {
|
||||
isPlaying = !isPlaying
|
||||
if (isPlaying) nativePlayer?.play(Surface(videoTextureView.surfaceTexture!!))
|
||||
else nativePlayer?.pause()
|
||||
val centerDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
isPlaying = !isPlaying
|
||||
if (isPlaying) nativePlayer?.play(Surface(videoTextureView.surfaceTexture!!))
|
||||
else nativePlayer?.pause()
|
||||
return true
|
||||
}
|
||||
|
||||
// 좌우 스크롤로 자막 싱크 조절
|
||||
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
||||
if (Math.abs(distanceX) > Math.abs(distanceY)) {
|
||||
if (distanceX > 0) adjustSubtitleSync(-500) // 왼쪽으로 밀면 자막을 빠르게
|
||||
else adjustSubtitleSync(500) // 오른쪽으로 밀면 자막을 느리게
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
center.setOnTouchListener { _, event ->
|
||||
centerDetector.onTouchEvent(event)
|
||||
true
|
||||
}
|
||||
|
||||
val rightDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() {
|
||||
@ -411,7 +432,17 @@ class PlayerActivity : AppCompatActivity(), TextureView.SurfaceTextureListener {
|
||||
true
|
||||
}
|
||||
}
|
||||
private var subtitleDelayMs: Long = 0L
|
||||
private fun adjustSubtitleSync(deltaMs: Long) {
|
||||
subtitleDelayMs += deltaMs
|
||||
val seconds = subtitleDelayMs / 1000.0
|
||||
val sign = if (subtitleDelayMs >= 0) "+" else ""
|
||||
Toast.makeText(this, "자막 싱크: $sign${String.format("%.1f", seconds)}초", Toast.LENGTH_SHORT).show()
|
||||
|
||||
// 싱크가 변경되면 즉시 루프에서 반영되므로 별도의 처리는 필요 없으나,
|
||||
// 즉각적인 피드백을 위해 lastSubTitle을 초기화하여 강제 갱신 유도 가능
|
||||
lastSubTitle = ""
|
||||
}
|
||||
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
hideSystemUI()
|
||||
@ -640,23 +671,21 @@ class PlayerActivity : AppCompatActivity(), TextureView.SurfaceTextureListener {
|
||||
* 💡 인덱스 기반 탐색: 이전 위치부터 찾기 때문에 CPU 부하가 거의 없습니다.
|
||||
*/
|
||||
private fun findSubtitleIndexed(currentSec: Double): SubtitleBlock? {
|
||||
// 1. 영상이 뒤로 감기 되었을 경우 인덱스 초기화
|
||||
// 💡 싱크 오프셋 적용 (초 단위로 변환하여 더함)
|
||||
val adjustedSec = currentSec - (subtitleDelayMs / 1000.0)
|
||||
|
||||
if (currentSubtitleIndex >= externalSubtitles.size ||
|
||||
externalSubtitles[currentSubtitleIndex].startSec > currentSec) {
|
||||
externalSubtitles[currentSubtitleIndex].startSec > adjustedSec) {
|
||||
currentSubtitleIndex = 0
|
||||
}
|
||||
|
||||
// 2. 마지막으로 찾았던 위치(currentSubtitleIndex)부터 탐색 시작
|
||||
for (i in currentSubtitleIndex until externalSubtitles.size) {
|
||||
val item = externalSubtitles[i]
|
||||
|
||||
if (currentSec in item.startSec..item.endSec) {
|
||||
currentSubtitleIndex = i // 현재 위치 저장
|
||||
if (adjustedSec in item.startSec..item.endSec) {
|
||||
currentSubtitleIndex = i
|
||||
return item
|
||||
}
|
||||
|
||||
// 3. 자막이 시간순으로 정렬되어 있다면, 현재 시간보다 시작 시간이 커지는 순간 루프 종료
|
||||
if (item.startSec > currentSec) break
|
||||
if (item.startSec > adjustedSec) break
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@ -24,6 +24,8 @@ abstract class BaseGetter(internal val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
val USAGT = "Mozilla/5.0 (Android 15; Mobile; rv:141.0) Gecko/141.0 Firefox/141.0"
|
||||
|
||||
val limitDateTime = beforeOneDay()
|
||||
@ -32,6 +34,7 @@ abstract class BaseGetter(internal val context: Context) {
|
||||
|
||||
abstract fun realWork() : List<RealmObject>
|
||||
open suspend fun fetchData(): List<RealmObject> {
|
||||
|
||||
return realWork()
|
||||
}
|
||||
}
|
||||
@ -93,11 +93,8 @@ object TaskAggregator {
|
||||
|
||||
// 병렬로 네트워크 요청 쏘기 (시간 획기적으로 단축됨)
|
||||
val jobs = listOf(
|
||||
// async { RuliWebGetter(context).fetchData() },
|
||||
async { TheQooGetter(context).fetchData() },
|
||||
// async { YoutubeGetter(context).fetchData() },
|
||||
async { DCGetter(context).fetchData() },
|
||||
// async { FmKoreaGetter(context).fetchData() },
|
||||
async { NewsFeedsGetter(context).fetchData() },
|
||||
async { ClienGetter(context).fetchData() },
|
||||
async { DotaxGetter(context).fetchData() },
|
||||
|
||||
@ -16,7 +16,7 @@ import com.frostwire.jlibtorrent.swig.settings_pack
|
||||
import com.frostwire.jlibtorrent.swig.torrent_status
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
|
||||
import android.telephony.TelephonyManager
|
||||
data class TorrentTask(
|
||||
val infoHash: String,
|
||||
val name: String,
|
||||
@ -45,8 +45,7 @@ class TorrentService : Service() {
|
||||
// 제어 플래그
|
||||
private var isWifiConnected = false
|
||||
private var isCharging = false
|
||||
private lateinit var connectivityManager: ConnectivityManager
|
||||
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
||||
|
||||
|
||||
inner class TorrentBinder : Binder() {
|
||||
fun getService(): TorrentService = this@TorrentService
|
||||
@ -56,6 +55,12 @@ class TorrentService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (!isKoreaRegion()) {
|
||||
Blog.LOGE("해외 지역 접속 감지: 서비스를 종료합니다.")
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
startForegroundService()
|
||||
initLibTorrent()
|
||||
|
||||
@ -64,7 +69,7 @@ class TorrentService : Service() {
|
||||
|
||||
// 2. 리시버 및 콜백 등록
|
||||
registerBatteryReceiver()
|
||||
registerNetworkCallback()
|
||||
|
||||
|
||||
// 3. 초기 세션 상태 적용
|
||||
updateSessionState()
|
||||
@ -79,11 +84,7 @@ class TorrentService : Service() {
|
||||
val status = batteryStatus?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1
|
||||
isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
||||
|
||||
// Wi-Fi 상태
|
||||
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val activeNetwork = connectivityManager.activeNetwork
|
||||
val caps = connectivityManager.getNetworkCapabilities(activeNetwork)
|
||||
isWifiConnected = caps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
|
||||
}
|
||||
|
||||
private fun registerBatteryReceiver() {
|
||||
@ -151,34 +152,7 @@ class TorrentService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerNetworkCallback() {
|
||||
val request = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build()
|
||||
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
|
||||
// 단순히 caps만 보지 말고, 현재 활성화된 기본 네트워크의 상태를 직접 다시 조회합니다.
|
||||
val activeNet = connectivityManager.activeNetwork
|
||||
val activeCaps = connectivityManager.getNetworkCapabilities(activeNet)
|
||||
|
||||
val wifiNow = activeCaps?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
|
||||
// 상태가 실제로 변했을 때만 업데이트하여 불필요한 로그와 로직 실행을 방지합니다.
|
||||
if (isWifiConnected != wifiNow) {
|
||||
isWifiConnected = wifiNow
|
||||
updateSessionState()
|
||||
}
|
||||
}
|
||||
|
||||
// 네트워크가 완전히 끊겼을 때도 처리해주는 것이 안전합니다.
|
||||
override fun onLost(network: Network) {
|
||||
checkInitialStatus() // 전체 상태 다시 체크
|
||||
updateSessionState()
|
||||
}
|
||||
}
|
||||
connectivityManager.registerNetworkCallback(request, networkCallback!!)
|
||||
}
|
||||
|
||||
|
||||
var lastUpdateTime = -1L
|
||||
@ -186,6 +160,15 @@ class TorrentService : Service() {
|
||||
* 핵심 제어 로직: 충전 중일 때만 세션을 열고, Wi-Fi 여부에 따라 슬롯 조절
|
||||
*/
|
||||
private fun updateSessionState() {
|
||||
if (!isKoreaRegion()) {
|
||||
Blog.LOGE("해외 지역 접속 감지: 서비스를 종료합니다.")
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
return
|
||||
}
|
||||
|
||||
checkIpAndStop()
|
||||
|
||||
if (session.isPaused) session.resume()
|
||||
var curentTime = System.currentTimeMillis()
|
||||
if (curentTime - lastUpdateTime > 5000) {
|
||||
@ -247,7 +230,38 @@ class TorrentService : Service() {
|
||||
refreshTorrentStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isKoreaRegion(): Boolean {
|
||||
val tm = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||
|
||||
// 1. SIM 카드의 국가 코드 확인 (예: "kr")
|
||||
val simCountry = tm.simCountryIso.lowercase()
|
||||
|
||||
// 2. 네트워크(기지국) 기준 국가 코드 확인
|
||||
val networkCountry = tm.networkCountryIso.lowercase()
|
||||
|
||||
// SIM이나 네트워크 중 하나라도 'kr'이면 한국으로 판단
|
||||
return simCountry == "kr" || networkCountry == "kr"
|
||||
}
|
||||
|
||||
private fun checkIpAndStop() {
|
||||
serviceScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// 외부 API를 통해 국가 코드 확인 (예: ip-api.com)
|
||||
val response = java.net.URL("http://ip-api.com/json/").readText()
|
||||
if (!response.contains("\"countryCode\":\"KR\"")) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Blog.LOGE("IP 위치가 대한민국이 아닙니다. 다운로드를 중지합니다.")
|
||||
// 모든 토렌트 일시정지 또는 서비스 종료
|
||||
session.pause()
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 네트워크 오류 시 보수적으로 처리 (선택 사항)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val TRACKER_URL = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt"
|
||||
@ -518,6 +532,10 @@ class TorrentService : Service() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
intent?.getStringExtra("EXTRA_MAGNET_URI")?.let { addMagnet(it) }
|
||||
intent?.getBooleanExtra("WIFI_STATE", false)?.let {
|
||||
isWifiConnected = it
|
||||
|
||||
}
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
@ -562,7 +580,6 @@ class TorrentService : Service() {
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver(batteryReceiver)
|
||||
networkCallback?.let { connectivityManager.unregisterNetworkCallback(it) }
|
||||
serviceScope.cancel()
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user