Implement shuffle from main menu

This commit is contained in:
RandyTheSilly committed 2026-10-11 18:17:03 -04:00
1 parent f8ef8b5667
commit 0bf16cda63
2 files changed
+120 -4

No files matched your search

+25 -2
View File
@@ -142,6 +142,29 @@ static void to_pkjs_play_from_track(uint16_t seqId) {
}
}
static void to_pkjs_shuffle_in_scope(NavScope scope, uint16_t *subscope_index, bool use_groups) {
if (!s_pkjs_ready) {
return;
}
DictionaryIterator *out;
AppMessageResult result = app_message_outbox_begin(&out);
if (result != APP_MSG_OK) {
return;
}
const char *subscope = subscope_index ? s_current_menu_vanity[*subscope_index] : "";
if (subscope_index) {
dict_write_cstring(out, MESSAGE_KEY_OUT_MENU_NAV_SUBSCOPE, subscope);
}
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, scope);
dict_write_uint8(out, MESSAGE_KEY_OUT_SHUFFLE_MODE, use_groups ? 2 : 1);
result = app_message_outbox_send();
if (result != APP_MSG_OK) {
return;
}
}
static void to_pkjs_move_deeper(NavScope scope, uint16_t *subscope_index) {
if (!s_pkjs_ready) {
return;
@@ -236,10 +259,10 @@ static void menu_select_click(struct MenuLayer *menu_layer, MenuIndex *clicked_i
case NAV_SCOPE_MAIN:
switch (clicked_index->row) {
case 0:
// TODO tell PKJS to shuffle all songs
to_pkjs_shuffle_in_scope(s_current_menu_scope, NULL, false);
break;
case 1:
// TODO tell PKJS to shuffle artists
to_pkjs_shuffle_in_scope(s_current_menu_scope, NULL, true);
break;
case 2:
to_pkjs_move_deeper(NAV_SCOPE_ARTISTS, NULL);
+95 -2
View File
@@ -14,7 +14,8 @@ var NavScope = Object.freeze({
var cfg = JSON.parse(localStorage.getItem('clay-settings'));
var currentArtist = null;
////////////////////// GENERAL API VARS //////////////////////
////////////////////// GENERAL VARS //////////////////////
var QUEUE_SIZE = 100;
var USER_ID_CACHE_KEY = 'user-id';
var jfSessionId;
@@ -229,6 +230,41 @@ function ringSlice(arr, start, end) {
return Array.from({ length: count }, (_, k) => arr[(((start + k) % n) + n) % n]);
}
// Fisher-Yates shuffles a copy of an array
function shuffledCopy(arr) {
const result = arr.slice();
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = result[i];
result[i] = result[j];
result[j] = tmp;
}
return result;
}
// flattens an album to tracks map into jfIds
function albumMapJfIds(albumToTracksMap) {
const jfIds = [];
for (const tracks of albumToTracksMap.values()) {
for (const track of tracks) {
jfIds.push(track.JfId);
}
}
return jfIds;
}
// shuffle the order of whole groups while keeping each group's tracks together
function groupedShuffle(groups, limit) {
const result = [];
for (const group of shuffledCopy(groups)) {
for (const jfId of group) {
result.push(jfId);
if (result.length >= limit) { return result; }
}
}
return result;
}
Pebble.addEventListener("appmessage", function (dict) {
//console.log("payload", JSON.stringify(dict.payload));
//console.log("expected NAV_SCOPE key", keys.BIDIR_MENU_NAV_SCOPE);
@@ -236,12 +272,69 @@ Pebble.addEventListener("appmessage", function (dict) {
const scope = payloadGet(dict.payload, "BIDIR_MENU_NAV_SCOPE");
const subscope = payloadGet(dict.payload, "OUT_MENU_NAV_SUBSCOPE");
const seqId = payloadGet(dict.payload, "OUT_PLAY_SEQ_ID");
const shuffleMode = payloadGet(dict.payload, "OUT_SHUFFLE_MODE");
if (seqId !== undefined) {
callAPI("POST", cfg.PRIV_BASE_URL + "/Sessions/" + jfSessionId + "/Playing?playCommand=PlayNow&itemIds=" + ringSlice(allJfIds, seqId, seqId+99), null);
callAPI("POST", cfg.PRIV_BASE_URL + "/Sessions/" + jfSessionId + "/Playing?playCommand=PlayNow&itemIds=" + ringSlice(allJfIds, seqId, seqId+QUEUE_SIZE), null);
}
if (scope !== undefined) {
// scope shuffle
if (shuffleMode) {
// build a shuffled queue of jfIds for the current scope
let queuedJfIds = [];
switch (scope) {
case NavScope.NAV_SCOPE_MAIN:
if (shuffleMode == 1) {
// choose 100 random jfIds from the entire library (truly random order)
queuedJfIds = shuffledCopy(allJfIds).slice(0, QUEUE_SIZE);
} else {
// choose 100 random jfIds from the entire library (grouped by artist)
const artistGroups = [];
for (const artistToAlbumsMap of lookupMap.values()) {
for (const albumToTracksMap of artistToAlbumsMap.values()) {
artistGroups.push(albumMapJfIds(albumToTracksMap));
}
}
queuedJfIds = groupedShuffle(artistGroups, QUEUE_SIZE);
}
break;
case NavScope.NAV_SCOPE_ALBUMS: {
const artistToAlbumsMap = lookupMap.get(Array.from(subscope)[0].toUpperCase());
const albumToTracksMap = artistToAlbumsMap ? artistToAlbumsMap.get(subscope) : null;
if (albumToTracksMap) {
if (shuffleMode == 1) {
// choose 100 random jfIds from the current artist (truly random order)
queuedJfIds = shuffledCopy(albumMapJfIds(albumToTracksMap)).slice(0, QUEUE_SIZE);
} else {
// choose 100 random jfIds from the current artist (grouped by album)
const albumGroups = [];
for (const tracks of albumToTracksMap.values()) {
albumGroups.push(tracks.map((track) => track.JfId));
}
queuedJfIds = groupedShuffle(albumGroups, QUEUE_SIZE);
}
}
break;
}
case NavScope.NAV_SCOPE_ALBUM_TRACKS: {
const artistToAlbumsMap = lookupMap.get(Array.from(currentArtist)[0].toUpperCase());
const albumToTracksMap = artistToAlbumsMap ? artistToAlbumsMap.get(currentArtist) : null;
const tracks = albumToTracksMap ? albumToTracksMap.get(subscope) : null;
if (tracks) {
// randomize the order of jfIds from the current album
queuedJfIds = shuffledCopy(tracks.map((track) => track.JfId));
}
break;
}
}
if (queuedJfIds.length > 0) {
callAPI("POST", cfg.PRIV_BASE_URL + "/Sessions/" + jfSessionId + "/Playing?playCommand=PlayNow&itemIds=" + queuedJfIds.join(','), null);
}
return; // no menu navigation
}
// navigation
let vanityList;
let seqIdList = [];
switch (scope) {