Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,51 @@ object VideoFilterSorter {
}

private fun sortVideos(videos: List<VideoItem>, opts: FilterSortOptions): List<VideoItem> {
return videos.sortedWith { a, b ->
val aWatched = isItemWatched(a, opts.watchedVideos)
val bWatched = isItemWatched(b, opts.watchedVideos)
val watchedCache = videos.associateWith { isItemWatched(it, opts.watchedVideos) }
val watchedComparator = Comparator<VideoItem> { a, b ->
val aWatched = watchedCache[a] ?: false
val bWatched = watchedCache[b] ?: false
if (aWatched != bWatched) {
if (aWatched) 1 else -1
} else {
compareItemsByCriteria(a, b, opts)
0
}
}
val criteriaComparator = getCriteriaComparator(videos, opts)
return videos.sortedWith(watchedComparator.then(criteriaComparator))
}

private fun getCriteriaComparator(
videos: List<VideoItem>,
opts: FilterSortOptions,
): Comparator<VideoItem> {
return when (opts.sortBy) {
SortBy.ALPHA -> {
val alphaCache = videos.associateWith { if (it.isSeriesGroup) it.seriesName.orEmpty() else it.name }
compareBy { alphaCache[it].orEmpty() }
}
SortBy.DATE -> {
val dateCache = videos.associateWith {
val lookup = if (it.isSeriesGroup) it.seriesName.orEmpty() else it.name
opts.releaseDates[lookup] ?: it.lastModified.toString()
}
compareByDescending { dateCache[it].orEmpty() }
}
SortBy.SIZE -> {
val sizeCache = videos.associateWith {
if (it.isSeriesGroup) it.episodes.orEmpty().sumOf { ep -> ep.size } else it.size
}
compareByDescending { sizeCache[it] ?: 0L }
}
SortBy.DURATION -> {
val durationCache = videos.associateWith {
if (it.isSeriesGroup) {
it.episodes.orEmpty().sumOf { ep -> opts.videoDurations[ep.name] ?: 0L }
} else {
opts.videoDurations[it.name] ?: 0L
}
}
compareByDescending { durationCache[it] ?: 0L }
}
}
}
Expand All @@ -58,40 +96,5 @@ object VideoFilterSorter {
watchedVideos[v.name] == true
}
}

private fun compareItemsByCriteria(a: VideoItem, b: VideoItem, opts: FilterSortOptions): Int {
return when (opts.sortBy) {
SortBy.ALPHA -> compareAlpha(a, b)
SortBy.DATE -> compareDate(a, b, opts.releaseDates)
SortBy.SIZE -> compareSize(a, b)
SortBy.DURATION -> compareDuration(a, b, opts.videoDurations)
}
}

private fun compareAlpha(a: VideoItem, b: VideoItem): Int {
val nameA = if (a.isSeriesGroup) a.seriesName ?: "" else a.name
val nameB = if (b.isSeriesGroup) b.seriesName ?: "" else b.name
return nameA.compareTo(nameB)
}

private fun compareDate(a: VideoItem, b: VideoItem, releaseDates: Map<String, String>): Int {
val lookupA = if (a.isSeriesGroup) a.seriesName ?: "" else a.name
val lookupB = if (b.isSeriesGroup) b.seriesName ?: "" else b.name
val dateA = releaseDates[lookupA] ?: a.lastModified.toString()
val dateB = releaseDates[lookupB] ?: b.lastModified.toString()
return dateB.compareTo(dateA)
}

private fun compareSize(a: VideoItem, b: VideoItem): Int {
val sizeA = if (a.isSeriesGroup) a.episodes.orEmpty().sumOf { it.size } else a.size
val sizeB = if (b.isSeriesGroup) b.episodes.orEmpty().sumOf { it.size } else b.size
return sizeB.compareTo(sizeA)
}

private fun compareDuration(a: VideoItem, b: VideoItem, videoDurations: Map<String, Long>): Int {
val durA = if (a.isSeriesGroup) a.episodes.orEmpty().sumOf { videoDurations[it.name] ?: 0L } else videoDurations[a.name] ?: 0L
val durB = if (b.isSeriesGroup) b.episodes.orEmpty().sumOf { videoDurations[it.name] ?: 0L } else videoDurations[b.name] ?: 0L
return durB.compareTo(durA)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class HistoryViewModel(
container.settingsRepository.observeForceAvailable,
) { watchedSet: Set<String>, playbackMap: Map<String, PlaybackStateEntity>, diskVideos: List<VideoItem>, forceSet: Set<String> ->
val allNames = (watchedSet + playbackMap.keys).distinct()
val diskVideoNames = diskVideos.map { it.name }.toSet()
val diskVideoMap = diskVideos.associateBy { it.name }
val diskVideoNames = diskVideoMap.keys

val historyItems = allNames.map { name ->
val pb = playbackMap[name]
Expand All @@ -51,7 +52,7 @@ class HistoryViewModel(
val isForceAvailable = forceSet.contains(name)
val effectiveAvailable = isDiskAvailable || isForceAvailable

val diskVideo = diskVideos.find { it.name == name }
val diskVideo = diskVideoMap[name]
val durationMs = (diskVideo?.duration ?: 0L) * 1000L
val positionMs = pb?.positionMs ?: 0L
val progressPercent = if (durationMs > 0L) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ class PlaylistsViewModel(
container.videoRepository.observeVideos,
) { playlists, selectedId, videos ->
val selected = playlists.find { it.id == selectedId }
val videoMap = videos.associateBy { it.name }
val playlistVideos = selected?.videoNames?.mapNotNull { name ->
videos.find { it.name == name }
videoMap[name]
} ?: emptyList()

PlaylistsUiState(
Expand Down
29 changes: 29 additions & 0 deletions native/app/src/test/java/com/localstream/app/domain/SortingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,34 @@ class SortingTest {
assertEquals(1, res.size)
assertEquals("Film.1080p.mkv", res[0].name)
}

@Test
fun filterAndSortVideos_sortsByDateDescending() {
val opts = baseOpts.copy(
sortBy = SortBy.DATE,
releaseDates = mapOf("Old.mkv" to "2010-01-01", "New.mkv" to "2024-05-01"),
)
val res = VideoFilterSorter.filterAndSortVideos(listOf(v("Old.mkv"), v("New.mkv")), opts)
assertEquals(listOf("New.mkv", "Old.mkv"), res.map { it.name })
}

@Test
fun filterAndSortVideos_sortsBySizeDescending() {
val opts = baseOpts.copy(sortBy = SortBy.SIZE)
val small = v("Small.mkv").copy(size = 100L)
val large = v("Large.mkv").copy(size = 5000L)
val res = VideoFilterSorter.filterAndSortVideos(listOf(small, large), opts)
assertEquals(listOf("Large.mkv", "Small.mkv"), res.map { it.name })
}

@Test
fun filterAndSortVideos_sortsByDurationDescending() {
val opts = baseOpts.copy(
sortBy = SortBy.DURATION,
videoDurations = mapOf("Short.mkv" to 60L, "Long.mkv" to 3600L),
)
val res = VideoFilterSorter.filterAndSortVideos(listOf(v("Short.mkv"), v("Long.mkv")), opts)
assertEquals(listOf("Long.mkv", "Short.mkv"), res.map { it.name })
}
}

Loading