fix: arreglar pantalla negra del reproductor y agregar filtro deportes

- Agregado hardwareAccelerated=true en AndroidManifest.xml
- Eliminado setMaxVideoSizeSd() que limitaba calidad de video
- Mejorada configuración de SurfaceView en PlayerView con fondo negro
- Agregado videoScalingMode SCALE_TO_FIT para mejor renderizado
- Agregado método getSportsChannels() en ChannelRepository
- Agregado showSportsChannelsOnly() en ChannelsViewModel para testing
- Agregado token Gitea en UpdateService para autenticación
- Actualizada versión a 1.0.1 (versionCode 2)
This commit is contained in:
Renato
2026-01-29 00:00:34 +00:00
parent 0d8c8e5ff2
commit 8edf5c893e
7 changed files with 104 additions and 15 deletions

View File

@@ -12,8 +12,8 @@ android {
applicationId = "com.iptv.app"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
versionCode = 2
versionName = "1.0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@@ -16,6 +16,7 @@
<application
android:name=".IPTVApplication"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@android:drawable/ic_media_play"
android:label="@string/app_name"
android:supportsRtl="true"

View File

@@ -25,6 +25,9 @@ class UpdateService(context: Context) {
private const val REPO_OWNER = "renato97"
private const val REPO_NAME = "iptv-app"
// Token de Gitea para acceder a releases privados
private const val GITEA_TOKEN = "efeed2af00597883adb04da70bd6a7c2993ae92d"
// Endpoints
private const val LATEST_RELEASE_ENDPOINT = "$GITEA_API_URL/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
@@ -70,6 +73,7 @@ class UpdateService(context: Context) {
val request = Request.Builder()
.url(LATEST_RELEASE_ENDPOINT)
.header("Accept", "application/json")
.header("Authorization", "token $GITEA_TOKEN")
.build()
client.newCall(request).execute().use { response ->
@@ -124,8 +128,17 @@ class UpdateService(context: Context) {
*/
fun downloadUpdate(updateInfo: UpdateInfo, outputDir: File): Flow<DownloadProgress> = flow {
try {
// Construir URL de descarga con autenticación para repos privados
val downloadUrl = if (updateInfo.downloadUrl.contains("/attachments/")) {
// Para Gitea, usar API con token
"$GITEA_API_URL/repos/$REPO_OWNER/$REPO_NAME/releases/attachments/${updateInfo.fileName}"
} else {
updateInfo.downloadUrl
}
val request = Request.Builder()
.url(updateInfo.downloadUrl)
.url(downloadUrl)
.header("Authorization", "token $GITEA_TOKEN")
.build()
client.newCall(request).execute().use { response ->

View File

@@ -107,6 +107,38 @@ class ChannelRepository(
}
.flowOn(Dispatchers.Default)
/**
* Returns sports channels for testing purposes.
*/
fun getSportsChannels(): Flow<List<Channel>> = _allChannels
.map { channels ->
channels.filter { channel ->
val category = channel.category.lowercase()
val name = channel.name.lowercase()
category.contains("sport") ||
category.contains("deport") ||
name.contains("espn") ||
name.contains("fox sport") ||
name.contains("bein") ||
name.contains("sky sport") ||
name.contains("bt sport") ||
name.contains("eurosport") ||
name.contains("tsn") ||
name.contains("nba") ||
name.contains("nfl") ||
name.contains("mlb") ||
name.contains("ufc") ||
name.contains("wwe") ||
name.contains("golf") ||
name.contains("tennis") ||
name.contains("f1") ||
name.contains("formula 1") ||
name.contains("racing") ||
name.contains("motorsport")
}
}
.flowOn(Dispatchers.Default)
/**
* Searches channels by name with debouncing.
*/

View File

@@ -37,6 +37,7 @@ import androidx.media3.datasource.okhttp.OkHttpDataSource
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import android.widget.FrameLayout
import com.iptv.app.utils.DnsConfigurator
import com.iptv.app.utils.PlayerManager
@@ -200,15 +201,16 @@ private fun createExoPlayer(
val trackSelector = DefaultTrackSelector(context).apply {
setParameters(
this.buildUponParameters()
.setMaxVideoSizeSd()
.setPreferredAudioLanguage("en")
.setSelectUndeterminedTextLanguage(false)
.setPreferredTextLanguage(null)
)
}
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,
DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * 2,
DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * 2,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
)
@@ -220,15 +222,16 @@ private fun createExoPlayer(
.setMediaSourceFactory(DefaultMediaSourceFactory(context))
.setAudioAttributes(playerManager.getAudioAttributes(), true)
.setHandleAudioBecomingNoisy(true)
.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT)
.build()
.apply {
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
repeatMode = Player.REPEAT_MODE_OFF
}
}
/**
* Creates the PlayerView for displaying video
* Creates the PlayerView for displaying video using SurfaceView for best performance
*/
@OptIn(UnstableApi::class)
private fun createPlayerView(context: Context, player: ExoPlayer): PlayerView {
@@ -236,10 +239,13 @@ private fun createPlayerView(context: Context, player: ExoPlayer): PlayerView {
this.player = player
useController = false // We use custom controls
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
layoutParams = ViewGroup.LayoutParams(
setBackgroundColor(android.graphics.Color.BLACK)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setKeepContentOnPlayerReset(true)
setShowBuffering(PlayerView.SHOW_BUFFERING_NEVER)
}

View File

@@ -58,9 +58,13 @@ fun PlayerScreen(
val activity = context as? Activity
val lifecycleOwner = LocalLifecycleOwner.current
// ExoPlayer setup
// ExoPlayer setup with proper video configuration
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
ExoPlayer.Builder(context)
.setVideoScalingMode(androidx.media3.common.C.VIDEO_SCALING_MODE_SCALE_TO_FIT)
.build()
.apply {
videoScalingMode = androidx.media3.common.C.VIDEO_SCALING_MODE_SCALE_TO_FIT
setMediaItem(MediaItem.fromUri(channel.streamUrl))
prepare()
playWhenReady = true
@@ -169,6 +173,8 @@ fun PlayerScreen(
player = exoPlayer
useController = false
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
setBackgroundColor(android.graphics.Color.BLACK)
setKeepContentOnPlayerReset(true)
}
},
modifier = Modifier.fillMaxSize()

View File

@@ -65,6 +65,37 @@ class ChannelsViewModel(
setupSearchAndFilter()
}
/**
* Gets sports channels for testing video playback.
*/
fun getSportsChannels(): Flow<List<Channel>> {
return channelRepository.getSportsChannels()
}
/**
* Shows only sports channels in the UI for testing.
*/
fun showSportsChannelsOnly() {
viewModelScope.launch {
channelRepository.getSportsChannels()
.onEach { sportsChannels ->
_uiState.update { state ->
state.copy(
channels = sportsChannels,
filteredChannels = sportsChannels,
selectedCategory = "Sports"
)
}
}
.catch { error ->
_uiState.update { state ->
state.copy(errorMessage = "Error loading sports channels: ${error.message}")
}
}
.launchIn(viewModelScope)
}
}
/**
* Loads channels from the repository.
*/