@@ -15,6 +15,7 @@ import androidx.media3.common.ForwardingPlayer
1515import androidx.media3.common.MediaMetadata
1616import androidx.media3.common.PlaybackException
1717import androidx.media3.common.Player
18+ import androidx.media3.common.util.Log
1819import androidx.media3.common.util.UnstableApi
1920import androidx.media3.exoplayer.ExoPlayer
2021import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
@@ -23,16 +24,18 @@ import androidx.media3.session.MediaLibraryService.MediaLibrarySession
2324import androidx.media3.session.MediaSession
2425import androidx.media3.session.SessionCommand
2526import androidx.media3.session.SessionResult
26- import com.github.libretube.api.obj.Subtitle
27+ import com.github.libretube.R
28+ import com.github.libretube.api.JsonHelper
29+ import com.github.libretube.api.obj.Segment
2730import com.github.libretube.constants.IntentData
2831import com.github.libretube.enums.PlayerCommand
2932import com.github.libretube.enums.PlayerEvent
30- import com.github.libretube.extensions.parcelable
33+ import com.github.libretube.enums.SbSkipOptions
3134import com.github.libretube.extensions.parcelableExtra
3235import com.github.libretube.extensions.toastFromMainThread
3336import com.github.libretube.extensions.updateParameters
3437import com.github.libretube.helpers.PlayerHelper
35- import com.github.libretube.helpers.PlayerHelper.getSubtitleRoleFlags
38+ import com.github.libretube.helpers.PlayerHelper.getCurrentSegment
3639import com.github.libretube.ui.activities.MainActivity
3740import com.github.libretube.util.DefaultTrackSelectorWithAudioQualitySupport
3841import com.github.libretube.util.NowPlayingNotification
@@ -65,6 +68,18 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
6568 delayMillis = PlayerHelper .WATCH_POSITION_TIMER_DELAY_MS
6669 )
6770
71+ // SponsorBlock Segment data
72+ private var sponsorBlockAutoSkip = true
73+ protected val sponsorBlockConfig = PlayerHelper .getSponsorBlockCategories()
74+ private var sponsorBlockSegments = listOf<Segment >()
75+
76+ /* *
77+ * Whether the service should automatically play the next video after the current video finished.
78+ *
79+ * If set to `false`, the player UI views have to handle autoplay themselves.
80+ */
81+ protected var shouldHandleAutoplay = true
82+
6883 private val playerListener = object : Player .Listener {
6984 override fun onIsPlayingChanged (isPlaying : Boolean ) {
7085 super .onIsPlayingChanged(isPlaying)
@@ -162,13 +177,15 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
162177 }
163178 }
164179
165- args.containsKey(PlayerCommand .SET_SUBTITLE .name) -> {
166- val subtitle: Subtitle ? = args.parcelable(PlayerCommand .SET_SUBTITLE .name)
180+ args.containsKey(PlayerCommand .SET_CAPTION_TRACK .name) -> {
181+ val exoPlayer = exoPlayer ? : return
182+
183+ val captionId = args.getString(PlayerCommand .SET_CAPTION_TRACK .name) ? : return
184+ val caption = PlayerHelper .getCaptionTracks(exoPlayer).firstOrNull { it.id == captionId }
167185
168186 trackSelector?.updateParameters {
169- val roleFlags = getSubtitleRoleFlags(subtitle)
170- setPreferredTextRoleFlags(roleFlags)
171- setPreferredTextLanguage(subtitle?.code)
187+ caption?.roleFlags?.let { setPreferredTextRoleFlags(it) }
188+ setPreferredTextLanguage(caption?.language)
172189 }
173190 }
174191
@@ -183,6 +200,15 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
183200 }
184201 updateNotification()
185202 }
203+
204+ args.containsKey(PlayerCommand .SET_SB_AUTO_SKIP_ENABLED .name) -> {
205+ sponsorBlockAutoSkip = args.getBoolean(PlayerCommand .SET_SB_AUTO_SKIP_ENABLED .name)
206+ }
207+
208+ args.containsKey(PlayerCommand .SET_AUTOPLAY_COUNTDOWN_ENABLED .name) -> {
209+ shouldHandleAutoplay =
210+ ! args.getBoolean(PlayerCommand .SET_AUTOPLAY_COUNTDOWN_ENABLED .name)
211+ }
186212 }
187213 }
188214
@@ -193,6 +219,8 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
193219 */
194220 @CallSuper
195221 open fun navigateVideo (videoId : String ) {
222+ sponsorBlockSegments = emptyList()
223+
196224 updatePlaylistMetadata {
197225 setExtras(bundleOf(IntentData .videoId to videoId))
198226 }
@@ -206,6 +234,39 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
206234 }
207235 }
208236
237+ protected fun setSponsorBlockSegments (segments : List <Segment >) {
238+ sponsorBlockSegments = segments
239+ if (! PlayerHelper .sponsorBlockEnabled) return
240+
241+ updatePlaylistMetadata {
242+ // JSON-encode as work-around for https://github.com/androidx/media/issues/564
243+ val segments = JsonHelper .json.encodeToString(sponsorBlockSegments)
244+ setExtras(bundleOf(IntentData .segments to segments))
245+ }
246+
247+ Log .e(" segments" , sponsorBlockSegments.toString())
248+ checkForSegments()
249+ }
250+
251+ /* *
252+ * check for SponsorBlock segments
253+ */
254+ private fun checkForSegments () {
255+ handler.postDelayed(this ::checkForSegments, 100 )
256+
257+ val (currentSegment, sbSkipOption) = exoPlayer?.getCurrentSegment(
258+ sponsorBlockSegments,
259+ sponsorBlockConfig
260+ ) ? : return
261+
262+ if (sbSkipOption in arrayOf(SbSkipOptions .AUTOMATIC , SbSkipOptions .AUTOMATIC_ONCE ) && sponsorBlockAutoSkip) {
263+ exoPlayer?.seekTo(currentSegment.segmentStartAndEnd.second.toLong() * 1000 )
264+ currentSegment.skipped = true
265+
266+ if (PlayerHelper .sponsorBlockNotifications) toastFromMainThread(R .string.segment_skipped)
267+ }
268+ }
269+
209270 protected fun updatePlaylistMetadata (updateAction : MediaMetadata .Builder .() -> Unit ) {
210271 handler.post {
211272 exoPlayer?.playlistMetadata = MediaMetadata .Builder ()
@@ -435,12 +496,12 @@ abstract class AbstractPlayerService : MediaLibraryService(), MediaLibrarySessio
435496
436497 override fun getAvailableCommands (): Player .Commands {
437498 return super .getAvailableCommands().buildUpon()
438- .addAll(Player . COMMAND_SEEK_TO_PREVIOUS , Player . COMMAND_SEEK_TO_NEXT )
499+ .addAll(COMMAND_SEEK_TO_PREVIOUS , COMMAND_SEEK_TO_NEXT )
439500 .build()
440501 }
441502
442503 override fun isCommandAvailable (command : Int ): Boolean {
443- if (command == Player . COMMAND_SEEK_TO_NEXT || command == Player . COMMAND_SEEK_TO_PREVIOUS ) return true
504+ if (command == COMMAND_SEEK_TO_NEXT || command == COMMAND_SEEK_TO_PREVIOUS ) return true
444505
445506 return super .isCommandAvailable(command)
446507 }
0 commit comments