From 9798dcb08e44ebdbc6f8cc0022c4d7fd27430b43 Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Sun, 11 Oct 2026 16:37:30 -0400 Subject: [PATCH] Initial music playback! --- src/c/jellyPod.c | 20 ++++++++++++- src/pkjs/index.js | 75 +++++++++++++++++++++++++++++++---------------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/src/c/jellyPod.c b/src/c/jellyPod.c index 3121010..1db255f 100644 --- a/src/c/jellyPod.c +++ b/src/c/jellyPod.c @@ -124,6 +124,24 @@ static void inbox_received_handler(DictionaryIterator *iter, void *context) { } } +static void to_pkjs_play_from_track(uint16_t seqId) { + if (!s_pkjs_ready) { + return; + } + + DictionaryIterator *out; + AppMessageResult result = app_message_outbox_begin(&out); + if (result != APP_MSG_OK) { + return; + } + dict_write_uint16(out, MESSAGE_KEY_OUT_PLAY_SEQ_ID, seqId); + + 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,7 +254,7 @@ static void menu_select_click(struct MenuLayer *menu_layer, MenuIndex *clicked_i to_pkjs_move_deeper(s_current_menu_scope + 1, &clicked_index->row); break; case NAV_SCOPE_ALBUM_TRACKS: - // TODO tell PKJS to play track (and queue sequential tracks) + to_pkjs_play_from_track(s_current_menu_seq_ids[clicked_index->row]); break; } } diff --git a/src/pkjs/index.js b/src/pkjs/index.js index 6fcd5f1..0a3943d 100644 --- a/src/pkjs/index.js +++ b/src/pkjs/index.js @@ -14,6 +14,10 @@ var NavScope = Object.freeze({ var cfg = JSON.parse(localStorage.getItem('clay-settings')); var currentArtist = null; +////////////////////// GENERAL API VARS ////////////////////// +var USER_ID_CACHE_KEY = 'user-id'; +var jfSessionId; + ////////////////////// LIBRARY CACHE ////////////////////// var LIBRARY_CACHE_KEY = 'library-cache'; @@ -77,9 +81,33 @@ function loadLibraryCache() { ////////////////////// SYNC ////////////////////// function toggleReadyState(ready) { - const readyDict = {}; - readyDict[keys.PKJS_READY] = ready; - Pebble.sendAppMessage(readyDict); + let bestSessionInfo = { + sessionId: "", + lastActiveTime: new Date("1970-01-01T00:00:00.0000000Z") + }; + + // get sessionId from Jellyfin + callAPI("GET", cfg.PRIV_BASE_URL + "/Sessions", function (sessionIdResp) { + if (sessionIdResp === null) { return; } + const sessionIdObj = JSON.parse(sessionIdResp); + let lastActiveTime; + for (const session of sessionIdObj) { + if (session.UserName == cfg.PRIV_USERNAME) { + lastActiveTime = new Date(session.LastActivityDate); + if (lastActiveTime > bestSessionInfo.lastActiveTime) { + bestSessionInfo = { + sessionId: session.Id, + lastActiveTime: lastActiveTime + }; + } + } + } + jfSessionId = bestSessionInfo.sessionId; + + const readyDict = {}; + readyDict[keys.PKJS_READY] = ready; + Pebble.sendAppMessage(readyDict); + }); } function syncLibrary() { @@ -87,8 +115,8 @@ function syncLibrary() { // store cache of library info from Jellyfin if (cfg.PRIV_BASE_URL && cfg.PRIV_API_KEY && cfg.PRIV_USERNAME) { - // get userId from Jellyfin callAPI("GET", cfg.PRIV_BASE_URL + "/Users", function (userIdResp) { + // get userId from Jellyfin if (userIdResp === null) { return; } const userIdObj = JSON.parse(userIdResp); let userId; @@ -200,6 +228,16 @@ 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"); + + if (seqId) { + console.log("SEQ ID RECEIVED"); + console.log("sessionId: " + jfSessionId); + console.log("seqId: " + seqId); + console.log("jfId: " + allJfIds[seqId]); + console.log("SEQ ID RECEIVED"); + callAPI("POST", cfg.PRIV_BASE_URL + "/Sessions/" + jfSessionId + "/Playing?playCommand=PlayNow&itemIds=" + allJfIds[seqId], null); + } if (scope) { let vanityList; @@ -250,8 +288,8 @@ Pebble.addEventListener("appmessage", function (dict) { ////////////////////// JELLYFIN API ////////////////////// -function reportAPIFailure() { - console.log("API FAILURE"); +function reportAPIFailure(statusCode, statusText) { + console.log("[" + statusCode+"] API FAILURE: " + statusText); } function callAPI(method, endpoint, callback) { @@ -260,17 +298,19 @@ function callAPI(method, endpoint, callback) { xhr.onload = function () { switch (xhr.status) { case 200: - callback(this.responseText); + if (callback) { + callback(this.responseText); + } return; default: - reportAPIFailure(); + reportAPIFailure(xhr.status, xhr.statusText); } }; xhr.onerror = function () { - reportAPIFailure(); + reportAPIFailure(xhr.status, xhr.statusText); }; xhr.ontimeout = function () { - reportAPIFailure(); + reportAPIFailure(xhr.status, xhr.statusText); }; xhr.open(method, endpoint, true); @@ -279,18 +319,3 @@ function callAPI(method, endpoint, callback) { xhr.setRequestHeader("Authorization", 'MediaBrowser Token="' + cfg.PRIV_API_KEY + '"'); xhr.send(); } - -// On Every Startup -//// Get Session ID - -//curl -H "Authorization: MediaBrowser Token=\"TOKEN\"" -X GET 'BASEURL/Sessions' - -// Filter by: -// UserId -// Sort by: -// LastActivityDate - -// Controls -//// Select Track (pass through a queue) - -//curl - H "Authorization: MediaBrowser Token=\"TOKEN\"" - X POST 'BASEURL/Sessions/SESSIONID/Playing?playCommand=PlayNow&itemIds=XXX,YYY'