Fix bug where sonos app cannot navigate from track to artist when subsonic returns null artistId on song (#79)

This commit is contained in:
Simon J
2021-11-20 18:22:24 +11:00
committed by GitHub
parent 6321cb71a4
commit 89340dd454
6 changed files with 101 additions and 23 deletions

View File

@@ -25,7 +25,7 @@ export type AuthFailure = {
}; };
export type ArtistSummary = { export type ArtistSummary = {
id: string; id: string | undefined;
name: string; name: string;
image: BUrn | undefined; image: BUrn | undefined;
}; };

View File

@@ -312,10 +312,10 @@ export const track = (bonobUrl: URLBuilder, track: Track) => ({
album: track.album.name, album: track.album.name,
albumId: `album:${track.album.id}`, albumId: `album:${track.album.id}`,
albumArtist: track.artist.name, albumArtist: track.artist.name,
albumArtistId: `artist:${track.artist.id}`, albumArtistId: track.artist.id? `artist:${track.artist.id}` : undefined,
albumArtURI: defaultAlbumArtURI(bonobUrl, track).href(), albumArtURI: defaultAlbumArtURI(bonobUrl, track).href(),
artist: track.artist.name, artist: track.artist.name,
artistId: `artist:${track.artist.id}`, artistId: track.artist.id ? `artist:${track.artist.id}` : undefined,
duration: track.duration, duration: track.duration,
genre: track.album.genre?.name, genre: track.album.genre?.name,
genreId: track.album.genre?.id, genreId: track.album.genre?.id,

View File

@@ -144,12 +144,14 @@ type GetArtistResponse = SubsonicResponse & {
}; };
}; };
type song = { export type song = {
id: string; id: string;
parent: string | undefined; parent: string | undefined;
title: string; title: string;
album: string | undefined; album: string | undefined;
albumId: string | undefined;
artist: string | undefined; artist: string | undefined;
artistId: string | undefined;
track: number | undefined; track: number | undefined;
year: string | undefined; year: string | undefined;
genre: string | undefined; genre: string | undefined;
@@ -159,8 +161,6 @@ type song = {
bitRate: number | undefined; bitRate: number | undefined;
suffix: string | undefined; suffix: string | undefined;
contentType: string | undefined; contentType: string | undefined;
albumId: string | undefined;
artistId: string | undefined;
type: string | undefined; type: string | undefined;
userRating: number | undefined; userRating: number | undefined;
starred: string | undefined; starred: string | undefined;
@@ -270,9 +270,9 @@ export const asTrack = (album: Album, song: song): Track => ({
coverArt: coverArtURN(song.coverArt), coverArt: coverArtURN(song.coverArt),
album, album,
artist: { artist: {
id: `${song.artistId!}`, id: song.artistId,
name: song.artist!, name: song.artist ? song.artist : "?",
image: artistImageURN({ artistId: song.artistId }), image: song.artistId ? artistImageURN({ artistId: song.artistId }) : undefined,
}, },
rating: { rating: {
love: song.starred != undefined, love: song.starred != undefined,

View File

@@ -126,8 +126,8 @@ describe("InMemoryMusicService", () => {
describe("when it exists", () => { describe("when it exists", () => {
it("should provide an artist", async () => { it("should provide an artist", async () => {
expect(await musicLibrary.artist(artist1.id)).toEqual(artist1); expect(await musicLibrary.artist(artist1.id!)).toEqual(artist1);
expect(await musicLibrary.artist(artist2.id)).toEqual(artist2); expect(await musicLibrary.artist(artist2.id!)).toEqual(artist2);
}); });
}); });

View File

@@ -398,6 +398,60 @@ describe("track", () => {
}, },
}); });
}); });
describe("when there is no artistId from subsonic", () => {
it("should not send an artist id to sonos", () => {
const bonobUrl = url("http://localhost:4567/foo?access-token=1234");
const someTrack = aTrack({
id: uuid(),
// audio/x-flac should be mapped to audio/flac
mimeType: "audio/x-flac",
name: "great song",
duration: randomInt(1000),
number: randomInt(100),
album: anAlbum({
name: "great album",
id: uuid(),
genre: { id: "genre101", name: "some genre" },
}),
artist: anArtist({ name: "great artist", id: undefined }),
coverArt: { system: "subsonic", resource: "887766" },
rating: {
love: true,
stars: 5
}
});
expect(track(bonobUrl, someTrack)).toEqual({
itemType: "track",
id: `track:${someTrack.id}`,
mimeType: "audio/flac",
title: someTrack.name,
trackMetadata: {
album: someTrack.album.name,
albumId: `album:${someTrack.album.id}`,
albumArtist: someTrack.artist.name,
albumArtistId: undefined,
albumArtURI: `http://localhost:4567/foo/art/${encodeURIComponent(formatForURL(someTrack.coverArt!))}/size/180?access-token=1234`,
artist: someTrack.artist.name,
artistId: undefined,
duration: someTrack.duration,
genre: someTrack.album.genre?.name,
genreId: someTrack.album.genre?.id,
trackNumber: someTrack.number,
},
dynamic: {
property: [
{
name: "rating",
value: `${ratingAsInt(someTrack.rating)}`,
},
],
},
});
});
});
}); });
describe("album", () => { describe("album", () => {

View File

@@ -18,6 +18,7 @@ import {
asTrack, asTrack,
artistImageURN, artistImageURN,
images, images,
song,
} from "../src/subsonic"; } from "../src/subsonic";
import axios from "axios"; import axios from "axios";
@@ -627,10 +628,33 @@ describe("artistURN", () => {
}); });
describe("asTrack", () => { describe("asTrack", () => {
const album = anAlbum(); describe("when the song has no artistId", () => {
const track = aTrack(); const album = anAlbum();
const track = aTrack({ artist: { id: undefined, name: "Not in library so no id", image: undefined }});
it("should provide no artistId", () => {
const result = asTrack(album, { ...asSongJson(track) });
expect(result.artist.id).toBeUndefined();
expect(result.artist.name).toEqual("Not in library so no id");
expect(result.artist.image).toBeUndefined();
});
});
describe("when the song has no artist name", () => {
const album = anAlbum();
it("should provide a ? to sonos", () => {
const result = asTrack(album, { id: '1' } as any as song);
expect(result.artist.id).toBeUndefined();
expect(result.artist.name).toEqual("?");
expect(result.artist.image).toBeUndefined();
});
});
describe("invalid rating.stars values", () => { describe("invalid rating.stars values", () => {
const album = anAlbum();
const track = aTrack();
describe("a value greater than 5", () => { describe("a value greater than 5", () => {
it("should be returned as 0", () => { it("should be returned as 0", () => {
const result = asTrack(album, { ...asSongJson(track), userRating: 6 }); const result = asTrack(album, { ...asSongJson(track), userRating: 6 });
@@ -861,7 +885,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: `${artist.id}`, id: `${artist.id}`,
@@ -923,7 +947,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -979,7 +1003,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1033,7 +1057,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1090,7 +1114,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1144,7 +1168,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1199,7 +1223,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1255,7 +1279,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1309,7 +1333,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,
@@ -1361,7 +1385,7 @@ describe("Subsonic", () => {
.generateToken({ username, password }) .generateToken({ username, password })
.then((it) => it as AuthSuccess) .then((it) => it as AuthSuccess)
.then((it) => navidrome.login(it.authToken)) .then((it) => navidrome.login(it.authToken))
.then((it) => it.artist(artist.id)); .then((it) => it.artist(artist.id!));
expect(result).toEqual({ expect(result).toEqual({
id: artist.id, id: artist.id,