package com.streamflixreborn.streamflix.fragments.player import android.net.Uri import android.os.Bundle import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.streamflixreborn.streamflix.models.Video import com.streamflixreborn.streamflix.utils.CustomTabHelper import com.streamflixreborn.streamflix.utils.EpisodeManager import com.streamflixreborn.streamflix.utils.OpenSubtitles import com.streamflixreborn.streamflix.utils.UserPreferences import com.streamflixreborn.streamflix.utils.format import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import com.streamflixreborn.streamflix.utils.SubDL class PlayerViewModel( videoType: Video.Type, id: String, ) : ViewModel() { private val _state = MutableStateFlow(State.LoadingServers) val state: Flow = _state private val _subtitleState = MutableSharedFlow() val subtitleState: SharedFlow = _subtitleState private val _playPreviousOrNextEpisode = MutableSharedFlow() val playPreviousOrNextEpisode: SharedFlow = _playPreviousOrNextEpisode init { getServers(videoType, id) getSubtitles(videoType) } fun playEpisode(direction: Direction) { val hasEpisode = when (direction) { Direction.PREVIOUS -> EpisodeManager.hasPreviousEpisode() Direction.NEXT -> EpisodeManager.hasNextEpisode() } if (!hasEpisode) return val ep = when (direction) { Direction.PREVIOUS -> EpisodeManager.getPreviousEpisode() Direction.NEXT -> EpisodeManager.getNextEpisode() } ?: return val nextEpisode = Video.Type.Episode( id = ep.id, number = ep.number, title = ep.title, poster = ep.poster, overview = ep.overview, tvShow = Video.Type.Episode.TvShow( id = ep.tvShow.id, title = ep.tvShow.title, poster = ep.tvShow.poster, banner = ep.tvShow.banner, releaseDate = ep.tvShow.releaseDate, imdbId = ep.tvShow.imdbId ), season = Video.Type.Episode.Season( number = ep.season.number, title = ep.season.title ) ) playEpisode(nextEpisode) viewModelScope.launch { _playPreviousOrNextEpisode.emit(nextEpisode) } } enum class Direction { PREVIOUS, NEXT } fun playPreviousEpisode() = playEpisode(Direction.PREVIOUS) fun playNextEpisode() = playEpisode(Direction.NEXT) fun autoplayNextEpisode() { if (UserPreferences.autoplay) { playEpisode(Direction.NEXT) } } fun playEpisode(episode: Video.Type.Episode) { getServers(episode, episode.id) getSubtitles(episode) } private fun getServers(videoType: Video.Type, id: String) = viewModelScope.launch(Dispatchers.IO) { Log.d("PlayerViewModel", "Inizio ricerca server per ID: $id") lastVideoType = videoType lastId = id _state.emit(State.LoadingServers) try { val servers = UserPreferences.currentProvider!!.getServers(id, videoType) if (servers.isEmpty()) throw Exception("No servers found") // LOG POTENZIATO: Mostra tutti i server disponibili per il player Log.i("StreamFlixES", "[SERVERS LIST] -> Provider: ${UserPreferences.currentProvider!!.name}") Log.i("StreamFlixES", "[SERVERS LIST] -> Found ${servers.size} servers: ${servers.joinToString { it.name }}") Log.d("PlayerViewModel", "Ricerca server completata: ${servers.size} server trovati") _state.emit(State.SuccessLoadingServers(servers)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore ricerca server: ", e) _state.emit(State.FailedLoadingServers(e)) } } fun getVideo(server: Video.Server) = viewModelScope.launch(Dispatchers.IO) { Log.d("PlayerViewModel", "Inizio estrazione video dal server: ${server.name}") _state.emit(State.LoadingVideo(server)) try { val video = UserPreferences.currentProvider!!.getVideo(server) if (video.source.isEmpty()) throw Exception("No source found") // LOGICA SOTTOTITOLI GLOBALE: // Se il provider non ha giĆ  impostato un default (es. i "forced" in spagnolo), // allora proviamo ad attivare l'ultimo sottotitolo usato dall'utente. // MA: se siamo su un provider spagnolo e non ci sono forced, non dobbiamo attivare nulla. val currentProviderLang = UserPreferences.currentProvider?.language ?: "" val hasDefaultAlready = video.subtitles.any { it.default } if (!hasDefaultAlready && currentProviderLang != "es") { if (!(video.useServerSubtitleSetting && UserPreferences.serverAutoSubtitlesDisabled)) { video.subtitles .firstOrNull { it.label.startsWith(UserPreferences.subtitleName ?: "") } ?.default = true } } Log.d("PlayerViewModel", "Estrazione video completata con successo") _state.emit(State.SuccessLoadingVideo(video, server)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore estrazione video: ", e) _state.emit(State.FailedLoadingVideo(e, server)) } } fun getSubtitles(videoType: Video.Type) = viewModelScope.launch(Dispatchers.IO) { Log.d("PlayerViewModel", "Inizio ricerca sottotitoli") _subtitleState.emit(SubtitleState.Loading) launch { try { Log.d("PlayerViewModel", "Inizio ricerca OpenSubtitles") val subtitles = when (videoType) { is Video.Type.Episode -> { OpenSubtitles.search( query = videoType.tvShow.title, season = videoType.season.number, episode = videoType.number, ) } is Video.Type.Movie -> { OpenSubtitles.search(query = videoType.title) } }.sortedWith(compareBy({ it.languageName }, { it.subDownloadsCnt })) Log.d("PlayerViewModel", "Ricerca OpenSubtitles completata: ${subtitles.size} risultati") _subtitleState.emit(SubtitleState.SuccessOpenSubtitles(subtitles)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore OpenSubtitles: ", e) _subtitleState.emit(SubtitleState.FailedOpenSubtitles(e)) } } launch { try { Log.d("PlayerViewModel", "Inizio ricerca SubDL") val subtitles = when (videoType) { is Video.Type.Episode -> { SubDL.search( filmName = videoType.tvShow.title, seasonNumber = videoType.season.number, episodeNumber = videoType.number, type = "tv" ) } is Video.Type.Movie -> { SubDL.search( filmName = videoType.title, type = "movie" ) } } Log.d("PlayerViewModel", "Ricerca SubDL completata: ${subtitles.size} risultati") _subtitleState.emit(SubtitleState.SuccessSubDLSubtitles(subtitles)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore SubDL: ", e) _subtitleState.emit(SubtitleState.FailedSubDLSubtitles(e)) } } } fun downloadSubtitle(subtitle: OpenSubtitles.Subtitle) = viewModelScope.launch(Dispatchers.IO) { Log.d("PlayerViewModel", "Inizio download sottotitolo OpenSubtitles: ${subtitle.subFileName}") _subtitleState.emit(SubtitleState.DownloadingOpenSubtitle) try { val uri = OpenSubtitles.download(subtitle) Log.d("PlayerViewModel", "Download OpenSubtitles completato: $uri") _subtitleState.emit(SubtitleState.SuccessDownloadingOpenSubtitle(subtitle, uri)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore download OpenSubtitles: ", e) _subtitleState.emit(SubtitleState.FailedDownloadingOpenSubtitle(e, subtitle)) } } fun downloadSubDLSubtitle(subtitle: SubDL.Subtitle) = viewModelScope.launch(Dispatchers.IO) { Log.d("PlayerViewModel", "Inizio download sottotitolo SubDL: ${subtitle.name}") _subtitleState.emit(SubtitleState.DownloadingSubDLSubtitle) try { val uri = SubDL.download(subtitle) Log.d("PlayerViewModel", "Download SubDL completato: $uri") _subtitleState.emit(SubtitleState.SuccessDownloadingSubDLSubtitle(subtitle, uri)) } catch (e: Exception) { Log.e("PlayerViewModel", "Errore download SubDL: ", e) _subtitleState.emit(SubtitleState.FailedDownloadingSubDLSubtitle(e, subtitle)) } } sealed class State { data object LoadingServers : State() data class SuccessLoadingServers(val servers: List) : State() data class FailedLoadingServers(val error: Exception) : State() data class LoadingVideo(val server: Video.Server) : State() data class SuccessLoadingVideo(val video: Video, val server: Video.Server) : State() data class FailedLoadingVideo(val error: Exception, val server: Video.Server) : State() } sealed class SubtitleState { data object Loading : SubtitleState() data class SuccessOpenSubtitles(val subtitles: List) : SubtitleState() data class FailedOpenSubtitles(val error: Exception) : SubtitleState() data object DownloadingOpenSubtitle : SubtitleState() data class SuccessDownloadingOpenSubtitle(val subtitle: OpenSubtitles.Subtitle, val uri: Uri) : SubtitleState() data class FailedDownloadingOpenSubtitle(val error: Exception, val subtitle: OpenSubtitles.Subtitle) : SubtitleState() data class SuccessSubDLSubtitles(val subtitles: List) : SubtitleState() data class FailedSubDLSubtitles(val error: Exception) : SubtitleState() data object DownloadingSubDLSubtitle : SubtitleState() data class SuccessDownloadingSubDLSubtitle(val subtitle: SubDL.Subtitle, val uri: Uri) : SubtitleState() data class FailedDownloadingSubDLSubtitle(val error: Exception, val subtitle: SubDL.Subtitle) : SubtitleState() } private var lastVideoType: Video.Type? = null private var lastId: String? = null fun reloadServersAfterBypass() { val type = lastVideoType ?: return val id = lastId ?: return getServers(type, id) } }