roonpipe 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -91,7 +91,11 @@ Or if installed from source:
91
91
  pnpm run cli
92
92
  ```
93
93
 
94
- Use arrow keys to navigate results and press Enter to play.
94
+ Features:
95
+ - Use arrow keys to navigate search results
96
+ - Press Enter to select a track
97
+ - Choose an action: Play now, Add to queue, or Play next
98
+ - Press Ctrl+C to exit
95
99
 
96
100
  ### Development Mode
97
101
 
@@ -149,19 +153,46 @@ Response:
149
153
 
150
154
  ### Play
151
155
 
156
+ Play a track immediately (preserves current queue):
157
+
158
+ ```bash
159
+ echo '{"command":"play","item_key":"10:0","session_key":"search_1234567890","action":"playNow"}' | nc -U /tmp/roonpipe.sock
160
+ ```
161
+
162
+ Add to the queue:
163
+
164
+ ```bash
165
+ echo '{"command":"play","item_key":"10:0","session_key":"search_1234567890","action":"queue"}' | nc -U /tmp/roonpipe.sock
166
+ ```
167
+
168
+ Play next (add after current track):
169
+
152
170
  ```bash
153
- echo '{"command":"play","item_key":"10:0","session_key":"search_1234567890"}' | nc -U /tmp/roonpipe.sock
171
+ echo '{"command":"play","item_key":"10:0","session_key":"search_1234567890","action":"addNext"}' | nc -U /tmp/roonpipe.sock
154
172
  ```
155
173
 
174
+ Replace the queue and play immediately:
175
+
176
+ ```bash
177
+ echo '{"command":"play","item_key":"10:0","session_key":"search_1234567890","action":"play"}' | nc -U /tmp/roonpipe.sock
178
+ ```
179
+
180
+ Available actions:
181
+ - `play` (default) — Replace queue and play immediately
182
+ - `playNow` — Play immediately while preserving the queue (adds next and skips)
183
+ - `queue` — Add to the end of the queue
184
+ - `addNext` — Add after the current track
185
+
156
186
  ## Project Structure
157
187
 
158
188
  ```
159
189
  src/
160
- ├── index.ts # Entry point, daemon/CLI mode switching
161
- ├── roon.ts # Roon API connection and browsing
162
- ├── mpris.ts # MPRIS player, notifications, metadata
163
- ├── socket.ts # Unix socket server
164
- └── cli.ts # Interactive terminal interface
190
+ ├── index.ts # Entry point, daemon/CLI mode switching
191
+ ├── roon.ts # Roon API connection and browsing
192
+ ├── mpris.ts # MPRIS player, notifications, metadata
193
+ ├── socket.ts # Unix socket server
194
+ ├── image-cache.ts # Album artwork caching
195
+ └── cli.ts # Interactive terminal interface
165
196
  ```
166
197
 
167
198
  ## Contributing
package/dist/cli.js CHANGED
@@ -109,19 +109,46 @@ function selectTrack(results) {
109
109
  }
110
110
  });
111
111
  }
112
- function playTrack(track) {
112
+ function selectAction() {
113
113
  return __awaiter(this, void 0, void 0, function* () {
114
- console.log(`\n▶️ Playing: ${track.title}${track.subtitle ? ` · ${track.subtitle}` : ""}\n`);
114
+ try {
115
+ const action = yield (0, prompts_1.select)({
116
+ message: "What do you want to do?",
117
+ choices: [
118
+ { name: "▶️ Play now", value: "playNow" },
119
+ { name: "📋 Add to queue", value: "queue" },
120
+ { name: "⏭️ Play next", value: "addNext" },
121
+ new prompts_1.Separator(),
122
+ { name: "← Back", value: "back" },
123
+ ],
124
+ theme: { prefix: "" },
125
+ });
126
+ return action === "back" ? null : action;
127
+ }
128
+ catch (_a) {
129
+ return null;
130
+ }
131
+ });
132
+ }
133
+ function playTrack(track, action) {
134
+ return __awaiter(this, void 0, void 0, function* () {
135
+ const actionLabels = {
136
+ playNow: "▶️ Playing",
137
+ queue: "📋 Added to queue",
138
+ addNext: "⏭️ Playing next",
139
+ };
140
+ console.log(`\n${actionLabels[action]}: ${track.title}${track.subtitle ? ` · ${track.subtitle}` : ""}\n`);
115
141
  try {
116
142
  yield sendCommand({
117
143
  command: "play",
118
144
  item_key: track.item_key,
119
145
  session_key: track.sessionKey,
146
+ action,
120
147
  });
121
- console.log("✅ Playback started!\n");
148
+ console.log("✅ Success!\n");
122
149
  }
123
150
  catch (error) {
124
- console.error("❌ Failed to play track:", error);
151
+ console.error("❌ Failed:", error);
125
152
  }
126
153
  });
127
154
  }
@@ -146,7 +173,12 @@ function startCLI() {
146
173
  if (selected.subtitle === "__search__") {
147
174
  continue;
148
175
  }
149
- yield playTrack(selected);
176
+ const action = yield selectAction();
177
+ if (action === null) {
178
+ // User chose "Back", continue to track selection
179
+ continue;
180
+ }
181
+ yield playTrack(selected, action);
150
182
  }
151
183
  });
152
184
  }
package/dist/roon.js CHANGED
@@ -32,7 +32,7 @@ function initRoon(callbacks) {
32
32
  const roon = new node_roon_api_1.default({
33
33
  extension_id: "com.bluemancz.roonpipe",
34
34
  display_name: "RoonPipe",
35
- display_version: "1.0.0",
35
+ display_version: "1.0.2",
36
36
  publisher: "BlueManCZ",
37
37
  email: "your@email.com",
38
38
  website: "https://github.com/bluemancz/roonpipe",
@@ -112,7 +112,7 @@ function searchRoon(query) {
112
112
  }
113
113
  const tracksCategory = (_a = loadResult.items) === null || _a === void 0 ? void 0 : _a.find((item) => item.title === "Tracks");
114
114
  if (!tracksCategory) {
115
- // No tracks category found - return empty array
115
+ // No tracks category found - return an empty array
116
116
  resolve([]);
117
117
  return;
118
118
  }
@@ -147,7 +147,40 @@ function searchRoon(query) {
147
147
  });
148
148
  });
149
149
  }
150
- function playItem(itemKey, sessionKey) {
150
+ const ACTION_TITLES = {
151
+ play: ["Play Now", "Play"],
152
+ queue: ["Queue", "Add to Queue"],
153
+ addNext: ["Play From Here", "Add Next"],
154
+ };
155
+ /**
156
+ * Skip to the next track in the queue
157
+ */
158
+ function skipToNext() {
159
+ return new Promise((resolve, reject) => {
160
+ if (!coreInstance || !zone) {
161
+ reject("Not connected");
162
+ return;
163
+ }
164
+ coreInstance.services.RoonApiTransport.control(zone, "next", (err) => {
165
+ if (err)
166
+ reject(err);
167
+ else
168
+ resolve();
169
+ });
170
+ });
171
+ }
172
+ function playItem(itemKey_1, sessionKey_1) {
173
+ return __awaiter(this, arguments, void 0, function* (itemKey, sessionKey, action = "play") {
174
+ // "playNow" = add next + skip to it (preserves queue)
175
+ if (action === "playNow") {
176
+ yield playItemInternal(itemKey, sessionKey, "addNext");
177
+ yield skipToNext();
178
+ return;
179
+ }
180
+ return playItemInternal(itemKey, sessionKey, action);
181
+ });
182
+ }
183
+ function playItemInternal(itemKey, sessionKey, action) {
151
184
  return new Promise((resolve, reject) => {
152
185
  if (!coreInstance) {
153
186
  reject("Roon Core not connected");
@@ -158,6 +191,7 @@ function playItem(itemKey, sessionKey) {
158
191
  return;
159
192
  }
160
193
  const browse = coreInstance.services.RoonApiBrowse;
194
+ const actionTitles = ACTION_TITLES[action];
161
195
  function loadUntilAction(currentItemKey, depth = 0) {
162
196
  if (depth > 5) {
163
197
  reject("Too many levels, cannot find action");
@@ -190,20 +224,20 @@ function playItem(itemKey, sessionKey) {
190
224
  reject("No items found");
191
225
  return;
192
226
  }
193
- const playNowAction = loadResult.items.find((item) => item.hint === "action" &&
194
- (item.title === "Play Now" || item.title === "Play"));
195
- if (playNowAction) {
227
+ const targetAction = loadResult.items.find((item) => item.hint === "action" &&
228
+ actionTitles.some((title) => item.title === title));
229
+ if (targetAction) {
196
230
  browse.browse({
197
231
  hierarchy: "search",
198
232
  multi_session_key: sessionKey,
199
- item_key: playNowAction.item_key,
233
+ item_key: targetAction.item_key,
200
234
  zone_or_output_id: zone.zone_id,
201
235
  }, (playError) => {
202
236
  if (playError) {
203
237
  reject(playError);
204
238
  }
205
239
  else {
206
- console.log("Successfully started playback");
240
+ console.log(`Successfully executed action: ${action}`);
207
241
  resolve();
208
242
  }
209
243
  });
@@ -214,7 +248,7 @@ function playItem(itemKey, sessionKey) {
214
248
  loadUntilAction(actionList.item_key, depth + 1);
215
249
  }
216
250
  else {
217
- reject("Could not find Play action or next level");
251
+ reject(`Could not find ${action} action or next level`);
218
252
  }
219
253
  }
220
254
  });
package/dist/socket.js CHANGED
@@ -66,7 +66,7 @@ function startSocketServer(handlers) {
66
66
  }
67
67
  else if (request.command === "play") {
68
68
  try {
69
- yield handlers.play(request.item_key, request.session_key);
69
+ yield handlers.play(request.item_key, request.session_key, request.action);
70
70
  client.write(`${JSON.stringify({ error: null, success: true })}\n`);
71
71
  }
72
72
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roonpipe",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Linux integration for Roon – MPRIS support, media keys, desktop notifications, and interactive search CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {