audiobookshelf-sonos
audiobookshelf-sonos copied to clipboard
More detailed instruction
Hey,
could you write a more detailed instruction please?
thanks :)
Could you be a bit more specific in what you're looking for? The "how to use" section is step by step on how to get things running. I'm more than happy to add additional information, but I'm not sure what exactly you're looking for
He might mean weather you are going to publish it on DockerHub? This might make it easier for people to implement. Just guessing here, and following this project with great interest!
Hey, sorry my english is no t so good...
If i use docker-compose with docker file.. which file i should edit?
so the docker-compose is running http://xxx.xxx.xxx.xxx:3333/wsdl
there is only a white site
could i see a log ... to analyze whats the problem
I think a more detailed how-to might help. I am struggling with quite a few point:
- where can the file "soap-server.js" be found? it is described and when running without docker it seems to be the mian file ("run node soap-server.js") but I cannot find that file not found in this repo
Since I couldnt find it I tried via docker-compose
- docker-compose refers to node-docker but that cannot be found or installed.
audiobookshelf-sonos Error
Error response from daemon: pull access denied for node-docker, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
-
The "ABS_TOKEN=" likely stands for the audiobookshelf user token, that can be found when logging in as root and clicking on settings -> users -> specific username
-
ABS_LIBRARY_ID= is not clear. Does it expect a ID (likely numeric) or the name of the library?
-
are the modifications of config.js and sonos.wsdl also necessary when running via docker compose? if yes how does docker compose know which files to take - the only volumne defined there is sonos.wsdl?
I've spent a bit of time looking at this and have a few answers to the above.
- The file is server.js, I think it's been renamed but the docs haven't been updated.
- The docker image isn't hosted anywhere, so you need to build it. If you cd into the cloned directory, and run the command
docker build -t node-docker:latest
, the command will run and it should work. - Pretty sure this is correct.
- This is a bit confusing, if you've got just one library, I belive the id is just "main". If you have a second library, you can see the id in the URL, "lib_xxxx".
- I think you need to add the environment variables in the Docker compose, and modify the sonos.wsdl file as well. From what I can see, you need to avoid inverted commas in the docker-compose ENV variables.
Anyway, I've tried all these, and still not working.
@jmt-gh, what sort of response should we be getting from the URLs: https://soap.server.com/wsdl or https://soap.server.com/manifest?
I get a 200 response but 0 bytes return from the /wsdl, and this response from the /manifest:
{
"schemaVersion": "1.0",
"endpoints": [
{
"type": "reporting",
"uri": "https://soap.server.com/wsdl/playback/v2.1/report"
}
]
}
Seems like there should be a bit more in the wsdl response at least.
By the way, if I use this for the env variable in the docker-compose:
- SOAP_URI="https://soap.server.com/wsdl"
The return from the manifest shows this:
"uri": '"https://soap.server.com/wsdl"/playback/v2.1/report'
When I remove the "", it displays correctly.
should the abs uri only a http or could be https?
i get {"schemaVersion":"1.0","endpoints":[{"type":"reporting","uri":"https://soabs.xxxx.de/wsdl/playback/v2.1/report"}]} what is right, but i can´t access in sonos
should the abs uri only a http or could be https?
I'd have thought whatever you have set up. Mine is https, so that's what I've put in.
ok me too.. but the sonos app can´t read the libary
Mine can't either, I get a connection error. There's no feedback on the log of the container, so it's quite hard to debug it.
schade eigentlich wirkte das Projekt vielversprechend
@jt196 did you ever get this working?
@jt196 did you ever get this working?
Hacked away at it for half a day but never got it to work. I could add the service to Sonos by unchecking "Support Manifest File", that's about as far as I got. There were no logs on the Docker container when I tried to access it in Sonos, and Sonos itself have disabled a lot of the log checking functions in their speakers. I went a little through their docs, but had little success I'm afraid.
Hi!
I set this up in a docker container on Unraid, Trial and error but it's running except cant find a way to access the library in the sonosapp.
The docker log gives me this error: [getLibraryItems] Error caught. Error: Request failed with status code 404
@eendenhart no idea, but maybe post how you got so far here so maybe we can get a solution together?
404 is just a not found error, so likely you've entered the audiobookshelf URL incorrectly.
I think the url is correct; I checked many times. I got a connection in audiobookshelf: when i typ the wrong token he gives in the logs of audiobookshelf an error.
What i did:
- create a directory: /mnt/user/appdata/audiobookshelf-sonos
- cloned the repository to this folder
- Update the sonos.wsdl file with my SOAP_URI
- create the docker image: execute in the unraid terminal
docker build /mnt/user/appdata/audiobookshelf-sonos
- renamed this docker image: node-docker
docker tag 'containerid' node-docker
- created the container from my dockerhub:
https://hub.docker.com/r/eendenhart/audiobookshelf-sonos/
- changed the repository in node-docker:latest
- port:
80:3333
- added ENV-variables: SOAP_URI, ABS_URI, ABS_LIBRARY_ID, ABS_TOKEN, SONOS_WSDL_FILE, SOAP_ENDPOINT
- add volume: /mnt/user/appdata/audiobookshelf-sonos:/app/sonos.wsdl
I got the error [getLibraryItems] Error caught. Error: Request failed with status code 404
when i hit the services in sonos
@jt196 did you ever get this working?
Hacked away at it for half a day but never got it to work. I could add the service to Sonos by unchecking "Support Manifest File", that's about as far as I got. There were no logs on the Docker container when I tried to access it in Sonos, and Sonos itself have disabled a lot of the log checking functions in their speakers. I went a little through their docs, but had little success I'm afraid.
I've put a bit of time into this today and got a little further but still not working.
First of all I turned on debugging in the servert.js file by uncommenting line 37. Now we can see incoming requests and how the SOAP server responds.
Now for what I had been doing wrong:
- I forgot to update the supplied sonos.wsdl with my SOAP_URI on line 2062
- when adding the service to sonos for the secure endpoint don't use https, just use http (unless of course, you have the endpoint running on https)
- The library ID isn't "main", nor did mine start with "lib_", in audiobookshelf open your library and it's in the url: "http://127.0.0.1:13378/library/[YOUR LIBRARY ID]", alternatively get it with curl following the instructions here: https://api.audiobookshelf.org/#libraries
The first few requests go through and seem to make it to audiobookshelf, however I'm hitting a wall with the error:
TypeError: Cannot read properties of undefined (reading 'id')
Edit: A bit more digging and I got there. Comment out lines: 104, 105, 106, 107, 108, 139, 140, 141, 142, 144, 145 in utils.js. It seems if you have any book metadata missing (author, narrator, album art etc) it causes the wrong data to be returned. after commenting out these lines Sonos finally connects to audio book shelf and shows my library.
However, it still does not play any of the books, saying Sonos is unable to connect. I suspect the function getMediaMetadata on line 16 of sonos-service.js needs completing.
@robwalster I've played around a little bit and made no progress at all. Some pointers that might be useful.
- If you're running Dockerised (and it might be worth trying to run this outside a docker environment, as it can definitely cause issues!), then try adding nodemon to the Dockerfile. If you're sharing the local files, you won't have to restart the dev container.
FROM node:16-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY ["package.json", "package-lock.json", "./"]
RUN npm install --production && npm install -g nodemon
COPY . .
CMD ["nodemon", "server.js"]
- Navigate to the page http://
/wsdl and see if the wsdl file is served there. I can't see one, maybe I got some stuff wrong. I can't see anything on my Sonos app. If you can, I'd be interested in knowing a bit more about your env variables. - Instead of commenting out the utils, try this code. It'll just enter null if they're empty. You may want to replace the null with '' if that breaks anything.
async function buildLibraryMetadataResult(res) { if (!res || !res.results) { return null; }
let libraryItems = res.results;
let count = res.total;
let total = count;
let mediaMetadata = [];
for (const libraryItem of libraryItems) {
if (!libraryItem || !libraryItem.media || !libraryItem.media.audioFiles || !libraryItem.media.audioFiles[0]) {
continue;
}
let authorId = libraryItem.media.metadata && libraryItem.media.metadata.authors && libraryItem.media.metadata.authors[0] ? libraryItem.media.metadata.authors[0].id : null;
let authorName = authorId ? libraryItem.media.metadata.authors[0].name : null;
let narratorId = libraryItem.media.metadata && libraryItem.media.metadata.narrators && libraryItem.media.metadata.narrators[0] ? libraryItem.media.metadata.narrators[0].id : null;
let narratorName = narratorId ? libraryItem.media.metadata.narrators[0].name : null;
var mediaMetadataEntry = {
itemType: "audiobook",
id: libraryItem.id,
mimeType: libraryItem.media.audioFiles[0].mimeType,
canPlay: true,
canResume: true,
title: libraryItem.media.metadata ? libraryItem.media.metadata.title : null,
summary: libraryItem.media.metadata ? libraryItem.media.metadata.description : null,
authorId: authorId,
author: authorName,
narratorId: narratorId,
narrator: narratorName,
albumArtURI: `${ABS_URI}${libraryItem.media.coverPath}?token=${ABS_TOKEN}`,
};
mediaMetadata.push(mediaMetadataEntry);
}
return {
getMetadataResult: {
count: count,
total: total,
index: 0,
mediaCollection: mediaMetadata,
},
};
}
async function buildAudiobookTrackList(libraryItem, progressData) {
if (!libraryItem || !libraryItem.media || !libraryItem.media.audioFiles) {
return null;
}
let tracks = libraryItem.media.audioFiles;
let icount = tracks.length;
let itotal = tracks.length;
let imediaMetadata = [];
for (const track of tracks) {
let authorId = libraryItem.media.metadata && libraryItem.media.metadata.authors && libraryItem.media.metadata.authors[0] ? libraryItem.media.metadata.authors[0].id : null;
let authorName = authorId ? libraryItem.media.metadata.authors[0].name : null;
let narratorId = libraryItem.media.metadata && libraryItem.media.metadata.narrators && libraryItem.media.metadata.narrators[0] ? libraryItem.media.metadata.narrators[0].id : null;
let narratorName = narratorId ? libraryItem.media.metadata.narrators[0].name : null;
var mediaMetadataEntry = {
id: `${libraryItem.id}/${track.metadata.filename}`,
itemType: "track",
title: track.metadata.filename,
mimeType: track.mimeType,
trackMetadata: {
authorId: authorId,
author: authorName,
narratorId: narratorId,
narrator: narratorName,
duration: track.duration,
book: libraryItem.media.metadata ? libraryItem.media.metadata.title : null,
albumArtURI: `${ABS_URI}${libraryItem.media.coverPath}?token=${ABS_TOKEN}`,
canPlay: true,
canAddToFavorites: false,
},
};
imediaMetadata.push(mediaMetadataEntry);
}
let positionInformation = {};
if (progressData) {
positionInformation = {
id: `${libraryItem.id}/${progressData.partName}`,
index: 0,
offsetMillis: Math.round(progressData.relativeTimeForPart * 1000),
};
}
return {
getMetadataResult: {
count: icount,
total: itotal,
index: 0,
positionInformation: positionInformation,
mediaMetadata: imediaMetadata,
},
};
}
Edit, 'scuse the crappy formating, didn't want to paste the whole lot here but the details tags don't play well with code.
OK it appears to be an issue with the buildLibraryMetadata function.
This is an object being fed into the loop:
{
id: '70edd71c-0088-49b8-8ea5-210c8049ce09',
ino: '370123',
oldLibraryItemId: 'ab_vm3xuee9z2z7krgw7q',
libraryId: '96cfd553-cc4e-4a9b-9f67-866838eb62d7',
folderId: '92fb9312-b5e2-4aae-b1d4-96fd7e521299',
path: '/audiobooks/C.S. Forester/The Good Shepherd',
relPath: 'C.S. Forester/The Good Shepherd',
isFile: false,
mtimeMs: 1594763569000,
ctimeMs: 1641490864998,
birthtimeMs: 1641490864998,
addedAt: 1646732242162,
updatedAt: 1646820362213,
isMissing: false,
isInvalid: false,
mediaType: 'book',
media: {
id: '43c51480-1e3c-4b50-88b7-b7f045065474',
metadata: {
title: 'The Good Shepherd',
titleIgnorePrefix: 'Good Shepherd, The',
subtitle: null,
authorName: 'C.S. Forester',
authorNameLF: 'Forester, C.S.',
narratorName: 'Edoardo Ballerini',
seriesName: '',
genres: [Array],
publishedYear: '2020',
publishedDate: null,
publisher: 'Podium Audio',
description: 'The Good Shepherd is now a major motion picture, Greyhound , scripted by and starring Tom Hanks, directed by Aaron Schneider, and produced by Gary Goetzman. A convoy of 37 merchant ships is ploughing through icy, submarine-infested North Atlantic seas during the most critical days of World War II, when the German submarines had the upper hand and Allied shipping was suffering heavy losses. In charge is Commander George Krause, an untested veteran of the US Navy. Hounded by a wolf pack of German U-boats, he faces 48 hours of desperate peril trapped the bridge of the ship. Exhausted beyond measure, he must make countless and terrible decisions as he leads his small fighting force against the relentless U-boats.',
isbn: null,
asin: '1774244152',
language: 'English',
explicit: false,
abridged: false
},
coverPath: '/audiobooks/C.S. Forester/The Good Shepherd/The Good Shepherd - 001.mp3',
tags: [],
numTracks: 17,
numAudioFiles: 17,
numChapters: 33,
numMissingParts: 0,
numInvalidAudioFiles: 0,
duration: 28401.437333,
size: 454976356,
ebookFormat: null
},
numFiles: 17,
size: 454976356
}
The function expects:
- media.audioFiles
- media.metadata.authors
- media.metadata.narrators
None of these exist in the object, so no wonder it's not returning any results.
According to the API docs, these should be returned in library > library item > book
But for some reason, it's not doing that.
The reason I can see, looks like expected behaviour, getting library items will not retrieve the expected data. The audioFiles and the metadata.authors, metadata.narrators simply aren't there.
With the data logging, this amended function will return a soap style object in the console:
async function buildLibraryMetadataResult(res) {
if (!res || !res.results) {
return null;
}
let libraryItems = res.results;
let total = count;
let mediaMetadata = [];
for (const libraryItem of libraryItems) {
var mediaMetadataEntry = {
itemType: "audiobook",
id: libraryItem.id,
canPlay: true,
canResume: true,
title: libraryItem.media.metadata
? libraryItem.media.metadata.title
: null,
summary: libraryItem.media.metadata
? libraryItem.media.metadata.description
: null,
author: libraryItem.media.metadata.authorName,
narrator: libraryItem.media.metadata.narratorName,
albumArtURI: `${ABS_URI}${libraryItem.media.coverPath}?token=${ABS_TOKEN}`,
};
mediaMetadata.push(mediaMetadataEntry);
}
return {
getMetadataResult: {
count: count,
total: total,
index: 0,
mediaCollection: mediaMetadata,
},
};
}
But I still can't get it to show the items...
I've tried a bunch of stuff and still can't get it to work.
The soap server is working correctly, and returning a correct response, with a properly structured soap xml. I've tested this on SoapUI - even to the point where I'm using the stock responses in the docs here and they still don't work.
I'm wondering at this point whether the customsd form is being filled incorrectly. It's polling the server and SoapUI, but not happy with the result.
Maybe unchecking the "Support Manifest File" is causing the issue? I can't add the service if I don't do this.
I've put the updated/worked on files in a new repo. They should maybe get some folks a bit further along the line.
I've also added notes about testing, debugging etc. Still can't get it working though!
:wave: Hello all. Sorry for not being particularly active on this project. I can't commit to helping resolve everything here, but I'll try and get things up and running locally again and see if there are any glaring issues I find.
A few things to note:
- This was built against whatever the latest stable version of audiobookshelf was at the time last year when it was first built. At that time, there was no stable public API interfaces to build against, so it's very possible some of the ABS response schemas have changed (@jt196 this is probably what you're seeing in the response above)
- The sonos app + OS (android specifically) are really finnicky about HTTP/HTTPS, and cert chains. I tried to make it clear what was needed here in the set up instructions, but please double check. I only had this working with a reverse proxy handling the HTTPS -- if you're trying to do it without that, I don't have any explicit answers, but open to updating the README if someone figures out solutions to any issues there
tl;dr: I was able to get things up easily still, but not necessairly fully funcitonal and running. The latter due to API changes in ABS.
OK, made some progress debugging. A few things:
- I'm still able to get things up and running to some extent (things aren't completely busted)
- It "runs" against the latest version of ABS (2.4.3), but it definitely isn't compatible
- The sonos app is still able to see the custom abs-sonos application I've added
- Interacting with it does cause network requests to hit audiobookshelf-sonos docker container
Some initial things I had to change to make some progress:
- I had to go get a new library ID. Looks like ABS updated their formatting for that. It is now something more akin to a UUID
- It was crashing all over buildLibraryMetadataResults
- This is the method that actually takes the library results (which come back successfully) from ABS and formats them in a way to hand off to the SOAP server for SONOS to read. Things here got busted at some point due to ABS API updates.
- At first it was just hard crashing to a white screen in the sonos app.
- After updating some of the building of mediaMetadataEntry to comment out things that weren't being responded, I was able to get the SONOS app to start displaying my library
- The "new" version of mediaMetadataEntry:
for (const libraryItem of libraryItems) {
// https://developer.sonos.com/build/content-service-add-features/save-resume-playback/
var mediaMetadataEntry = {
itemType: "audiobook",
id: libraryItem.id,
//mimeType: libraryItem.media.audioFiles[0].mimeType,
canPlay: true,
canResume: true,
title: libraryItem.media.metadata.title,
summary: libraryItem.media.metadata.description,
//authorId: libraryItem.media.metadata.authors[0].id,
//author: libraryItem.media.metadata.authors[0].name,
//narratorId: libraryItem.media.metadata.narrators[0].id,
//narrator: libraryItem.media.metadata.narrators[0].name,
//albumArtURI: `${ABS_URI}${libraryItem.media.coverPath}?token=${ABS_TOKEN}`,
};
mediaMetadata.push(mediaMetadataEntry);
}
I'll keep poking around, as well as update this repo with the new "debugging support" I've added to a few of thes methods to help. My gut is telling me there's a decent chunk of work that needs to be done here to get it working though, or at least working to the level it was prior. That said, the initial issues that were had in this issue definitely weren't related to this, and were most likely just environmental set up issues.
Some more progress:
Had to comment out some more due-to-API-changes fields:
async function buildAudiobookTrackList(libraryItem, progressData) {
let tracks = libraryItem.media.audioFiles;
let icount = tracks.length;
let itotal = tracks.length;
let imediaMetadata = [];
for (const track of tracks) {
var mediaMetadataEntry = {
id: `${libraryItem.id}/${track.metadata.filename}`,
itemType: "track",
title: track.metadata.filename,
mimeType: track.mimeType,
trackMetadata: {
authorId: libraryItem.media.metadata.authors[0].id,
author: libraryItem.media.metadata.authors[0].name,
//narratorId: libraryItem.media.metadata.narrators[0].id,
//narrator: libraryItem.media.metadata.narrators[0].name,
duration: track.duration,
book: libraryItem.media.metadata.title,
albumArtURI: `${ABS_URI}${libraryItem.media.coverPath}?token=${ABS_TOKEN}`,
canPlay: true,
canAddToFavorites: false,
},
};
imediaMetadata.push(mediaMetadataEntry);
}
This brings SONOS to actually trying to play the file, but throws an error modal in the app that says "Part01.mp3 is no longer available on audiobookshelf" which is interesting. Though the soap part of the server is actively running and participating at this point, as indicated by these messages in the docker output:
audiobookshelf-sonos | getMediaURI called
audiobookshelf-sonos | [soapServer] /playback/v2.1/report/timePlayed called
audiobookshelf-sonos | [soapServer] /playback/v2.1/report/timePlayed called
audiobookshelf-sonos | oh it got called?
which is great
ABS API reference: https://api.audiobookshelf.org
OK. More progress.
I have audio streaming to the speakers again.
This is the function that actually generates the URL telling the speaker where to fetch the audio from. As with the other breakages, this is no longer valid, in a variety of ways.
async function buildMediaURI(id) {
let path = `${ABS_URI}/s/item/${id}?token=${ABS_TOKEN}`;
return {
getMediaURIResult: path,
};
}
The path needed now resembles something like: <ABS_URI>/api/items/<library_id>/file/<file_id>?token=<auth_token>
. Hardcoding path to be the appropriate URL using an auth token from a web session, and the audio starts playing.
This brings up a couple things:
- The library item id we've been getting has been updated to a UUID similarly to the library id.
- The library item id we had previously been using doesn' seem particularly relevant here
- A new API needs to be implemented to get the file_id (which points to the MP3 to play)
Number 3 doesn't need to happen. It's available in the libraryItem as the ino
field already