mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-20 09:36:16 +00:00
Adds the soundtouch-player "Library" tab (Preact + htm), implementing the discover -> add server -> browse -> play flow over the device-scoped library API. Device is picked up front (browsing is speaker-native), then: find LAN servers (SSDP) and add one to the speaker, open a registered server, navigate folders via a breadcrumb, and play a track via native STORED_MUSIC. Mirrors the TuneIn/RadioBrowser components and reuses their CSS classes; marked BETA. api.js gains libraryDiscover/Servers/AddServer/ RemoveServer/Browse/Play; app.js gets the nav entry, title, and route. Validated end to end through the running player against real hardware (FRITZ!Box media server -> ST10): now_playing source=STORED_MUSIC status=PLAY_STATE. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
4.4 KiB
JavaScript
75 lines
4.4 KiB
JavaScript
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
|
|
async function req(url, opts = {}) {
|
|
const r = await fetch(url, opts);
|
|
return r.json();
|
|
}
|
|
|
|
export const api = {
|
|
devices: () => req('/api/control/devices'),
|
|
device: (id) => req(`/api/control/devices/${id}`),
|
|
removeDevice: (id) => req(`/api/control/devices/${id}`, { method: 'DELETE' }),
|
|
discover: () => req('/api/control/discover', { method: 'POST' }),
|
|
key: (id, key) => req(`/api/control/devices/${id}/key/${key}`, { method: 'POST' }),
|
|
volume: (id, level) => req(`/api/control/devices/${id}/volume/${level}`, { method: 'POST' }),
|
|
bass: (id, level) => req(`/api/control/devices/${id}/action/bass`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify({ level }),
|
|
}),
|
|
power: (id) => req(`/api/control/devices/${id}/power`, { method: 'POST' }),
|
|
recents: (id) => req(`/api/control/devices/${id}/recents`),
|
|
zone: (id) => req(`/api/control/devices/${id}/zone`),
|
|
zoneAdd: (masterId, slaveId) => req(`/api/control/devices/${masterId}/zone/add/${slaveId}`, { method: 'POST' }),
|
|
zoneRemove: (masterId, slaveId) => req(`/api/control/devices/${masterId}/zone/remove/${slaveId}`, { method: 'POST' }),
|
|
zoneDissolve: (id) => req(`/api/control/devices/${id}/zone/dissolve`, { method: 'POST' }),
|
|
zoneLeave: (id) => req(`/api/control/devices/${id}/zone/leave`, { method: 'POST' }),
|
|
play: (id, item) => req(`/api/control/devices/${id}/play`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify(item),
|
|
}),
|
|
tuneInBrowse: (path) => req(path ? `/api/control/providers/tunein/navigate/${path}` : '/api/control/providers/tunein/navigate'),
|
|
tuneInSearch: (q) => req(`/api/control/providers/tunein/search?q=${encodeURIComponent(q)}`),
|
|
tuneInSearchNext: (cursor) => req(`/api/control/providers/tunein/search/next?cursor=${encodeURIComponent(cursor)}`),
|
|
control: (id, action, presetId) => req(`/api/control/devices/${id}/action/${action}?id=${presetId}`),
|
|
storePreset: (id, slotId) => req(`/api/control/devices/${id}/action/storepreset?id=${slotId}`),
|
|
selectSource: (id, source, account) => req(`/api/control/devices/${id}/action/source?name=${encodeURIComponent(source)}&account=${encodeURIComponent(account || '')}`),
|
|
tuneInPlay: (deviceId, item) => req(`/api/control/devices/${deviceId}/providers/tunein/play`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify(item),
|
|
}),
|
|
radioBrowserSearch: (q) => req(`/api/control/providers/radiobrowser/search?q=${encodeURIComponent(q)}`),
|
|
radioBrowserPlay: (deviceId, item) => req(`/api/control/devices/${deviceId}/providers/radiobrowser/play`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify(item),
|
|
}),
|
|
playURL: (deviceId, url, name, imageUrl, serviceUrl) => req(`/api/control/devices/${deviceId}/providers/url/play`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify({ url, name, imageUrl, serviceUrl }),
|
|
}),
|
|
speak: (deviceId, text) => req(`/api/control/devices/${deviceId}/providers/tts/play`, {
|
|
method: 'POST',
|
|
headers: JSON_HEADERS,
|
|
body: JSON.stringify({ text }),
|
|
}),
|
|
libraryDiscover: (timeout) => req(`/api/control/providers/library/servers${timeout ? `?timeout=${timeout}` : ''}`),
|
|
libraryServers: (id) => req(`/api/control/devices/${id}/library/servers`),
|
|
libraryAddServer: (id, body) => req(`/api/control/devices/${id}/library/servers`, { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify(body) }),
|
|
libraryRemoveServer: (id, account) => req(`/api/control/devices/${id}/library/servers/${encodeURIComponent(account)}`, { method: 'DELETE' }),
|
|
libraryBrowse: (id, { account, location, type, start, count }) => {
|
|
const qs = [
|
|
`account=${encodeURIComponent(account)}`,
|
|
location !== undefined && location !== '' ? `location=${encodeURIComponent(location)}` : null,
|
|
type ? `type=${encodeURIComponent(type)}` : null,
|
|
start !== undefined ? `start=${encodeURIComponent(start)}` : null,
|
|
count !== undefined ? `count=${encodeURIComponent(count)}` : null,
|
|
].filter(Boolean).join('&');
|
|
return req(`/api/control/devices/${id}/library/browse?${qs}`);
|
|
},
|
|
libraryPlay: (id, body) => req(`/api/control/devices/${id}/library/play`, { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify(body) }),
|
|
};
|