Skip to content

Commit

Permalink
Add Playlist Sort By Video Duration (#5627)
Browse files Browse the repository at this point in the history
  • Loading branch information
1 parent 6c6ea23 commit f03f4b6
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 2 deletions.
25 changes: 24 additions & 1 deletion src/renderer/helpers/playlists.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const SORT_BY_VALUES = {
AuthorDescending: 'author_descending',
VideoTitleAscending: 'video_title_ascending',
VideoTitleDescending: 'video_title_descending',
VideoDurationAscending: 'video_duration_ascending',
VideoDurationDescending: 'video_duration_descending',
Custom: 'custom'
}

Expand All @@ -19,7 +21,9 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
sortOrder === SORT_BY_VALUES.VideoTitleAscending ||
sortOrder === SORT_BY_VALUES.VideoTitleDescending ||
sortOrder === SORT_BY_VALUES.AuthorAscending ||
sortOrder === SORT_BY_VALUES.AuthorDescending
sortOrder === SORT_BY_VALUES.AuthorDescending ||
sortOrder === SORT_BY_VALUES.VideoDurationAscending ||
sortOrder === SORT_BY_VALUES.VideoDurationDescending
) {
collator = new Intl.Collator([locale, 'en'])
}
Expand All @@ -31,6 +35,19 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
})
}

export function videoDurationPresent(video) {
if (typeof video.lengthSeconds !== 'number') { return false }

return !(isNaN(video.lengthSeconds) || video.lengthSeconds === 0)
}

export function videoDurationWithFallback(video) {
if (videoDurationPresent(video)) { return video.lengthSeconds }

// Fallback
return 0
}

function compareTwoPlaylistItems(a, b, sortOrder, collator) {
switch (sortOrder) {
case SORT_BY_VALUES.DateAddedNewest:
Expand All @@ -45,6 +62,12 @@ function compareTwoPlaylistItems(a, b, sortOrder, collator) {
return collator.compare(a.author, b.author)
case SORT_BY_VALUES.AuthorDescending:
return collator.compare(b.author, a.author)
case SORT_BY_VALUES.VideoDurationAscending: {
return videoDurationWithFallback(a) - videoDurationWithFallback(b)
}
case SORT_BY_VALUES.VideoDurationDescending: {
return videoDurationWithFallback(b) - videoDurationWithFallback(a)
}
default:
console.error(`Unknown sortOrder: ${sortOrder}`)
return 0
Expand Down
45 changes: 44 additions & 1 deletion src/renderer/views/Playlist/Playlist.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ import {
getIconForSortPreference,
setPublishedTimestampsInvidious,
showToast,
deepCopy,
} from '../../helpers/utils'
import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
import { getSortedPlaylistItems, SORT_BY_VALUES } from '../../helpers/playlists'
import { getSortedPlaylistItems, videoDurationPresent, videoDurationWithFallback, SORT_BY_VALUES } from '../../helpers/playlists'
import packageDetails from '../../../../package.json'
import { MOBILE_WIDTH_THRESHOLD, PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD } from '../../../constants'

Expand Down Expand Up @@ -74,6 +75,7 @@ export default defineComponent({
getPlaylistInfoDebounce: function() {},
playlistInEditMode: false,
forceListView: false,
alreadyShownNotice: false,

videoSearchQuery: '',

Expand Down Expand Up @@ -180,6 +182,13 @@ export default defineComponent({
return this.sortOrder === SORT_BY_VALUES.Custom
},
sortedPlaylistItems: function () {
if (
this.sortOrder === SORT_BY_VALUES.VideoDurationAscending ||
this.sortOrder === SORT_BY_VALUES.VideoDurationDescending
) {
const playlistItems = this.getPlaylistItemsWithDuration()
return getSortedPlaylistItems(playlistItems, this.sortOrder, this.currentLocale)
}
return getSortedPlaylistItems(this.playlistItems, this.sortOrder, this.currentLocale)
},
visiblePlaylistItems: function () {
Expand Down Expand Up @@ -214,6 +223,10 @@ export default defineComponent({
return this.$t('Playlist.Sort By.AuthorAscending')
case SORT_BY_VALUES.AuthorDescending:
return this.$t('Playlist.Sort By.AuthorDescending')
case SORT_BY_VALUES.VideoDurationAscending:
return this.$t('Playlist.Sort By.VideoDurationAscending')
case SORT_BY_VALUES.VideoDurationDescending:
return this.$t('Playlist.Sort By.VideoDurationDescending')
default:
console.error(`Unknown sort: ${k}`)
return k
Expand Down Expand Up @@ -416,10 +429,40 @@ export default defineComponent({

this.isLoading = false
},

showUserPlaylistNotFound() {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist does not exist'))
},

getPlaylistItemsWithDuration() {
const modifiedPlaylistItems = deepCopy(this.playlistItems)
let anyVideoMissingDuration = false
modifiedPlaylistItems.forEach(video => {
if (videoDurationPresent(video)) { return }

const videoHistory = this.$store.getters.getHistoryCacheById[video.videoId]
if (typeof videoHistory !== 'undefined') {
const fetchedLengthSeconds = videoDurationWithFallback(videoHistory)
video.lengthSeconds = fetchedLengthSeconds
// if the video duration is 0, it will be the fallback value, so mark it as missing a duration
if (fetchedLengthSeconds === 0) { anyVideoMissingDuration = true }
} else {
// Mark at least one video have no duration, show notice later
// Also assign fallback duration here
anyVideoMissingDuration = true
video.lengthSeconds = 0
}
})

// Show notice if not already shown before returning playlist items
if (anyVideoMissingDuration && !this.alreadyShownNotice) {
showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist has a video with a duration error'), 5000)
this.alreadyShownNotice = true
}

return modifiedPlaylistItems
},

getNextPage: function () {
switch (this.infoSource) {
case 'local':
Expand Down
4 changes: 4 additions & 0 deletions static/locales/en-GB.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ User Playlists:
This playlist is protected and cannot be removed.: This playlist is protected
and cannot be removed.
This playlist does not exist: This playlist does not exist

This playlist has a video with a duration error: This playlist contains at least one video that doesn't have a duration, it will be sorted as if their duration is zero.
Playlist {playlistName} has been deleted.: Playlist {playlistName} has been
deleted.
Video has been removed: Video has been removed
Expand Down Expand Up @@ -1006,6 +1008,8 @@ Playlist:
AuthorAscending: Author (A-Z)
AuthorDescending: Author (Z-A)
VideoTitleAscending: Title (A-Z)
VideoDurationAscending: Duration (Shortest first)
VideoDurationDescending: Duration (Longest first)
Toggle Theatre Mode: 'Toggle Theatre Mode'
Change Format:
Change Media Formats: 'Change Media Formats'
Expand Down
4 changes: 4 additions & 0 deletions static/locales/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ User Playlists:
Playlist {playlistName} is the new quick bookmark playlist.: Playlist {playlistName} is the new quick bookmark playlist.

This playlist does not exist: This playlist does not exist

This playlist has a video with a duration error: This playlist contains at least one video that doesn't have a duration, it will be sorted as if their duration is zero.
AddVideoPrompt:
Select a playlist to add your N videos to: 'Select a playlist to add your video to | Select a playlist to add your {videoCount} videos to'
N playlists selected: '{playlistCount} Selected'
Expand Down Expand Up @@ -934,6 +936,8 @@ Playlist:
AuthorDescending: Author (Z-A)
VideoTitleAscending: Title (A-Z)
VideoTitleDescending: Title (Z-A)
VideoDurationAscending: Duration (Shortest first)
VideoDurationDescending: Duration (Longest first)
Custom: Custom

# On Video Watch Page
Expand Down

0 comments on commit f03f4b6

Please sign in to comment.