From b12f84cb661959af671c229ae1b476aa1c94cd2b Mon Sep 17 00:00:00 2001 From: Randall Winkhart Date: Fri, 9 Oct 2026 01:35:14 -0400 Subject: [PATCH] Initial code commit --- .clang-format | 7 + .gitignore | 5 + README.md | 22 ++- package.json | 56 ++++++++ resources/CREDITS.md | 1 + resources/ChicagoFLF.ttf | Bin 0 -> 50472 bytes src/c/JellyPod.c | 86 ++++++++++++ src/c/custom_status_bar.c | 289 ++++++++++++++++++++++++++++++++++++++ src/c/custom_status_bar.h | 31 ++++ src/pkjs/config.js | 33 +++++ src/pkjs/index.js | 54 +++++++ wscript | 54 +++++++ 12 files changed, 637 insertions(+), 1 deletion(-) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 package.json create mode 100644 resources/CREDITS.md create mode 100644 resources/ChicagoFLF.ttf create mode 100644 src/c/JellyPod.c create mode 100644 src/c/custom_status_bar.c create mode 100644 src/c/custom_status_bar.h create mode 100644 src/pkjs/config.js create mode 100644 src/pkjs/index.js create mode 100644 wscript diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..31aa732 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +--- +BasedOnStyle: LLVM +TabWidth: 4 +IndentWidth: 4 +UseTab: ForIndentation +ColumnLimit: 0 +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eaf2f4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/build +/.clangd +/.lock-waf* +/package-lock.json +/node_modules diff --git a/README.md b/README.md index a046c78..66ff9c8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,23 @@ # JellyPod +Control Jellyfin music playback from your Pebble with an iPod-like interface. -Control Jellyfin music playback from your Pebble with an iPod-like interface. Even works on iOS! \ No newline at end of file +# Supported Phone Clients +JellyPod requires a Jellyfin client to be running on your phone to handle playback. Not all clients support all features of JellyPod. Tested clients are detailed below. +## Legend +- ✅ = verified (supports all features of JellyPod and prefers offline playback of local media over online streaming) +- ⚠️ = usable (functional with missing features/bugs detailed below) +- ⛔ = unsupported (does not work well or at all with JellyPod) +## iOS +- ✅ [Finamp Beta/Redesign](https://github.com/finamp-app/finamp/tree/redesign) (last tested release: 1.0.1) + +# Special Features +- True sequential playback + - Your music continues to play in order even after you exhaust the tracks on the selected album and by the current artist + - The next album is chosen by release date and the next artist is chosen alphanumerically +- Multi-client/device support + - JellyPod is not just a remote for your phone + - Determines which client to control based on the most recently active session under your Jellyfin user +- Classic design + - Inspired by the 4th generation iPod +- Support for ALL Pebbles +- Absolutely NO AI was used in the creation of this application diff --git a/package.json b/package.json new file mode 100644 index 0000000..c22a727 --- /dev/null +++ b/package.json @@ -0,0 +1,56 @@ +{ + "name": "jellypod", + "author": "RandyTheSilly", + "version": "0.1.0", + "keywords": [ + "music", + "remote", + "jellyfin", + "finamp", + "ipod", + "playback", + "ios" + ], + "capabilities": [ + "configurable" + ], + "private": true, + "dependencies": { + "@rebble/clay": "^1.0.10" + }, + "pebble": { + "displayName": "JellyPod", + "uuid": "b135c5a1-ddeb-4278-9da5-3ac68b718f0c", + "sdkVersion": "3", + "enableMultiJS": true, + "targetPlatforms": [ + "aplite", + "basalt", + "chalk", + "diorite", + "emery", + "flint", + "gabbro" + ], + "watchapp": { + "watchface": false + }, + "messageKeys": [ + "IN_CURRENT_LEVEL", + "IN_VANITY_LIST", + "IN_ID_LIST", + "OUT_TRACK_ID", + "OUT_PLAYBACK_MODE", + "OUT_PLAYBACK_ACTION" + ], + "resources": { + "media": [ + { + "file": "ChicagoFLF.ttf", + "name": "FONT_CHICAGO_16", + "type": "font" + } + ] + } + } +} diff --git a/resources/CREDITS.md b/resources/CREDITS.md new file mode 100644 index 0000000..a8e9524 --- /dev/null +++ b/resources/CREDITS.md @@ -0,0 +1 @@ +https://fontsarena.com/chicago-flf-by-robin-casady/ diff --git a/resources/ChicagoFLF.ttf b/resources/ChicagoFLF.ttf new file mode 100644 index 0000000000000000000000000000000000000000..91e2ae956c79f10e97898ef85153fa167efadedc GIT binary patch literal 50472 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 Gum3+Ixos8z literal 0 HcmV?d00001 diff --git a/src/c/JellyPod.c b/src/c/JellyPod.c new file mode 100644 index 0000000..f421fcb --- /dev/null +++ b/src/c/JellyPod.c @@ -0,0 +1,86 @@ +#include "custom_status_bar.h" +#include + +// layer-age +static Window *s_main_window; +static CustomStatusBarLayer *s_status_bar_layer; +static Layer *s_divider_layer; +static MenuLayer *s_menu_layer; + +// globals +static int s_current_menu_rows = 3; + +// resources +static GFont s_font_chicago; + +// constants +#define BAR_HEIGHT 20 +#define MARGIN 4 +static const GRect s_menu_grect = GRect(MARGIN, BAR_HEIGHT + MARGIN, PBL_DISPLAY_WIDTH - (MARGIN * 2), PBL_DISPLAY_HEIGHT - (MARGIN * 2)); +static const char *s_main_menu_options[3] = {"Artists >", "Playlists >", "Now Playing >"}; + +static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) { + return s_current_menu_rows; +} + +static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { + menu_cell_basic_draw(ctx, cell_layer, s_main_menu_options[cell_index->row], NULL, NULL); +} + +static void menu_selection_changed(struct MenuLayer *menu_layer, MenuIndex new_index, MenuIndex old_index, void *callback_context) { + speaker_play_tone(200, 10, 80, SpeakerWaveformSine); +} + +static void divider_layer_update_proc(Layer *layer, GContext *ctx) { + graphics_context_set_fill_color(ctx, GColorBlack); + graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone); +} + +static void main_window_load() { + Layer *main_window_layer = window_get_root_layer(s_main_window); + + s_menu_layer = menu_layer_create(s_menu_grect); + menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks){.get_num_rows = menu_get_num_rows, .draw_row = menu_draw_row, .selection_changed = menu_selection_changed}); + menu_layer_pad_bottom_enable(s_menu_layer, false); + menu_layer_set_click_config_onto_window(s_menu_layer, s_main_window); + layer_add_child(main_window_layer, menu_layer_get_layer(s_menu_layer)); + + s_status_bar_layer = custom_status_bar_layer_create(BAR_HEIGHT, GColorWhite, 15); + custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_CENTER, "pebble"); + custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_LEFT, " | |"); + custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_RIGHT, "[// ]- "); + custom_status_bar_layer_set_text_font(s_status_bar_layer, CSB_TEXT_CENTER, s_font_chicago); + layer_add_child(main_window_layer, s_status_bar_layer); + + s_divider_layer = layer_create(GRect(MARGIN, BAR_HEIGHT + (MARGIN / 2), PBL_DISPLAY_WIDTH - (MARGIN * 2), 1)); + layer_set_update_proc(s_divider_layer, divider_layer_update_proc); + layer_add_child(main_window_layer, s_divider_layer); +} + +static void main_window_unload() { + layer_destroy(s_divider_layer); + custom_status_bar_layer_destroy(s_status_bar_layer); + menu_layer_destroy(s_menu_layer); +} + +static void init() { + s_font_chicago = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_CHICAGO_16)); + + s_main_window = window_create(); + window_set_window_handlers( + s_main_window, + (WindowHandlers){.load = main_window_load, .unload = main_window_unload}); + window_stack_push(s_main_window, false); + app_touch_navigation_enable(true); +} + +static void deinit() { + window_destroy(s_main_window); + fonts_unload_custom_font(s_font_chicago); +} + +int main(void) { + init(); + app_event_loop(); + deinit(); +} diff --git a/src/c/custom_status_bar.c b/src/c/custom_status_bar.c new file mode 100644 index 0000000..73087ef --- /dev/null +++ b/src/c/custom_status_bar.c @@ -0,0 +1,289 @@ +#include "custom_status_bar.h" +#include "pebble.h" + +typedef struct { + uint8_t height; + uint8_t icon_width; + GColor bar_colour; + GColor text_colour; + TextLayer *left_text; + TextLayer *center_text; + TextLayer *right_text; + BitmapLayer *icon_0; + BitmapLayer *icon_1; + BitmapLayer *icon_2; + BitmapLayer *icon_3; + BitmapLayer *icon_4; +} CustomStatusBarLayerHidden; + +static void update_proc(Layer *layer, GContext *context); + +CustomStatusBarLayer *custom_status_bar_layer_create(uint8_t height, GColor bar_colour, uint8_t icon_width) { + + if (icon_width > MAX_ICON_WIDTH) { + APP_LOG(APP_LOG_LEVEL_ERROR, "ERROR: ICON WIDTH TOO LARGE. SEE MAX_ICON_WIDTH. STATUS BAR NOT CREATED. RETURNING NULL."); + return NULL; + } + + // Status bar frame + GRect frame = (GRect){.origin = GPointZero, + .size = { + .h = height, + .w = PBL_DISPLAY_WIDTH}}; + + CustomStatusBarLayer *status_bar = layer_create_with_data(frame, sizeof(CustomStatusBarLayerHidden)); + if (status_bar == NULL) { + APP_LOG(APP_LOG_LEVEL_ERROR, "OOM Couldn't create custom status bar."); + } + + // Get status hidden pointer + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(status_bar); + + layer_set_update_proc(status_bar, update_proc); + + // Set up bar variables + status_hidden->height = height; + status_hidden->icon_width = icon_width; + status_hidden->bar_colour = bar_colour; + // TextColor + if (bar_colour.argb == GColorBlack.argb) { + status_hidden->text_colour = GColorWhite; + } else { + status_hidden->text_colour = GColorBlack; + } + + status_hidden->left_text = NULL; + status_hidden->center_text = NULL; + status_hidden->right_text = NULL; + status_hidden->icon_0 = NULL; + status_hidden->icon_1 = NULL; + status_hidden->icon_2 = NULL; + status_hidden->icon_3 = NULL; + status_hidden->icon_4 = NULL; + + return status_bar; +} + +void custom_status_bar_layer_destroy(CustomStatusBarLayer *custom_status_bar_layer) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + if (status_hidden->left_text != NULL) + text_layer_destroy(status_hidden->left_text); + if (status_hidden->right_text != NULL) + text_layer_destroy(status_hidden->right_text); + if (status_hidden->center_text != NULL) + text_layer_destroy(status_hidden->center_text); + if (status_hidden->icon_0 != NULL) + bitmap_layer_destroy(status_hidden->icon_0); + if (status_hidden->icon_1 != NULL) + bitmap_layer_destroy(status_hidden->icon_1); + if (status_hidden->icon_2 != NULL) + bitmap_layer_destroy(status_hidden->icon_2); + if (status_hidden->icon_3 != NULL) + bitmap_layer_destroy(status_hidden->icon_3); + if (status_hidden->icon_4 != NULL) + bitmap_layer_destroy(status_hidden->icon_4); + + if (custom_status_bar_layer != NULL) + layer_destroy(custom_status_bar_layer); +} + +void custom_status_bar_layer_set_height(CustomStatusBarLayer *custom_status_bar_layer, uint32_t height) { + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + status_hidden->height = height; + layer_mark_dirty(custom_status_bar_layer); +} + +void custom_status_bar_layer_set_text(CustomStatusBarLayer *custom_status_bar_layer, CsbTextPosition position, char *status_bar_text) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + switch (position) { + case CSB_TEXT_LEFT: + if (status_hidden->left_text == NULL) { + status_hidden->left_text = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, status_hidden->height)); + text_layer_set_text_alignment(status_hidden->left_text, GTextAlignmentLeft); + text_layer_set_font(status_hidden->left_text, fonts_get_system_font(FONT_KEY_GOTHIC_14)); + text_layer_set_text_color(status_hidden->left_text, status_hidden->text_colour); + text_layer_set_background_color(status_hidden->left_text, GColorClear); + layer_add_child(custom_status_bar_layer, text_layer_get_layer(status_hidden->left_text)); + } + text_layer_set_text(status_hidden->left_text, status_bar_text); + break; + + case CSB_TEXT_RIGHT: + if (status_hidden->right_text == NULL) { + status_hidden->right_text = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, status_hidden->height)); + text_layer_set_text_alignment(status_hidden->right_text, GTextAlignmentRight); + text_layer_set_font(status_hidden->right_text, fonts_get_system_font(FONT_KEY_GOTHIC_14)); + text_layer_set_text_color(status_hidden->right_text, status_hidden->text_colour); + text_layer_set_background_color(status_hidden->right_text, GColorClear); + layer_add_child(custom_status_bar_layer, text_layer_get_layer(status_hidden->right_text)); + } + text_layer_set_text(status_hidden->right_text, status_bar_text); + break; + + case CSB_TEXT_CENTER: + if (status_hidden->center_text == NULL) { + status_hidden->center_text = text_layer_create(GRect(0, 0, PBL_DISPLAY_WIDTH, status_hidden->height)); + text_layer_set_text_alignment(status_hidden->center_text, GTextAlignmentCenter); + text_layer_set_font(status_hidden->center_text, fonts_get_system_font(FONT_KEY_GOTHIC_14)); + text_layer_set_text_color(status_hidden->center_text, status_hidden->text_colour); + text_layer_set_background_color(status_hidden->center_text, GColorClear); + layer_add_child(custom_status_bar_layer, text_layer_get_layer(status_hidden->center_text)); + } + text_layer_set_text(status_hidden->center_text, status_bar_text); + break; + } +} + +void custom_status_bar_layer_set_text_font(CustomStatusBarLayer *custom_status_bar_layer, CsbTextPosition position, GFont font) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + switch (position) { + case CSB_TEXT_LEFT: + text_layer_set_font(status_hidden->left_text, font); + break; + + case CSB_TEXT_RIGHT: + text_layer_set_font(status_hidden->right_text, font); + break; + + case CSB_TEXT_CENTER: + text_layer_set_font(status_hidden->center_text, font); + break; + } +} + +void custom_status_bar_layer_set_text_hidden(CustomStatusBarLayer *custom_status_bar_layer, CsbTextPosition position, bool hidden) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + switch (position) { + case CSB_TEXT_LEFT: + layer_set_hidden(text_layer_get_layer(status_hidden->left_text), hidden); + break; + + case CSB_TEXT_RIGHT: + layer_set_hidden(text_layer_get_layer(status_hidden->right_text), hidden); + break; + + case CSB_TEXT_CENTER: + layer_set_hidden(text_layer_get_layer(status_hidden->center_text), hidden); + break; + } +} + +void custom_status_bar_layer_set_bitmap(CustomStatusBarLayer *custom_status_bar_layer, CsbIconPosition position, GBitmap *gbitmap) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + uint8_t icon_width = status_hidden->icon_width; + uint8_t height = status_hidden->height; + + switch (position) { + case CSB_ICON_0: + if (status_hidden->icon_0 == NULL) { + status_hidden->icon_0 = bitmap_layer_create(GRect(PBL_DISPLAY_WIDTH / 10 - icon_width / 2, height / 2 - icon_width / 2, icon_width, icon_width)); + bitmap_layer_set_background_color(status_hidden->icon_0, GColorClear); + layer_add_child(custom_status_bar_layer, bitmap_layer_get_layer(status_hidden->icon_0)); + } + bitmap_layer_set_bitmap(status_hidden->icon_0, gbitmap); + break; + + case CSB_ICON_1: + if (status_hidden->icon_1 == NULL) { + status_hidden->icon_1 = bitmap_layer_create(GRect(PBL_DISPLAY_WIDTH * 3 / 10 - icon_width / 2, height / 2 - icon_width / 2, icon_width, icon_width)); + bitmap_layer_set_background_color(status_hidden->icon_1, GColorClear); + layer_add_child(custom_status_bar_layer, bitmap_layer_get_layer(status_hidden->icon_1)); + } + bitmap_layer_set_bitmap(status_hidden->icon_1, gbitmap); + break; + + case CSB_ICON_2: + if (status_hidden->icon_2 == NULL) { + status_hidden->icon_2 = bitmap_layer_create(GRect(PBL_DISPLAY_WIDTH / 2 - icon_width / 2, height / 2 - icon_width / 2, icon_width, icon_width)); + bitmap_layer_set_background_color(status_hidden->icon_2, GColorClear); + layer_add_child(custom_status_bar_layer, bitmap_layer_get_layer(status_hidden->icon_2)); + } + bitmap_layer_set_bitmap(status_hidden->icon_2, gbitmap); + break; + + case CSB_ICON_3: + if (status_hidden->icon_3 == NULL) { + status_hidden->icon_3 = bitmap_layer_create(GRect(PBL_DISPLAY_WIDTH * 7 / 10 - icon_width / 2, height / 2 - icon_width / 2, icon_width, icon_width)); + bitmap_layer_set_background_color(status_hidden->icon_3, GColorClear); + layer_add_child(custom_status_bar_layer, bitmap_layer_get_layer(status_hidden->icon_3)); + } + bitmap_layer_set_bitmap(status_hidden->icon_3, gbitmap); + break; + + case CSB_ICON_4: + if (status_hidden->icon_4 == NULL) { + status_hidden->icon_4 = bitmap_layer_create(GRect(PBL_DISPLAY_WIDTH * 9 / 10 - icon_width / 2, height / 2 - icon_width / 2, icon_width, icon_width)); + bitmap_layer_set_background_color(status_hidden->icon_4, GColorClear); + layer_add_child(custom_status_bar_layer, bitmap_layer_get_layer(status_hidden->icon_4)); + } + bitmap_layer_set_bitmap(status_hidden->icon_4, gbitmap); + break; + } +} + +void custom_status_bar_layer_set_bitmap_hidden(CustomStatusBarLayer *custom_status_bar_layer, CsbIconPosition position, bool hidden) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + switch (position) { + case CSB_ICON_0: + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_0), hidden); + break; + + case CSB_ICON_1: + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_1), hidden); + break; + + case CSB_ICON_2: + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_2), hidden); + break; + + case CSB_ICON_3: + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_3), hidden); + break; + + case CSB_ICON_4: + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_4), hidden); + break; + } +} + +void custom_status_bar_layer_set_all_text_hidden(CustomStatusBarLayer *custom_status_bar_layer, bool hidden) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + layer_set_hidden(text_layer_get_layer(status_hidden->left_text), hidden); + layer_set_hidden(text_layer_get_layer(status_hidden->right_text), hidden); + layer_set_hidden(text_layer_get_layer(status_hidden->center_text), hidden); +} + +void custom_status_bar_layer_set_all_bitmaps_hidden(CustomStatusBarLayer *custom_status_bar_layer, bool hidden) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(custom_status_bar_layer); + + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_0), hidden); + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_1), hidden); + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_2), hidden); + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_3), hidden); + layer_set_hidden(bitmap_layer_get_layer(status_hidden->icon_4), hidden); +} + +static void update_proc(Layer *layer, GContext *context) { + + CustomStatusBarLayerHidden *status_hidden = (CustomStatusBarLayerHidden *)layer_get_data(layer); + + GRect bounds = layer_get_bounds(layer); + + graphics_context_set_fill_color(context, status_hidden->bar_colour); + graphics_fill_rect(context, bounds, 0, GCornersAll); +} diff --git a/src/c/custom_status_bar.h b/src/c/custom_status_bar.h new file mode 100644 index 0000000..8c50b02 --- /dev/null +++ b/src/c/custom_status_bar.h @@ -0,0 +1,31 @@ +#include "pebble.h" + +typedef struct Layer CustomStatusBarLayer; + +#define MAX_ICON_WIDTH 28 + +typedef enum { + CSB_TEXT_LEFT, + CSB_TEXT_RIGHT, + CSB_TEXT_CENTER, +} CsbTextPosition; + +//ICON positions start left to right, 0 indexed +typedef enum { + CSB_ICON_0, + CSB_ICON_1, + CSB_ICON_2, + CSB_ICON_3, + CSB_ICON_4, +} CsbIconPosition; + +//icon_width determines both the width and height of the BitmapLayers being created in the status bar +CustomStatusBarLayer * custom_status_bar_layer_create(uint8_t height, GColor bar_colour, uint8_t icon_width); +void custom_status_bar_layer_destroy(CustomStatusBarLayer* custom_status_bar_layer); +void custom_status_bar_layer_set_text(CustomStatusBarLayer* custom_status_bar_layer, CsbTextPosition position, char* status_bar_text); +void custom_status_bar_layer_set_text_font(CustomStatusBarLayer* custom_status_bar_layer, CsbTextPosition position, GFont font); +void custom_status_bar_layer_set_text_hidden(CustomStatusBarLayer* custom_status_bar_layer, CsbTextPosition position, bool hidden); +void custom_status_bar_layer_set_bitmap(CustomStatusBarLayer* custom_status_bar_layer, CsbIconPosition position, GBitmap* gbitmap); +void custom_status_bar_layer_set_bitmap_hidden(CustomStatusBarLayer* custom_status_bar_layer, CsbIconPosition position, bool hidden); +void custom_status_bar_layer_set_all_text_hidden(CustomStatusBarLayer* custom_status_bar_layer, bool hidden); +void custom_status_bar_layer_set_all_bitmaps_hidden(CustomStatusBarLayer* custom_status_bar_layer, bool hidden); diff --git a/src/pkjs/config.js b/src/pkjs/config.js new file mode 100644 index 0000000..b68ae62 --- /dev/null +++ b/src/pkjs/config.js @@ -0,0 +1,33 @@ +module.exports = [ + { + "type": "heading", + "defaultValue": "JellyPod | Settings" + }, + { + "type": "section", + "items": [ + { + "id": "base_url", + "type": "input", + "messageKey": "PRIV_BASE_URL", + "label": "Base URL" + }, + { + "id": "api_key", + "type": "input", + "messageKey": "PRIV_API_KEY", + "label": "API Key" + }, + { + "id": "username", + "type": "input", + "messageKey": "PRIV_USERNAME", + "label": "Username" + } + ], + }, + { + "type": "submit", + "defaultValue": "Save/Sync Library" + } +]; diff --git a/src/pkjs/index.js b/src/pkjs/index.js new file mode 100644 index 0000000..749e166 --- /dev/null +++ b/src/pkjs/index.js @@ -0,0 +1,54 @@ +var Clay = require('@rebble/clay'); +var clayConfig = require('./config'); +new Clay(clayConfig); + +Pebble.addEventListener("ready", function () { + Pebble.sendAppMessage({ PKJS_READY: 1 }); +}); + +function callAPI(method, endpoint) { + const xhr = new XMLHttpRequest(); + + xhr.onload = function () { + switch (xhr.status) { + case 200: + return this.responseText; + default: + reportAPIFailure(); // TODO + } + }; + xhr.onerror = function () { + reportAPIFailure(); // TODO + }; + xhr.ontimeout = function () { + reportAPIFailure(); // TODO + }; + + xhr.open(method, endpoint, true); + xhr.timeout = 8000; + xhr.setRequestHeader("Accept", "application/json"); + xhr.setRequestHeader("Authorization", 'MediaBrowser Token="' + apiKey + '"'); // TODO + xhr.send(); +} + +// On Settings Save/Sync Button Press +//// Get User ID + +//curl -H "Authorization: MediaBrowser Token=\"TOKEN\"" -X GET 'BASEURL/Users' + +// Get Track List Using Logic From JellyPod Server + +// 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' diff --git a/wscript b/wscript new file mode 100644 index 0000000..5238bc8 --- /dev/null +++ b/wscript @@ -0,0 +1,54 @@ +# +# This file is the default set of rules to compile a Pebble application. +# +# Feel free to customize this to your needs. +# +import os.path + +top = '.' +out = 'build' + + +def options(ctx): + ctx.load('pebble_sdk') + + +def configure(ctx): + """ + This method is used to configure your build. ctx.load(`pebble_sdk`) automatically configures + a build for each valid platform in `targetPlatforms`. Platform-specific configuration: add your + change after calling ctx.load('pebble_sdk') and make sure to set the correct environment first. + Universal configuration: add your change prior to calling ctx.load('pebble_sdk'). + """ + ctx.load('pebble_sdk') + + +def build(ctx): + ctx.load('pebble_sdk') + + build_worker = os.path.exists('worker_src') + binaries = [] + + cached_env = ctx.env + for platform in ctx.env.TARGET_PLATFORMS: + ctx.env = ctx.all_envs[platform] + ctx.set_group(ctx.env.PLATFORM_NAME) + app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) + ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), target=app_elf, bin_type='app') + + if build_worker: + worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) + binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) + ctx.pbl_build(source=ctx.path.ant_glob('worker_src/c/**/*.c'), + target=worker_elf, + bin_type='worker') + else: + binaries.append({'platform': platform, 'app_elf': app_elf}) + ctx.env = cached_env + + ctx.set_group('bundle') + ctx.pbl_bundle(binaries=binaries, + js=ctx.path.ant_glob(['src/pkjs/**/*.js', + 'src/pkjs/**/*.json', + 'src/common/**/*.js']), + js_entry_file='src/pkjs/index.js')