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:
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user