Skip to content

Commit a36a046

Browse files
authored
Merge branch 'recloudstream:master' into master
2 parents d188328 + 3dd7467 commit a36a046

2 files changed

Lines changed: 45 additions & 10 deletions

File tree

library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,14 +1284,48 @@ object HlsPlaylistParser {
12841284
val subtitleGroupId: String?,
12851285
val captionGroupId: String?,
12861286
) {
1287-
fun isPlayableStandalone(): Boolean = containsAudio() && !isTrickPlay()
1287+
/** This is unfortunately impossible to do 100%, given that audio detection is hard without TS inspection,
1288+
* however this is a generous safety abstraction */
1289+
fun isPlayableStandalone(playlist: HlsMultivariantPlaylist): Boolean =
1290+
mustContainAudio(playlist) && !isTrickPlay()
12881291

1289-
// Trick play is "visual feedback while they are rewinding or fast-forwarding a stream",
1290-
// This more or less means thumbnails
1292+
/**
1293+
* https://datatracker.ietf.org/doc/html/rfc8216#section-4.3.3.6
1294+
* > The EXT-X-I-FRAMES-ONLY tag indicates that each Media Segment in the
1295+
* > Playlist describes a single I-frame. I-frames are encoded video
1296+
* > frames whose encoding does not depend on any other frame. I-frame
1297+
* > Playlists can be used for trick play, such as fast forward, rapid
1298+
* > reverse, and scrubbing.
1299+
*/
12911300
fun isTrickPlay(): Boolean = (format.roleFlags and C.ROLE_FLAG_TRICK_PLAY != 0)
12921301

1293-
fun containsAudio(): Boolean = (audioGroupId == null || format.codecs?.split(",")
1294-
?.any { MimeTypes.isAudio(MimeTypes.getMediaMimeType(it)) } == true)
1302+
/**
1303+
https://datatracker.ietf.org/doc/html/rfc6381:
1304+
> When the 'codecs' parameter is used, it MUST contain all codecs
1305+
> indicated by the content present in the body part. The 'codecs'
1306+
> parameter MUST NOT include any codecs that are not indicated by any
1307+
> media elements in the body part.
1308+
1309+
This means that codecs cant be used
1310+
"|| format.codecs?.split(",")?.any { MimeTypes.isAudio(MimeTypes.getMediaMimeType(it)) } == true"
1311+
They may be used for harsher restriction on "audioGroupId == null", but codecs is optional
1312+
1313+
https://datatracker.ietf.org/doc/html/rfc8216
1314+
> Since the EXT-X-STREAM-INF tag has no AUDIO attribute, all video
1315+
> Renditions would be required to contain the audio.
1316+
1317+
However it may still contain audio with the AUDIO attribute, therefore we also check the audio with that groupId:
1318+
1319+
> If the media type is VIDEO or AUDIO, a missing URI attribute
1320+
> indicates that the media data for this Rendition is included in the
1321+
> Media Playlist of any EXT-X-STREAM-INF tag referencing this EXT-
1322+
> X-MEDIA tag.
1323+
1324+
But this is not foolproof, because TS segments needs to be investigated to be sure as I do not see any
1325+
way to detect this from the m3u8 playlist
1326+
*/
1327+
fun mustContainAudio(playlist: HlsMultivariantPlaylist): Boolean =
1328+
audioGroupId == null || (playlist.audios.firstOrNull { it.groupId == audioGroupId }?.url == null)
12951329
}
12961330

12971331
data class Rendition(

library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,15 @@ object M3u8Helper2 {
134134
var anyFound = false
135135
if (parsed != null) {
136136
for (video in parsed.variants) {
137-
// The m3u8 should not be split it that causes a loss of audio
138-
if (!video.isPlayableStandalone()) {
137+
// The m3u8 should not be split it that causes a loss of audio, however this can not be done reliably
138+
// Therefore that should be figured out on the extension level, so only "trick play" is checked for
139+
if (video.isTrickPlay()) {
139140
//println("Denied m3u8Generation, isTrickPlay = ${video.isTrickPlay()} containsAudio = ${video.containsAudio()}, codec = ${video.format.codecs}, url = ${video.url}")
140141
continue
141142
}
142143

143144
anyFound = true
144-
val quality = video.format.width
145+
val quality = video.format.height
145146
list.add(
146147
M3u8Helper.M3u8Stream(
147148
streamUrl = video.url.toString(),
@@ -265,7 +266,7 @@ object M3u8Helper2 {
265266
// find first with no audio group if audio is required, as otherwise muxing is required
266267
// as m3u8 files can include separate tracks for dubs/subs
267268
val variants = if (requireAudio) {
268-
parsed.variants.filter { it.isPlayableStandalone() }
269+
parsed.variants.filter { it.isPlayableStandalone(parsed) }
269270
} else {
270271
parsed.variants.filter { !it.isTrickPlay() }
271272
}
@@ -291,7 +292,7 @@ object M3u8Helper2 {
291292
variants.minBy { (it.format.width * it.format.height).toLong() * 1000L + it.format.averageBitrate.toLong() }
292293
}
293294

294-
val quality = bestVideo.format.width
295+
val quality = bestVideo.format.height
295296
return hslLazy(
296297
playlistStream = M3u8Helper.M3u8Stream(
297298
bestVideo.url.toString(),

0 commit comments

Comments
 (0)