mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-18 15:32:35 +00:00
check to see if an HLS playlist is a master playlist before parsing it
Changelog: changed
This commit is contained in:
parent
d63fa521a1
commit
1509c11f64
2 changed files with 172 additions and 109 deletions
|
@ -4,8 +4,14 @@ import android.app.NotificationManager
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.DefaultHlsPlaylistParserFactory
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMediaPlaylist
|
||||||
|
import androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.activities.MainActivity
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
import com.futo.platformplayer.activities.SettingsActivity
|
import com.futo.platformplayer.activities.SettingsActivity
|
||||||
|
@ -37,6 +43,9 @@ import com.futo.platformplayer.models.Playlist
|
||||||
import com.futo.platformplayer.models.Subscription
|
import com.futo.platformplayer.models.Subscription
|
||||||
import com.futo.platformplayer.models.SubscriptionGroup
|
import com.futo.platformplayer.models.SubscriptionGroup
|
||||||
import com.futo.platformplayer.parsers.HLS
|
import com.futo.platformplayer.parsers.HLS
|
||||||
|
import com.futo.platformplayer.parsers.HLS.MediaRendition
|
||||||
|
import com.futo.platformplayer.parsers.HLS.StreamInfo
|
||||||
|
import com.futo.platformplayer.parsers.HLS.VariantPlaylistReference
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StateDownloads
|
import com.futo.platformplayer.states.StateDownloads
|
||||||
import com.futo.platformplayer.states.StateHistory
|
import com.futo.platformplayer.states.StateHistory
|
||||||
|
@ -63,6 +72,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
class UISlideOverlays {
|
class UISlideOverlays {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -269,6 +280,7 @@ class UISlideOverlays {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
fun showHlsPicker(video: IPlatformVideoDetails, source: Any, sourceUrl: String, container: ViewGroup): SlideUpMenuOverlay {
|
fun showHlsPicker(video: IPlatformVideoDetails, source: Any, sourceUrl: String, container: ViewGroup): SlideUpMenuOverlay {
|
||||||
val items = arrayListOf<View>(LoaderView(container.context))
|
val items = arrayListOf<View>(LoaderView(container.context))
|
||||||
val slideUpMenuOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items)
|
val slideUpMenuOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items)
|
||||||
|
@ -292,55 +304,103 @@ class UISlideOverlays {
|
||||||
|
|
||||||
val masterPlaylist: HLS.MasterPlaylist
|
val masterPlaylist: HLS.MasterPlaylist
|
||||||
try {
|
try {
|
||||||
masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, sourceUrl, source is IHLSManifestAudioSource)
|
val inputStream = ByteArrayInputStream(masterPlaylistContent.toByteArray())
|
||||||
|
val playlist = DefaultHlsPlaylistParserFactory().createPlaylistParser()
|
||||||
|
.parse(sourceUrl.toUri(), inputStream)
|
||||||
|
|
||||||
masterPlaylist.getAudioSources().forEach { it ->
|
if (playlist is HlsMediaPlaylist) {
|
||||||
|
if (source is IHLSManifestAudioSource) {
|
||||||
|
val variant = HLS.mediaRenditionToVariant(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))!!
|
||||||
|
|
||||||
val estSize = VideoHelper.estimateSourceSize(it);
|
val estSize = VideoHelper.estimateSourceSize(variant);
|
||||||
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
||||||
audioButtons.add(SlideUpMenuItem(
|
audioButtons.add(SlideUpMenuItem(
|
||||||
container.context,
|
container.context,
|
||||||
R.drawable.ic_music,
|
R.drawable.ic_music,
|
||||||
it.name,
|
variant.name,
|
||||||
listOf(it.language, it.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
|
listOf(variant.language, variant.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
|
||||||
(prefix + it.codec).trim(),
|
(prefix + variant.codec).trim(),
|
||||||
tag = it,
|
tag = variant,
|
||||||
call = {
|
call = {
|
||||||
selectedAudioVariant = it
|
selectedAudioVariant = variant
|
||||||
slideUpMenuOverlay.selectOption(audioButtons, it)
|
slideUpMenuOverlay.selectOption(audioButtons, variant)
|
||||||
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
|
||||||
},
|
|
||||||
invokeParent = false
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*masterPlaylist.getSubtitleSources().forEach { it ->
|
|
||||||
subtitleButtons.add(SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, listOf(it.format).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "), it, {
|
|
||||||
selectedSubtitleVariant = it
|
|
||||||
slideUpMenuOverlay.selectOption(subtitleButtons, it)
|
|
||||||
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
|
||||||
}, false))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
masterPlaylist.getVideoSources().forEach {
|
|
||||||
val estSize = VideoHelper.estimateSourceSize(it);
|
|
||||||
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
|
||||||
videoButtons.add(SlideUpMenuItem(
|
|
||||||
container.context,
|
|
||||||
R.drawable.ic_movie,
|
|
||||||
it.name,
|
|
||||||
"${it.width}x${it.height}",
|
|
||||||
(prefix + it.codec).trim(),
|
|
||||||
tag = it,
|
|
||||||
call = {
|
|
||||||
selectedVideoVariant = it
|
|
||||||
slideUpMenuOverlay.selectOption(videoButtons, it)
|
|
||||||
if (audioButtons.isEmpty()){
|
|
||||||
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
||||||
}
|
},
|
||||||
},
|
invokeParent = false
|
||||||
invokeParent = false
|
))
|
||||||
))
|
} else {
|
||||||
|
val variant = HLS.variantReferenceToVariant(VariantPlaylistReference(playlist.baseUri, StreamInfo(null, null, null, null, null, null, null, null, null)))
|
||||||
|
|
||||||
|
val estSize = VideoHelper.estimateSourceSize(variant);
|
||||||
|
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
||||||
|
videoButtons.add(SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
|
R.drawable.ic_movie,
|
||||||
|
variant.name,
|
||||||
|
"${variant.width}x${variant.height}",
|
||||||
|
(prefix + variant.codec).trim(),
|
||||||
|
tag = variant,
|
||||||
|
call = {
|
||||||
|
selectedVideoVariant = variant
|
||||||
|
slideUpMenuOverlay.selectOption(videoButtons, variant)
|
||||||
|
if (audioButtons.isEmpty()){
|
||||||
|
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invokeParent = false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else if (playlist is HlsMultivariantPlaylist) {
|
||||||
|
masterPlaylist = HLS.parseMasterPlaylist(masterPlaylistContent, sourceUrl)
|
||||||
|
|
||||||
|
masterPlaylist.getAudioSources().forEach { it ->
|
||||||
|
|
||||||
|
val estSize = VideoHelper.estimateSourceSize(it);
|
||||||
|
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
||||||
|
audioButtons.add(SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
|
R.drawable.ic_music,
|
||||||
|
it.name,
|
||||||
|
listOf(it.language, it.codec).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "),
|
||||||
|
(prefix + it.codec).trim(),
|
||||||
|
tag = it,
|
||||||
|
call = {
|
||||||
|
selectedAudioVariant = it
|
||||||
|
slideUpMenuOverlay.selectOption(audioButtons, it)
|
||||||
|
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
||||||
|
},
|
||||||
|
invokeParent = false
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*masterPlaylist.getSubtitleSources().forEach { it ->
|
||||||
|
subtitleButtons.add(SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, listOf(it.format).mapNotNull { x -> x.ifEmpty { null } }.joinToString(", "), it, {
|
||||||
|
selectedSubtitleVariant = it
|
||||||
|
slideUpMenuOverlay.selectOption(subtitleButtons, it)
|
||||||
|
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
||||||
|
}, false))
|
||||||
|
}*/
|
||||||
|
|
||||||
|
masterPlaylist.getVideoSources().forEach {
|
||||||
|
val estSize = VideoHelper.estimateSourceSize(it);
|
||||||
|
val prefix = if(estSize > 0) "±" + estSize.toHumanBytesSize() + " " else "";
|
||||||
|
videoButtons.add(SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
|
R.drawable.ic_movie,
|
||||||
|
it.name,
|
||||||
|
"${it.width}x${it.height}",
|
||||||
|
(prefix + it.codec).trim(),
|
||||||
|
tag = it,
|
||||||
|
call = {
|
||||||
|
selectedVideoVariant = it
|
||||||
|
slideUpMenuOverlay.selectOption(videoButtons, it)
|
||||||
|
if (audioButtons.isEmpty()){
|
||||||
|
slideUpMenuOverlay.setOk(container.context.getString(R.string.download))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invokeParent = false
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val newItems = arrayListOf<View>()
|
val newItems = arrayListOf<View>()
|
||||||
|
@ -950,26 +1010,30 @@ class UISlideOverlays {
|
||||||
+ actions).filterNotNull()
|
+ actions).filterNotNull()
|
||||||
));
|
));
|
||||||
items.add(
|
items.add(
|
||||||
SlideUpMenuGroup(container.context, container.context.getString(R.string.add_to), "addto",
|
SlideUpMenuGroup(
|
||||||
SlideUpMenuItem(container.context,
|
container.context, container.context.getString(R.string.add_to), "addto",
|
||||||
|
SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
R.drawable.ic_queue_add,
|
R.drawable.ic_queue_add,
|
||||||
container.context.getString(R.string.add_to_queue),
|
container.context.getString(R.string.add_to_queue),
|
||||||
"${queue.size} " + container.context.getString(R.string.videos),
|
"${queue.size} " + container.context.getString(R.string.videos),
|
||||||
tag = "queue",
|
tag = "queue",
|
||||||
call = { StatePlayer.instance.addToQueue(video); }),
|
call = { StatePlayer.instance.addToQueue(video); }),
|
||||||
SlideUpMenuItem(container.context,
|
SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
R.drawable.ic_watchlist_add,
|
R.drawable.ic_watchlist_add,
|
||||||
"${container.context.getString(R.string.add_to)} " + StatePlayer.TYPE_WATCHLATER + "",
|
"${container.context.getString(R.string.add_to)} " + StatePlayer.TYPE_WATCHLATER + "",
|
||||||
"${watchLater.size} " + container.context.getString(R.string.videos),
|
"${watchLater.size} " + container.context.getString(R.string.videos),
|
||||||
tag = "watch later",
|
tag = "watch later",
|
||||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }),
|
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }),
|
||||||
SlideUpMenuItem(container.context,
|
SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
R.drawable.ic_history,
|
R.drawable.ic_history,
|
||||||
container.context.getString(R.string.add_to_history),
|
container.context.getString(R.string.add_to_history),
|
||||||
"Mark as watched",
|
"Mark as watched",
|
||||||
tag = "history",
|
tag = "history",
|
||||||
call = { StateHistory.instance.markAsWatched(video); }),
|
call = { StateHistory.instance.markAsWatched(video); }),
|
||||||
));
|
));
|
||||||
|
|
||||||
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
||||||
playlistItems.add(SlideUpMenuItem(
|
playlistItems.add(SlideUpMenuItem(
|
||||||
|
@ -1033,22 +1097,26 @@ class UISlideOverlays {
|
||||||
val queue = StatePlayer.instance.getQueue();
|
val queue = StatePlayer.instance.getQueue();
|
||||||
val watchLater = StatePlaylists.instance.getWatchLater();
|
val watchLater = StatePlaylists.instance.getWatchLater();
|
||||||
items.add(
|
items.add(
|
||||||
SlideUpMenuGroup(container.context, container.context.getString(R.string.other), "other",
|
SlideUpMenuGroup(
|
||||||
SlideUpMenuItem(container.context,
|
container.context, container.context.getString(R.string.other), "other",
|
||||||
|
SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
R.drawable.ic_queue_add,
|
R.drawable.ic_queue_add,
|
||||||
container.context.getString(R.string.queue),
|
container.context.getString(R.string.queue),
|
||||||
"${queue.size} " + container.context.getString(R.string.videos),
|
"${queue.size} " + container.context.getString(R.string.videos),
|
||||||
tag = "queue",
|
tag = "queue",
|
||||||
call = { StatePlayer.instance.addToQueue(video); }),
|
call = { StatePlayer.instance.addToQueue(video); }),
|
||||||
SlideUpMenuItem(container.context,
|
SlideUpMenuItem(
|
||||||
|
container.context,
|
||||||
R.drawable.ic_watchlist_add,
|
R.drawable.ic_watchlist_add,
|
||||||
StatePlayer.TYPE_WATCHLATER,
|
StatePlayer.TYPE_WATCHLATER,
|
||||||
"${watchLater.size} " + container.context.getString(R.string.videos),
|
"${watchLater.size} " + container.context.getString(R.string.videos),
|
||||||
tag = "watch later",
|
tag = "watch later",
|
||||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true);
|
call = {
|
||||||
|
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true);
|
||||||
UIDialogs.appToast("Added to watch later", false);
|
UIDialogs.appToast("Added to watch later", false);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
||||||
|
|
|
@ -15,18 +15,14 @@ import com.futo.platformplayer.toYesNo
|
||||||
import com.futo.platformplayer.yesNoToBoolean
|
import com.futo.platformplayer.yesNoToBoolean
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLConnection
|
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import kotlin.text.ifEmpty
|
||||||
|
|
||||||
class HLS {
|
class HLS {
|
||||||
companion object {
|
companion object {
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
fun parseMasterPlaylist(masterPlaylistContent: String, sourceUrl: String, isAudioSource: Boolean? = null): MasterPlaylist {
|
fun parseMasterPlaylist(masterPlaylistContent: String, sourceUrl: String): MasterPlaylist {
|
||||||
val inputStream = ByteArrayInputStream(masterPlaylistContent.toByteArray())
|
|
||||||
val playlist = DefaultHlsPlaylistParserFactory().createPlaylistParser()
|
|
||||||
.parse(Uri.parse(sourceUrl), inputStream)
|
|
||||||
|
|
||||||
val baseUrl = URI(sourceUrl).resolve("./").toString()
|
val baseUrl = URI(sourceUrl).resolve("./").toString()
|
||||||
|
|
||||||
val variantPlaylists = mutableListOf<VariantPlaylistReference>()
|
val variantPlaylists = mutableListOf<VariantPlaylistReference>()
|
||||||
|
@ -34,36 +30,27 @@ class HLS {
|
||||||
val sessionDataList = mutableListOf<SessionData>()
|
val sessionDataList = mutableListOf<SessionData>()
|
||||||
var independentSegments = false
|
var independentSegments = false
|
||||||
|
|
||||||
if (playlist is HlsMediaPlaylist) {
|
masterPlaylistContent.lines().forEachIndexed { index, line ->
|
||||||
independentSegments = playlist.hasIndependentSegments
|
when {
|
||||||
if (isAudioSource == true) {
|
line.startsWith("#EXT-X-STREAM-INF") -> {
|
||||||
mediaRenditions.add(MediaRendition("AUDIO", playlist.baseUri, "Single Playlist", null, null, null, null, null))
|
val nextLine = masterPlaylistContent.lines().getOrNull(index + 1)
|
||||||
} else {
|
?: throw Exception("Expected URI following #EXT-X-STREAM-INF, found none")
|
||||||
variantPlaylists.add(VariantPlaylistReference(playlist.baseUri, StreamInfo(null, null, null, null, null, null, null, null, null)))
|
val url = resolveUrl(baseUrl, nextLine)
|
||||||
}
|
|
||||||
} else if (playlist is HlsMultivariantPlaylist) {
|
|
||||||
masterPlaylistContent.lines().forEachIndexed { index, line ->
|
|
||||||
when {
|
|
||||||
line.startsWith("#EXT-X-STREAM-INF") -> {
|
|
||||||
val nextLine = masterPlaylistContent.lines().getOrNull(index + 1)
|
|
||||||
?: throw Exception("Expected URI following #EXT-X-STREAM-INF, found none")
|
|
||||||
val url = resolveUrl(baseUrl, nextLine)
|
|
||||||
|
|
||||||
variantPlaylists.add(VariantPlaylistReference(url, parseStreamInfo(line)))
|
variantPlaylists.add(VariantPlaylistReference(url, parseStreamInfo(line)))
|
||||||
}
|
}
|
||||||
|
|
||||||
line.startsWith("#EXT-X-MEDIA") -> {
|
line.startsWith("#EXT-X-MEDIA") -> {
|
||||||
mediaRenditions.add(parseMediaRendition(line, baseUrl))
|
mediaRenditions.add(parseMediaRendition(line, baseUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
line == "#EXT-X-INDEPENDENT-SEGMENTS" -> {
|
line == "#EXT-X-INDEPENDENT-SEGMENTS" -> {
|
||||||
independentSegments = true
|
independentSegments = true
|
||||||
}
|
}
|
||||||
|
|
||||||
line.startsWith("#EXT-X-SESSION-DATA") -> {
|
line.startsWith("#EXT-X-SESSION-DATA") -> {
|
||||||
val sessionData = parseSessionData(line)
|
val sessionData = parseSessionData(line)
|
||||||
sessionDataList.add(sessionData)
|
sessionDataList.add(sessionData)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,6 +58,31 @@ class HLS {
|
||||||
return MasterPlaylist(variantPlaylists, mediaRenditions, sessionDataList, independentSegments)
|
return MasterPlaylist(variantPlaylists, mediaRenditions, sessionDataList, independentSegments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mediaRenditionToVariant(rendition: MediaRendition): HLSVariantAudioUrlSource? {
|
||||||
|
if (rendition.uri == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val suffix = listOf(rendition.language, rendition.groupID).mapNotNull { x -> x?.ifEmpty { null } }.joinToString(", ")
|
||||||
|
return when (rendition.type) {
|
||||||
|
"AUDIO" -> HLSVariantAudioUrlSource(rendition.name?.ifEmpty { "Audio (${suffix})" } ?: "Audio (${suffix})", 0, "application/vnd.apple.mpegurl", "", rendition.language ?: "", null, false, rendition.uri)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun variantReferenceToVariant(reference: VariantPlaylistReference): HLSVariantVideoUrlSource {
|
||||||
|
var width: Int? = null
|
||||||
|
var height: Int? = null
|
||||||
|
val resolutionTokens = reference.streamInfo.resolution?.split('x')
|
||||||
|
if (resolutionTokens?.isNotEmpty() == true) {
|
||||||
|
width = resolutionTokens[0].toIntOrNull()
|
||||||
|
height = resolutionTokens[1].toIntOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
val suffix = listOf(reference.streamInfo.video, reference.streamInfo.codecs).mapNotNull { x -> x?.ifEmpty { null } }.joinToString(", ")
|
||||||
|
return HLSVariantVideoUrlSource(suffix, width ?: 0, height ?: 0, "application/vnd.apple.mpegurl", reference.streamInfo.codecs ?: "", reference.streamInfo.bandwidth, 0, false, reference.url)
|
||||||
|
}
|
||||||
|
|
||||||
fun parseVariantPlaylist(content: String, sourceUrl: String): VariantPlaylist {
|
fun parseVariantPlaylist(content: String, sourceUrl: String): VariantPlaylist {
|
||||||
val lines = content.lines()
|
val lines = content.lines()
|
||||||
val version = lines.find { it.startsWith("#EXT-X-VERSION:") }?.substringAfter(":")?.toIntOrNull()
|
val version = lines.find { it.startsWith("#EXT-X-VERSION:") }?.substringAfter(":")?.toIntOrNull()
|
||||||
|
@ -137,10 +149,10 @@ class HLS {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseAndGetAudioSources(source: Any, content: String, url: String, isAudioSource: Boolean? = null): List<HLSVariantAudioUrlSource> {
|
fun parseAndGetAudioSources(source: Any, content: String, url: String): List<HLSVariantAudioUrlSource> {
|
||||||
val masterPlaylist: MasterPlaylist
|
val masterPlaylist: MasterPlaylist
|
||||||
try {
|
try {
|
||||||
masterPlaylist = parseMasterPlaylist(content, url, isAudioSource)
|
masterPlaylist = parseMasterPlaylist(content, url)
|
||||||
return masterPlaylist.getAudioSources()
|
return masterPlaylist.getAudioSources()
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (content.lines().any { it.startsWith("#EXTINF:") }) {
|
if (content.lines().any { it.startsWith("#EXTINF:") }) {
|
||||||
|
@ -347,30 +359,13 @@ class HLS {
|
||||||
|
|
||||||
fun getVideoSources(): List<HLSVariantVideoUrlSource> {
|
fun getVideoSources(): List<HLSVariantVideoUrlSource> {
|
||||||
return variantPlaylistsRefs.map {
|
return variantPlaylistsRefs.map {
|
||||||
var width: Int? = null
|
variantReferenceToVariant(it)
|
||||||
var height: Int? = null
|
|
||||||
val resolutionTokens = it.streamInfo.resolution?.split('x')
|
|
||||||
if (resolutionTokens?.isNotEmpty() == true) {
|
|
||||||
width = resolutionTokens[0].toIntOrNull()
|
|
||||||
height = resolutionTokens[1].toIntOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
val suffix = listOf(it.streamInfo.video, it.streamInfo.codecs).mapNotNull { x -> x?.ifEmpty { null } }.joinToString(", ")
|
|
||||||
HLSVariantVideoUrlSource(suffix, width ?: 0, height ?: 0, "application/vnd.apple.mpegurl", it.streamInfo.codecs ?: "", it.streamInfo.bandwidth, 0, false, it.url)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAudioSources(): List<HLSVariantAudioUrlSource> {
|
fun getAudioSources(): List<HLSVariantAudioUrlSource> {
|
||||||
return mediaRenditions.mapNotNull {
|
return mediaRenditions.mapNotNull {
|
||||||
if (it.uri == null) {
|
return@mapNotNull mediaRenditionToVariant(it)
|
||||||
return@mapNotNull null
|
|
||||||
}
|
|
||||||
|
|
||||||
val suffix = listOf(it.language, it.groupID).mapNotNull { x -> x?.ifEmpty { null } }.joinToString(", ")
|
|
||||||
return@mapNotNull when (it.type) {
|
|
||||||
"AUDIO" -> HLSVariantAudioUrlSource(it.name?.ifEmpty { "Audio (${suffix})" } ?: "Audio (${suffix})", 0, "application/vnd.apple.mpegurl", "", it.language ?: "", null, false, it.uri)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue