@@ -3,14 +3,11 @@ package org.schabi.newpipe.util
33import android.content.Context
44import android.content.res.Resources
55import android.net.ConnectivityManager
6+ import android.util.Log
67import androidx.annotation.StringRes
78import androidx.core.content.ContextCompat
89import androidx.preference.PreferenceManager
9- import java.util.Collections
1010import java.util.Locale
11- import java.util.Objects
12- import java.util.function.Predicate
13- import java.util.stream.Collectors
1411import org.schabi.newpipe.MainActivity
1512import org.schabi.newpipe.R
1613import org.schabi.newpipe.extractor.MediaFormat
@@ -22,6 +19,8 @@ import org.schabi.newpipe.extractor.stream.Stream
2219import org.schabi.newpipe.extractor.stream.VideoStream
2320
2421object ListHelper {
22+ private const val TAG = " ListHelper"
23+
2524 // Video format in order of quality. 0=lowest quality, n=highest quality
2625 private val VIDEO_FORMAT_QUALITY_RANKING =
2726 listOf (MediaFormat .v3GPP, MediaFormat .WEBM , MediaFormat .MPEG_4 )
@@ -70,6 +69,8 @@ object ListHelper {
7069 278 , 242 , 243 , 244 , 245 , 246 , 247 , 248 , 271 , 272 , 302 , 303 , 308 , 313 , 315
7170 )
7271
72+ private val QUALITY_REGEX = Regex (""" ^(\d+)p(\d+)?(?:@(\d+)([km])?)?$""" , RegexOption .IGNORE_CASE )
73+
7374 /* *
7475 * @param context Android app context
7576 * @param videoStreams list of the video streams to check
@@ -197,10 +198,9 @@ object ListHelper {
197198 streamList : List <S >? ,
198199 deliveryMethod : DeliveryMethod
199200 ): List <S > {
200- return getFilteredStreamList(
201- streamList
202- )
203- { stream -> stream.deliveryMethod == deliveryMethod }
201+ return getFilteredStreamList(streamList) { stream ->
202+ stream.deliveryMethod == deliveryMethod
203+ }
204204 }
205205
206206 /* *
@@ -214,10 +214,9 @@ object ListHelper {
214214 fun <S : Stream > getUrlAndNonTorrentStreams (
215215 streamList : List <S >?
216216 ): List <S > {
217- return getFilteredStreamList(
218- streamList
219- )
220- { stream -> stream.isUrl && stream.deliveryMethod != DeliveryMethod .TORRENT }
217+ return getFilteredStreamList(streamList) { stream ->
218+ stream.isUrl && stream.deliveryMethod != DeliveryMethod .TORRENT
219+ }
221220 }
222221
223222 /* *
@@ -238,19 +237,15 @@ object ListHelper {
238237 serviceId : Int
239238 ): List <S > {
240239 val youtubeServiceId = ServiceList .YouTube .serviceId
241- return getFilteredStreamList(
242- streamList
243- )
244- { stream ->
240+ return getFilteredStreamList(streamList) { stream ->
245241 stream.deliveryMethod != DeliveryMethod .TORRENT &&
246242 (
247243 stream.deliveryMethod != DeliveryMethod .HLS ||
248244 stream.format != MediaFormat .OPUS
249245 ) &&
250246 (
251247 serviceId != youtubeServiceId ||
252- stream.itagItem == null ||
253- SUPPORTED_ITAG_IDS .contains(stream.itagItem!! .id)
248+ stream.itagItem?.id?.let { SUPPORTED_ITAG_IDS .contains(it) } != false
254249 )
255250 }
256251 }
@@ -351,9 +346,9 @@ object ListHelper {
351346 audioStreams : List <AudioStream >?
352347 ): List <AudioStream > {
353348 if (audioStreams == null ) {
354- return Collections . emptyList()
349+ return emptyList()
355350 }
356- val collectedStreams: HashMap <String , AudioStream > = HashMap ()
351+ val collectedStreams = mutableMapOf <String , AudioStream >()
357352 val cmp = getAudioFormatComparator(context)
358353 for (stream in audioStreams) {
359354 if (stream.deliveryMethod == DeliveryMethod .TORRENT ||
@@ -364,7 +359,7 @@ object ListHelper {
364359 ) {
365360 continue
366361 }
367- val trackId = Objects .toString( stream.audioTrackId, " " )
362+ val trackId = stream.audioTrackId ? : " "
368363 val presentStream = collectedStreams[trackId]
369364 if (presentStream == null || cmp.compare(stream, presentStream) > 0 ) {
370365 collectedStreams[trackId] = stream
@@ -391,19 +386,11 @@ object ListHelper {
391386 audioStreams : List <AudioStream >?
392387 ): List <List <AudioStream >> {
393388 if (audioStreams == null ) {
394- return Collections .emptyList()
395- }
396- val collectedStreams: HashMap <String , MutableList <AudioStream >> = HashMap ()
397- for (stream in audioStreams) {
398- val trackId = Objects .toString(stream.audioTrackId, " " )
399- if (collectedStreams.containsKey(trackId)) {
400- collectedStreams[trackId]!! .add(stream)
401- } else {
402- val list: MutableList <AudioStream > = ArrayList ()
403- list.add(stream)
404- collectedStreams[trackId] = list
405- }
389+ return emptyList()
406390 }
391+ val collectedStreams = audioStreams
392+ .groupBy { it.audioTrackId ? : " " }
393+ .toMutableMap()
407394 // Filter unknown audio tracks if there are multiple tracks
408395 if (collectedStreams.size > 1 ) {
409396 collectedStreams.remove(" " )
@@ -421,23 +408,21 @@ object ListHelper {
421408 // ////////////////////////////////////////////////////////////////////////
422409
423410 /* *
424- * Get a filtered stream list, by using Java 8 Stream's API and the given predicate.
411+ * Get a filtered stream list using the given predicate.
425412 *
426413 * @param streamList the stream list to filter
427- * @param streamListPredicate the predicate which will be used to filter streams
414+ * @param predicate the predicate which will be used to filter streams
428415 * @param <S> the item type's class that extends [Stream]
429416 * @return a new stream list filtered using the given predicate
430417 */
431418 private fun <S : Stream > getFilteredStreamList (
432419 streamList : List <S >? ,
433- streamListPredicate : Predicate < S >
420+ predicate : ( S ) -> Boolean
434421 ): List <S > {
435422 if (streamList == null ) {
436- return Collections . emptyList()
423+ return emptyList()
437424 }
438- return streamList.stream()
439- .filter(streamListPredicate)
440- .collect(Collectors .toList())
425+ return streamList.filter(predicate)
441426 }
442427
443428 private fun computeDefaultResolution (
@@ -569,8 +554,8 @@ object ListHelper {
569554 // preferred. They might have been overridden if allInitialStreams has more than one stream
570555 // for the same resolution key but a none 'defaultFormat' stream was added later.
571556 // See 'qualityKeyOf'.
572- defaultFormat?.let { defaultFormat ->
573- allInitialStreams.filter { it.stream.format == defaultFormat }
557+ defaultFormat?.let { fmt ->
558+ allInitialStreams.filter { it.stream.format == fmt }
574559 .forEach { streamsWithDefaultFormatPreferred[qualityKeyOf(it)] = it }
575560 }
576561
@@ -641,7 +626,7 @@ object ListHelper {
641626 * The algorithm iterates over all available streams and assigns each one
642627 * a "priority class". Lower numbers represent a better match.
643628 *
644- * Matching priority (best → worst):
629+ * Matching priority (best -> worst):
645630 *
646631 * 1. Format + resolution + fps + exact bitrate
647632 * 2. Format + resolution + fps
@@ -797,39 +782,32 @@ object ListHelper {
797782 val defaultFormatString = preferences.getString(
798783 context.getString(defaultFormatKey),
799784 defaultFormat
800- )
801- return getMediaFormatFromKey(context, defaultFormatString!! )
785+ ) ? : defaultFormat
786+ return getMediaFormatFromKey(context, defaultFormatString)
802787 }
803788
804789 private fun getMediaFormatFromKey (
805790 context : Context ,
806791 formatKey : String
807792 ): MediaFormat ? {
808- var format: MediaFormat ? = null
809- when (formatKey) {
810- context.getString(R .string.video_webm_key) -> {
811- format = MediaFormat .WEBM
812- }
813-
814- context.getString(R .string.video_mp4_key) -> {
815- format = MediaFormat .MPEG_4
816- }
817-
818- context.getString(R .string.video_3gp_key) -> {
819- format = MediaFormat .v3GPP
820- }
821-
822- context.getString(R .string.audio_webm_key) -> {
823- format = MediaFormat .WEBMA
824- }
825-
826- context.getString(R .string.audio_m4a_key) -> {
827- format = MediaFormat .M4A
828- }
793+ return when (formatKey) {
794+ context.getString(R .string.video_webm_key) -> MediaFormat .WEBM
795+ context.getString(R .string.video_mp4_key) -> MediaFormat .MPEG_4
796+ context.getString(R .string.video_3gp_key) -> MediaFormat .v3GPP
797+ context.getString(R .string.audio_webm_key) -> MediaFormat .WEBMA
798+ context.getString(R .string.audio_m4a_key) -> MediaFormat .M4A
799+ else -> null
829800 }
830- return format
831801 }
832802
803+ /* *
804+ * Compares two video stream resolution strings in descending order.
805+ *
806+ * Returns a negative value if r1 has higher quality than r2, zero if equal,
807+ * and a positive value if r1 has lower quality than r2.
808+ * Note: arguments are intentionally compared in reverse order (r2 vs r1)
809+ * to produce descending comparison, matching the original Java behavior.
810+ */
833811 private fun compareVideoStreamResolution (
834812 r1 : String ,
835813 r2 : String
@@ -855,34 +833,31 @@ object ListHelper {
855833 * @param context App context
856834 * @return maximum resolution allowed or null if there is no maximum
857835 */
858- fun getResolutionLimit (context : Context ): String? {
859- var resolutionLimit: String? = null
860- if (isMeteredNetwork(context)) {
861- val preferences =
862- PreferenceManager .getDefaultSharedPreferences(context)
863- val defValue = context.getString(R .string.limit_data_usage_none_key)
864- val value = preferences.getString(
865- context.getString(R .string.limit_mobile_data_usage_key),
866- defValue
867- )
868- resolutionLimit = if (defValue == value) null else value
836+ private fun getResolutionLimit (context : Context ): String? {
837+ if (! isMeteredNetwork(context)) {
838+ return null
869839 }
870- return resolutionLimit
840+ val preferences =
841+ PreferenceManager .getDefaultSharedPreferences(context)
842+ val defValue = context.getString(R .string.limit_data_usage_none_key)
843+ val value = preferences.getString(
844+ context.getString(R .string.limit_mobile_data_usage_key),
845+ defValue
846+ )
847+ return if (defValue == value) null else value
871848 }
872849
873850 /* *
874851 * The current network is metered (like mobile data)?
875852 *
876853 * @param context App context
877- * @return {@code true} if connected to a metered network
854+ * @return ` true` if connected to a metered network
878855 */
879856 @JvmStatic
880857 fun isMeteredNetwork (context : Context ): Boolean {
881858 val manager =
882859 ContextCompat .getSystemService(context, ConnectivityManager ::class .java)
883- if (manager == null || manager.activeNetworkInfo == null ) {
884- return false
885- }
860+ ? : return false
886861 return manager.isActiveNetworkMetered
887862 }
888863
@@ -933,7 +908,7 @@ object ListHelper {
933908 { it.format },
934909 Comparator { o1: MediaFormat ? , o2: MediaFormat ? ->
935910 if (defaultFormat != null ) {
936- java.lang. Boolean .compare (o1 == defaultFormat, o2 == defaultFormat)
911+ (o1 == defaultFormat).compareTo( o2 == defaultFormat)
937912 } else {
938913 0
939914 }
@@ -1020,10 +995,7 @@ object ListHelper {
1020995 { it.audioTrackType },
1021996 Comparator { o1: AudioTrackType ? , o2: AudioTrackType ? ->
1022997 if (preferOriginalAudio) {
1023- java.lang.Boolean .compare(
1024- o1 == AudioTrackType .ORIGINAL ,
1025- o2 == AudioTrackType .ORIGINAL
1026- )
998+ (o1 == AudioTrackType .ORIGINAL ).compareTo(o2 == AudioTrackType .ORIGINAL )
1027999 } else {
10281000 0
10291001 }
@@ -1066,20 +1038,6 @@ object ListHelper {
10661038 )
10671039 }
10681040
1069- // ------Extensions--------
1070- fun VideoStream.toQuality (): VideoQuality {
1071- return parseQuality(getResolution(), format)
1072- }
1073-
1074- /* *
1075- * Extension on Iterable<VideoStream> that maps each VideoStream
1076- * to a [VideoStreamWithQuality] using its toQuality() function.
1077- * This avoids repeatedly parsing the quality string during matching.
1078- */
1079- fun Iterable<VideoStream>.wrapWithQuality (): List <VideoStreamWithQuality > {
1080- return this .map { stream -> VideoStreamWithQuality (stream, stream.toQuality()) }
1081- }
1082-
10831041 // --------data classes----------
10841042
10851043 /* *
@@ -1097,16 +1055,26 @@ object ListHelper {
10971055 val formatRank : Int
10981056 )
10991057
1100- // -------- helper ------------
1058+ // -------- private helpers ------------
11011059
1102- val QUALITY_REGEX = Regex (""" ^(\d+)p(\d+)?(?:@(\d+)([km])?)?$""" , RegexOption .IGNORE_CASE )
1060+ private fun VideoStream.toQuality (): VideoQuality {
1061+ return parseQuality(getResolution(), format)
1062+ }
1063+
1064+ /* *
1065+ * Maps each VideoStream to a [VideoStreamWithQuality] using its toQuality() function.
1066+ * This avoids repeatedly parsing the quality string during matching.
1067+ */
1068+ private fun Iterable<VideoStream>.wrapWithQuality (): List <VideoStreamWithQuality > {
1069+ return map { stream -> VideoStreamWithQuality (stream, stream.toQuality()) }
1070+ }
11031071
11041072 /* *
11051073 * Parses a video quality string into a [VideoQuality] object.
11061074 *
11071075 * Supports strings like: `"720p"`, `"720p60"`, `"720p60@1500k"` or `"1080p@2m"`.
11081076 * The components represent resolution, optional fps, and optional bitrate.
1109- * Bitrate units `k` and `m` are interpreted as ×1000 and ×1_000_000 respectively.
1077+ * Bitrate units `k` and `m` are interpreted as x1000 and x1_000_000 respectively.
11101078 *
11111079 * @param resFpsBitrate string to parse for quality information (e.g. `"720p60@1500k"`), may be null
11121080 * @param format optional media format used to determine the format rank
@@ -1121,7 +1089,9 @@ object ListHelper {
11211089
11221090 val match = QUALITY_REGEX .matchEntire(resFpsBitrateStr)
11231091 ? : run {
1124- if (MainActivity .DEBUG ) println (" QualityParser" + " Cannot parse: \" $resFpsBitrateStr \" " )
1092+ if (MainActivity .DEBUG ) {
1093+ Log .d(TAG , " Cannot parse quality: \" $resFpsBitrateStr \" " )
1094+ }
11251095 return VideoQuality (0 , 0 , 0L , - 1 )
11261096 }
11271097
@@ -1138,7 +1108,9 @@ object ListHelper {
11381108 else -> bitrate
11391109 }
11401110 } catch (e: ArithmeticException ) {
1141- if (MainActivity .DEBUG ) println (" QualityParser" + " Bitrate overflow in \" $resFpsBitrateStr \" " )
1111+ if (MainActivity .DEBUG ) {
1112+ Log .d(TAG , " Bitrate overflow in \" $resFpsBitrateStr \" " , e)
1113+ }
11421114 bitrate = 0L
11431115 }
11441116 }
0 commit comments