mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
174 lines
6.9 KiB
Kotlin
174 lines
6.9 KiB
Kotlin
package com.github.libretube.services
|
|
|
|
import android.net.Uri
|
|
import android.os.Bundle
|
|
import androidx.annotation.OptIn
|
|
import androidx.core.net.toUri
|
|
import androidx.media3.common.C
|
|
import androidx.media3.common.MediaItem
|
|
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
|
import androidx.media3.common.MimeTypes
|
|
import androidx.media3.common.util.UnstableApi
|
|
import androidx.media3.datasource.cronet.CronetDataSource
|
|
import androidx.media3.exoplayer.hls.HlsMediaSource
|
|
import com.github.libretube.R
|
|
import com.github.libretube.api.CronetHelper
|
|
import com.github.libretube.api.obj.Streams
|
|
import com.github.libretube.api.obj.Subtitle
|
|
import com.github.libretube.constants.IntentData
|
|
import com.github.libretube.constants.PreferenceKeys
|
|
import com.github.libretube.enums.PlayerCommand
|
|
import com.github.libretube.extensions.parcelable
|
|
import com.github.libretube.extensions.setMetadata
|
|
import com.github.libretube.extensions.toastFromMainThread
|
|
import com.github.libretube.extensions.updateParameters
|
|
import com.github.libretube.helpers.PlayerHelper
|
|
import com.github.libretube.helpers.PreferenceHelper
|
|
import com.github.libretube.helpers.ProxyHelper
|
|
import com.github.libretube.ui.activities.MainActivity
|
|
import com.github.libretube.util.YoutubeHlsPlaylistParser
|
|
import java.util.concurrent.Executors
|
|
|
|
@OptIn(UnstableApi::class)
|
|
class VideoOnlinePlayerService : AbstractPlayerService() {
|
|
override val isOfflinePlayer: Boolean = false
|
|
override val isAudioOnlyPlayer: Boolean = false
|
|
override val intentActivity: Class<*> = MainActivity::class.java
|
|
|
|
private val cronetDataSourceFactory = CronetDataSource.Factory(
|
|
CronetHelper.cronetEngine,
|
|
Executors.newCachedThreadPool()
|
|
)
|
|
|
|
private lateinit var streams: Streams
|
|
|
|
override suspend fun onServiceCreated(args: Bundle) {
|
|
this.streams = args.parcelable<Streams>(IntentData.streams) ?: return
|
|
|
|
startPlayback()
|
|
}
|
|
|
|
override suspend fun startPlayback() = Unit
|
|
|
|
override fun runPlayerCommand(args: Bundle) {
|
|
when {
|
|
args.containsKey(PlayerCommand.START_PLAYBACK.name) -> setStreamSource()
|
|
args.containsKey(PlayerCommand.SKIP_SILENCE.name) -> exoPlayer?.skipSilenceEnabled = args.getBoolean(PlayerCommand.SKIP_SILENCE.name)
|
|
args.containsKey(PlayerCommand.SET_VIDEO_TRACK_TYPE_DISABLED.name) -> trackSelector?.updateParameters {
|
|
setTrackTypeDisabled(
|
|
C.TRACK_TYPE_VIDEO,
|
|
args.getBoolean(PlayerCommand.SET_VIDEO_TRACK_TYPE_DISABLED.name)
|
|
)
|
|
}
|
|
args.containsKey(PlayerCommand.SET_AUDIO_ROLE_FLAGS.name) -> {
|
|
trackSelector?.updateParameters {
|
|
setPreferredAudioRoleFlags(args.getInt(PlayerCommand.SET_AUDIO_ROLE_FLAGS.name))
|
|
}
|
|
}
|
|
args.containsKey(PlayerCommand.SET_AUDIO_LANGUAGE.name) -> {
|
|
trackSelector?.updateParameters {
|
|
setPreferredAudioLanguage(args.getString(PlayerCommand.SET_AUDIO_LANGUAGE.name))
|
|
}
|
|
}
|
|
args.containsKey(PlayerCommand.SET_RESOLUTION.name) -> {
|
|
trackSelector?.updateParameters {
|
|
val resolution = args.getInt(PlayerCommand.SET_RESOLUTION.name)
|
|
setMinVideoSize(Int.MIN_VALUE, resolution)
|
|
setMaxVideoSize(Int.MAX_VALUE, resolution)
|
|
}
|
|
}
|
|
args.containsKey(PlayerCommand.SET_SUBTITLE.name) -> {
|
|
updateCurrentSubtitle(args.parcelable(PlayerCommand.SET_SUBTITLE.name))
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun setStreamSource() {
|
|
if (!this::streams.isInitialized) return
|
|
|
|
val (uri, mimeType) = when {
|
|
// LBRY HLS
|
|
PreferenceHelper.getBoolean(
|
|
PreferenceKeys.LBRY_HLS,
|
|
false
|
|
) && streams.videoStreams.any {
|
|
it.quality.orEmpty().contains("LBRY HLS")
|
|
} -> {
|
|
val lbryHlsUrl = streams.videoStreams.first {
|
|
it.quality!!.contains("LBRY HLS")
|
|
}.url!!
|
|
lbryHlsUrl.toUri() to MimeTypes.APPLICATION_M3U8
|
|
}
|
|
// DASH
|
|
!PlayerHelper.useHlsOverDash && streams.videoStreams.isNotEmpty() -> {
|
|
// only use the dash manifest generated by YT if either it's a livestream or no other source is available
|
|
val dashUri =
|
|
if (streams.isLive && streams.dash != null) {
|
|
ProxyHelper.unwrapStreamUrl(
|
|
streams.dash!!
|
|
).toUri()
|
|
} else {
|
|
// skip LBRY urls when checking whether the stream source is usable
|
|
PlayerHelper.createDashSource(streams, this)
|
|
}
|
|
|
|
dashUri to MimeTypes.APPLICATION_MPD
|
|
}
|
|
// HLS
|
|
streams.hls != null -> {
|
|
val hlsMediaSourceFactory = HlsMediaSource.Factory(cronetDataSourceFactory)
|
|
.setPlaylistParserFactory(YoutubeHlsPlaylistParser.Factory())
|
|
|
|
val mediaSource = hlsMediaSourceFactory.createMediaSource(
|
|
createMediaItem(
|
|
ProxyHelper.unwrapStreamUrl(streams.hls!!).toUri(),
|
|
MimeTypes.APPLICATION_M3U8
|
|
)
|
|
)
|
|
exoPlayer?.setMediaSource(mediaSource)
|
|
return
|
|
}
|
|
// NO STREAM FOUND
|
|
else -> {
|
|
toastFromMainThread(R.string.unknown_error)
|
|
return
|
|
}
|
|
}
|
|
setMediaSource(uri, mimeType)
|
|
}
|
|
|
|
private fun getSubtitleConfigs(): List<SubtitleConfiguration> = streams.subtitles.map {
|
|
val roleFlags = getSubtitleRoleFlags(it)
|
|
SubtitleConfiguration.Builder(it.url!!.toUri())
|
|
.setRoleFlags(roleFlags)
|
|
.setLanguage(it.code)
|
|
.setMimeType(it.mimeType).build()
|
|
}
|
|
|
|
private fun createMediaItem(uri: Uri, mimeType: String) = MediaItem.Builder()
|
|
.setUri(uri)
|
|
.setMimeType(mimeType)
|
|
.setSubtitleConfigurations(getSubtitleConfigs())
|
|
.setMetadata(streams)
|
|
.build()
|
|
|
|
private fun setMediaSource(uri: Uri, mimeType: String) {
|
|
val mediaItem = createMediaItem(uri, mimeType)
|
|
exoPlayer?.setMediaItem(mediaItem)
|
|
}
|
|
|
|
private fun getSubtitleRoleFlags(subtitle: Subtitle?): Int {
|
|
return if (subtitle?.autoGenerated != true) {
|
|
C.ROLE_FLAG_CAPTION
|
|
} else {
|
|
PlayerHelper.ROLE_FLAG_AUTO_GEN_SUBTITLE
|
|
}
|
|
}
|
|
|
|
private fun updateCurrentSubtitle(subtitle: Subtitle?) =
|
|
trackSelector?.updateParameters {
|
|
val roleFlags = if (subtitle?.code != null) getSubtitleRoleFlags(subtitle) else 0
|
|
setPreferredTextRoleFlags(roleFlags)
|
|
setPreferredTextLanguage(subtitle?.code)
|
|
}
|
|
} |