@@ -3,9 +3,16 @@ package com.github.libretube.services
33import android.content.Intent
44import android.os.Bundle
55import androidx.annotation.OptIn
6+ import androidx.media3.common.C
67import androidx.media3.common.MediaItem
8+ import androidx.media3.common.MediaItem.SubtitleConfiguration
9+ import androidx.media3.common.MimeTypes
710import androidx.media3.common.Player
811import androidx.media3.common.util.UnstableApi
12+ import androidx.media3.datasource.FileDataSource
13+ import androidx.media3.exoplayer.source.MergingMediaSource
14+ import androidx.media3.exoplayer.source.ProgressiveMediaSource
15+ import androidx.media3.exoplayer.source.SingleSampleMediaSource
916import com.github.libretube.constants.IntentData
1017import com.github.libretube.db.DatabaseHelper
1118import com.github.libretube.db.DatabaseHolder.Database
@@ -15,6 +22,7 @@ import com.github.libretube.enums.FileType
1522import com.github.libretube.extensions.serializable
1623import com.github.libretube.extensions.setMetadata
1724import com.github.libretube.extensions.toAndroidUri
25+ import com.github.libretube.extensions.updateParameters
1826import com.github.libretube.helpers.PlayerHelper
1927import com.github.libretube.ui.activities.MainActivity
2028import com.github.libretube.ui.activities.NoInternetActivity
@@ -65,6 +73,7 @@ open class OfflinePlayerService : AbstractPlayerService() {
6573 downloadTab = args.serializable(IntentData .downloadTab)!!
6674 shuffle = args.getBoolean(IntentData .shuffle, false )
6775 noInternetService = args.getBoolean(IntentData .noInternet, false )
76+ isAudioOnlyPlayer = args.getBoolean(IntentData .audioOnly, false )
6877
6978 val videoId = if (shuffle) {
7079 runBlocking(Dispatchers .IO ) {
@@ -75,10 +84,12 @@ open class OfflinePlayerService : AbstractPlayerService() {
7584 } ? : return
7685 setVideoId(videoId)
7786
78- PlayingQueue .clear()
79-
8087 exoPlayer?.addListener(playerListener)
88+ trackSelector?.updateParameters {
89+ setTrackTypeDisabled(C .TRACK_TYPE_VIDEO , isAudioOnlyPlayer)
90+ }
8191
92+ PlayingQueue .clear()
8293 fillQueue()
8394 }
8495
@@ -116,23 +127,71 @@ open class OfflinePlayerService : AbstractPlayerService() {
116127 }
117128 }
118129
119- open fun setMediaItem (downloadWithItems : DownloadWithItems ) {
120- val audioItem = downloadWithItems.downloadItems.filter { it.path.exists() }
121- .firstOrNull { it.type == FileType .AUDIO }
122- ? : // in some rare cases, video files can contain audio
123- downloadWithItems.downloadItems.firstOrNull { it.type == FileType .VIDEO }
130+ private fun setMediaItem (downloadWithItems : DownloadWithItems ) {
131+ val downloadFiles = downloadWithItems.downloadItems.filter { it.path.exists() }
124132
125- if (audioItem == null ) {
133+ val videoUri = downloadFiles.firstOrNull { it.type == FileType .VIDEO }?.path?.toAndroidUri()
134+ val audioUri = downloadFiles.firstOrNull { it.type == FileType .AUDIO }?.path?.toAndroidUri()
135+ if (isAudioOnlyPlayer && audioUri == null ) {
126136 stopSelf()
127137 return
128138 }
129139
130- val mediaItem = MediaItem .Builder ()
131- .setUri(audioItem.path.toAndroidUri())
132- .setMetadata(downloadWithItems)
133- .build()
140+ val subtitleInfo = downloadFiles.firstOrNull { it.type == FileType .SUBTITLE }
141+
142+ val subtitle = subtitleInfo?.let {
143+ SubtitleConfiguration .Builder (it.path.toAndroidUri())
144+ .setMimeType(MimeTypes .APPLICATION_TTML )
145+ .setLanguage(it.language ? : " en" )
146+ .build()
147+ }
148+
149+ when {
150+ videoUri != null && audioUri != null -> {
151+ val videoItem = MediaItem .Builder ()
152+ .setUri(videoUri)
153+ .setMetadata(downloadWithItems)
154+ .setSubtitleConfigurations(listOfNotNull(subtitle))
155+ .build()
156+
157+ val videoSource = ProgressiveMediaSource .Factory (FileDataSource .Factory ())
158+ .createMediaSource(videoItem)
159+
160+ val audioSource = ProgressiveMediaSource .Factory (FileDataSource .Factory ())
161+ .createMediaSource(MediaItem .fromUri(audioUri))
134162
135- exoPlayer?.setMediaItem(mediaItem)
163+ var mediaSource = MergingMediaSource (audioSource, videoSource)
164+ if (subtitle != null ) {
165+ val subtitleSource = SingleSampleMediaSource .Factory (FileDataSource .Factory ())
166+ .createMediaSource(subtitle, C .TIME_UNSET )
167+
168+ mediaSource = MergingMediaSource (mediaSource, subtitleSource)
169+ }
170+
171+ exoPlayer?.setMediaSource(mediaSource)
172+ }
173+
174+ videoUri != null -> exoPlayer?.setMediaItem(
175+ MediaItem .Builder ()
176+ .setUri(videoUri)
177+ .setMetadata(downloadWithItems)
178+ .setSubtitleConfigurations(listOfNotNull(subtitle))
179+ .build()
180+ )
181+
182+ audioUri != null -> exoPlayer?.setMediaItem(
183+ MediaItem .Builder ()
184+ .setUri(audioUri)
185+ .setMetadata(downloadWithItems)
186+ .setSubtitleConfigurations(listOfNotNull(subtitle))
187+ .build()
188+ )
189+ }
190+
191+ trackSelector?.updateParameters {
192+ setPreferredTextRoleFlags(C .ROLE_FLAG_CAPTION )
193+ setPreferredTextLanguage(subtitle?.language)
194+ }
136195 }
137196
138197 private suspend fun fillQueue () {
0 commit comments