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 +38 -7
- package/dist/cli.js +37 -5
- package/dist/roon.js +43 -9
- package/dist/socket.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,7 +91,11 @@ Or if installed from source:
|
|
|
91
91
|
pnpm run cli
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
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
|
|
161
|
-
├── roon.ts
|
|
162
|
-
├── mpris.ts
|
|
163
|
-
├── socket.ts
|
|
164
|
-
|
|
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
|
|
112
|
+
function selectAction() {
|
|
113
113
|
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
-
|
|
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("✅
|
|
148
|
+
console.log("✅ Success!\n");
|
|
122
149
|
}
|
|
123
150
|
catch (error) {
|
|
124
|
-
console.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
|
|
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.
|
|
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
|
-
|
|
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
|
|
194
|
-
(
|
|
195
|
-
if (
|
|
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:
|
|
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(
|
|
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(
|
|
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) {
|