From 6d063af2b57841dce30ddb07bf60ce828d737697 Mon Sep 17 00:00:00 2001 From: Florrie Date: Fri, 11 Aug 2017 10:06:04 -0300 Subject: Completely re-implement pickers --- src/pickers.js | 154 ++++++++++++++++++++++++++++++--------------------------- src/play.js | 40 +++++++++------ todo.txt | 1 + 3 files changed, 109 insertions(+), 86 deletions(-) diff --git a/src/pickers.js b/src/pickers.js index ec4d629..6a30c37 100644 --- a/src/pickers.js +++ b/src/pickers.js @@ -2,71 +2,100 @@ const { flattenGrouplike } = require('./playlist-utils') -function makeLoopingOrderedPlaylistPicker(grouplike) { - // Looping ordered playlist picker - this plays all the tracks in a group - // in order, while looping the same order forever. +function makePicker(grouplike, sort, loop) { + // Options to take into consideration: + // - How should the top-level be sorted? + // (e.g. "order", "shuffle", "shuffle-groups") + // - How should looping be handled? + // (e.g. "loop", "no-loop") + // - Also keep in mind aliases for all of the above. + // (e.g. "ordered", "shuffled", "noloop") + // + // What about a shuffle-mode that should simply pick a random track every + // time? + // + // What about a shuffle-mode that re-shuffles the list every time a loop + // happens? + // + // Both of those options could probably be handled via the 'loop' option. + + const topLevel = {items: []} + + let generateTopLevel = () => { + if (sort === 'order') { + topLevel.items = flattenGrouplike(grouplike).items + } - const flatGroup = flattenGrouplike(grouplike) - let index = 0 + if (sort === 'shuffle') { + topLevel.items = shuffleArray(flattenGrouplike(grouplike).items) + } - return function() { - if (index >= flatGroup.items.length) { - index = 0 + if (sort === 'shuffle-top-level' || sort === 'shuffle-groups') { + topLevel.items = flattenGrouplike(shuffleArray(grouplike.items)) } - const picked = flatGroup.items[index] - index++ - return picked + console.log(topLevel.items.map(require('./playlist-utils').getItemPathString).join('\n')) } -} -function makeNonLoopingOrderedPlaylistPicker(grouplike) { - // Ordered playlist picker - this plays all the tracks in a group in - // order, after flattening it. + generateTopLevel() - const flatGroup = flattenGrouplike(grouplike) let index = 0 return function() { - if (index < flatGroup.items.length) { - const picked = flatGroup.items[index] - index++ - return picked - } else { - return null + if (index === topLevel.items.length) { + if (loop === 'loop-same-order' || loop === 'loop') { + index = 0 + } + + if (loop === 'loop-regenerate') { + generateTopLevel() + index = 0 + } + + if (loop === 'no-loop' || loop === 'no') { + // Returning null means the picker is done picking. + // (In theory, we could use an ES2015 generator intead, but this works + // well enough.) + return null + } } - } -} - -function makeLoopingShufflePlaylistPicker(grouplike) { - // Shuffle playlist picker - this selects a random track at any index in - // the playlist, after flattening it. - - const flatGroup = flattenGrouplike(grouplike) - return function() { - if (flatGroup.items.length) { - const index = Math.floor(Math.random() * flatGroup.items.length) - return flatGroup.items[index] - } else { - return null + if (index > topLevel.items.length) { + throw new Error( + "Picker index is greater than total item count?" + + `(${index} > ${topLevel.items.length}` + ) } - } -} - -function makeNonLoopingShufflePlaylistPicker(grouplike) { - // No-loop shuffle playlist picker - this takes a playlist and randomly - // shuffles the order of the items in it, then uses that as an "ordered" - // playlist (i.e. it plays all the items in it then stops). - - const flatGroup = flattenGrouplike(grouplike) - const items = shuffleArray(flatGroup.items) - return function() { - if (items.length) { - return items.splice(0, 1)[0] - } else { - return null + if (index < topLevel.items.length) { + // Pick-random is a special exception - in this case we don't actually + // care about the value of the index variable; instead we just pick a + // random track from the generated top level. + // + // Loop=pick-random is different from sort=shuffle. Sort=shuffle always + // ensures the same song doesn't play twice in a single shuffle. It's + // like how when you shuffle a deck of cards, you'll still never pick + // the same card twice, until you go all the way through the deck and + // re-shuffle the deck! + // + // Loop=pick-random instead picks a random track every time the picker + // is called. It's more like you reshuffle the complete deck every time + // you pick something. + // + // Now, how should pick-random work when dealing with groups, such as + // when using sort=shuffle-groups? (If I can't find a solution, I'd say + // that's alright.) + if (loop === 'pick-random') { + const pickedIndex = Math.floor(Math.random() * topLevel.items.length) + return topLevel.items[pickedIndex] + } + + // If we're using any other mode, we just want to get the current item + // in the playlist, and increment the index variable by one (note i++ + // and not ++i; i++ increments AFTER getting i so it gets us the range + // 0..length-1, whereas ++i increments BEFORE, which gets us the range + // 1..length. + return topLevel.items[index++] } } } @@ -93,24 +122,5 @@ function shuffleArray(array) { return workingArray } -module.exports = { - makeLoopingOrderedPlaylistPicker, - makeNonLoopingOrderedPlaylistPicker, - makeLoopingShufflePlaylistPicker, - makeNonLoopingShufflePlaylistPicker, - - byName: { - 'order': makeNonLoopingOrderedPlaylistPicker, - 'ordered': makeNonLoopingOrderedPlaylistPicker, - 'order-loop': makeLoopingOrderedPlaylistPicker, - 'ordered-loop': makeLoopingOrderedPlaylistPicker, - 'order-noloop': makeNonLoopingOrderedPlaylistPicker, - 'ordered-noloop': makeNonLoopingOrderedPlaylistPicker, - 'order-no-loop': makeNonLoopingOrderedPlaylistPicker, - 'ordered-no-loop': makeNonLoopingOrderedPlaylistPicker, - 'shuffle': makeLoopingShufflePlaylistPicker, - 'shuffle-loop': makeLoopingShufflePlaylistPicker, - 'shuffle-noloop': makeNonLoopingShufflePlaylistPicker, - 'shuffle-no-loop': makeNonLoopingShufflePlaylistPicker, - } -} + +module.exports = makePicker diff --git a/src/play.js b/src/play.js index 50be20e..408b79c 100755 --- a/src/play.js +++ b/src/play.js @@ -7,7 +7,7 @@ const clone = require('clone') const fs = require('fs') const fetch = require('node-fetch') const commandExists = require('./command-exists') -const { byName: pickersByName } = require('./pickers') +const makePicker = require('./pickers') const startLoopPlay = require('./loop-play') const processArgv = require('./process-argv') const processSmartPlaylist = require('./smart-playlist') @@ -55,7 +55,8 @@ async function main(args) { let sourcePlaylist = null let activePlaylist = null - let pickerType = 'shuffle' + let pickerSortMode = 'shuffle' + let pickerLoopMode = 'loop-regenerate' let playerCommand = await determineDefaultPlayer() let playOpts = [] @@ -276,15 +277,31 @@ async function main(args) { 'np': util => util.alias('-no-play'), - '-picker': function(util) { - // --picker (alias: --selector) - // Selects the mode that the song to play is picked. + '-sort-mode': function(util) { + // --sort-mode (alias: --sort) + // Sets the mode by which the playback order list is sorted. + // Valid options include 'order', 'shuffle', and 'shuffle-top-level' + // (which has an alias 'shuffle-groups'). // See pickers.js. - pickerType = util.nextArg() + pickerSortMode = util.nextArg() }, - '-selector': util => util.alias('-picker'), + '-sort': util => util.alias('-sort-mode'), + + '-loop-mode': function(util) { + // --loop-mode (alias: --loop) + // Sets the mode by which the playback order list is looped (typically, + // what happens when the picker's index counter gets to the end of the + // list). + // Valid options include 'no-loop', 'loop-same-order' (or 'loop'), + // 'loop-regenerate', and 'pick-random'. + // See pickers.js. + + pickerLoopMode = util.nextArg() + }, + + '-loop': util => util.alias('-loop-mode'), '-player': function(util) { // --player @@ -307,14 +324,9 @@ async function main(args) { } if (willPlay || (willPlay === null && shouldPlay)) { - if (!Object.keys(pickersByName).includes(pickerType)) { - console.error("Invalid picker type: " + pickerType) - return - } - - console.log(`Using ${pickerType} picker.`) + console.log(`Using sort: ${pickerSortMode} and loop: ${pickerLoopMode}.`) - const picker = pickersByName[pickerType](activePlaylist) + const picker = makePicker(activePlaylist, pickerSortMode, pickerLoopMode) console.log(`Using ${playerCommand} player.`) diff --git a/todo.txt b/todo.txt index 5501926..e943b79 100644 --- a/todo.txt +++ b/todo.txt @@ -304,3 +304,4 @@ TODO: safeUnlink probably shouldn't throw/reject if it fails to delete its TODO: A shuffle-groups picker would be fun! (That is, it would take a grouplike, then shuffle all the items on the TOP level; so it would play random (top-level) groups at a time.) + (Done!) -- cgit 1.3.0-6-gf8a5