mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Feature/move close stream (#176)
* Move stream destroy closer to where stream is retrieved * Change BNB_SUBSONIC_URL to be of type URLBuilder to better handle URL construction rather than string concat, should addresse #169
This commit is contained in:
@@ -58,8 +58,6 @@ const asBoolean = (value: string) => value == "true";
|
||||
|
||||
const asInt = (value: string) => Number.parseInt(value);
|
||||
|
||||
const asStringNoTrailingSlash = (value: string) => value.replace(/\/$/, "");
|
||||
|
||||
export default function () {
|
||||
const port = bnbEnvVar<number>("PORT", { default: 4534, parser: asInt })!;
|
||||
const bonobUrl = bnbEnvVar("URL", {
|
||||
@@ -100,8 +98,7 @@ export default function () {
|
||||
sid: bnbEnvVar<number>("SONOS_SERVICE_ID", { default: 246, parser: asInt }),
|
||||
},
|
||||
subsonic: {
|
||||
// todo: why isnt this a url like bonobUrl above?
|
||||
url: bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533`, parser: asStringNoTrailingSlash })!,
|
||||
url: url(bnbEnvVar("SUBSONIC_URL", { legacy: ["BONOB_NAVIDROME_URL"], default: `http://${hostname()}:4533` })!),
|
||||
customClientsFor: bnbEnvVar<string>("SUBSONIC_CUSTOM_CLIENTS", { legacy: ["BONOB_NAVIDROME_CUSTOM_CLIENTS"] }),
|
||||
artistImageCache: bnbEnvVar<string>("SUBSONIC_ARTIST_IMAGE_CACHE"),
|
||||
},
|
||||
|
||||
@@ -307,13 +307,13 @@ function server(
|
||||
return `<Match propname="rating" value="${value}">
|
||||
<Ratings>
|
||||
<Rating Id="${ratingAsInt(
|
||||
nextLove
|
||||
)}" AutoSkip="NEVER" OnSuccessStringId="LOVE_SUCCESS" StringId="LOVE">
|
||||
nextLove
|
||||
)}" AutoSkip="NEVER" OnSuccessStringId="LOVE_SUCCESS" StringId="LOVE">
|
||||
<Icon Controller="universal" LastModified="${LastModified}" Uri="${loveRatingIcon}" />
|
||||
</Rating>
|
||||
<Rating Id="${-ratingAsInt(
|
||||
nextStar
|
||||
)}" AutoSkip="NEVER" OnSuccessStringId="STAR_SUCCESS" StringId="STAR">
|
||||
nextStar
|
||||
)}" AutoSkip="NEVER" OnSuccessStringId="STAR_SUCCESS" StringId="STAR">
|
||||
<Icon Controller="universal" LastModified="${LastModified}" Uri="${starsRatingIcon}" />
|
||||
</Rating>
|
||||
</Ratings>
|
||||
@@ -327,9 +327,9 @@ function server(
|
||||
<Match>
|
||||
<imageSizeMap>
|
||||
${SONOS_RECOMMENDED_IMAGE_SIZES.map(
|
||||
(size) =>
|
||||
`<sizeEntry size="${size}" substitution="/size/${size}"/>`
|
||||
).join("")}
|
||||
(size) =>
|
||||
`<sizeEntry size="${size}" substitution="/size/${size}"/>`
|
||||
).join("")}
|
||||
</imageSizeMap>
|
||||
</Match>
|
||||
</PresentationMap>
|
||||
@@ -338,9 +338,9 @@ function server(
|
||||
<browseIconSizeMap>
|
||||
<sizeEntry size="0" substitution="/size/legacy"/>
|
||||
${SONOS_RECOMMENDED_IMAGE_SIZES.map(
|
||||
(size) =>
|
||||
`<sizeEntry size="${size}" substitution="/size/${size}"/>`
|
||||
).join("")}
|
||||
(size) =>
|
||||
`<sizeEntry size="${size}" substitution="/size/${size}"/>`
|
||||
).join("")}
|
||||
</browseIconSizeMap>
|
||||
</Match>
|
||||
</PresentationMap>
|
||||
@@ -406,13 +406,17 @@ function server(
|
||||
trackId: id,
|
||||
range: req.headers["range"] || undefined,
|
||||
})
|
||||
.then((stream) => {
|
||||
res.on('close', () => {
|
||||
stream.stream.destroy()
|
||||
});
|
||||
return stream;
|
||||
})
|
||||
.then((stream) => ({ musicLibrary: it, stream }))
|
||||
)
|
||||
.then(({ musicLibrary, stream }) => {
|
||||
logger.debug(
|
||||
`${trace} bnb<- stream response from music service for ${id}, status=${
|
||||
stream.status
|
||||
}, headers=(${JSON.stringify(stream.headers)})`
|
||||
`${trace} bnb<- stream response from music service for ${id}, status=${stream.status}, headers=(${JSON.stringify(stream.headers)})`
|
||||
);
|
||||
|
||||
const sonosisfyContentType = (contentType: string) =>
|
||||
@@ -436,9 +440,7 @@ function server(
|
||||
nowPlaying: boolean;
|
||||
}) => {
|
||||
logger.debug(
|
||||
`${trace} bnb-> ${
|
||||
req.path
|
||||
}, status=${status}, headers=${JSON.stringify(headers)}`
|
||||
`${trace} bnb-> ${req.path}, status=${status}, headers=${JSON.stringify(headers)}`
|
||||
);
|
||||
(nowPlaying
|
||||
? musicLibrary.nowPlaying(id)
|
||||
@@ -450,9 +452,6 @@ function server(
|
||||
.forEach(([header, value]) => {
|
||||
res.setHeader(header, value!);
|
||||
});
|
||||
res.on('close', () => {
|
||||
stream.stream.destroy()
|
||||
});
|
||||
if (sendStream) stream.stream.pipe(filter).pipe(res)
|
||||
else res.send()
|
||||
});
|
||||
@@ -516,15 +515,15 @@ function server(
|
||||
const spec =
|
||||
size == "legacy"
|
||||
? {
|
||||
mimeType: "image/png",
|
||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||
sharp(Buffer.from(svg)).resize(80).png().toBuffer(),
|
||||
}
|
||||
mimeType: "image/png",
|
||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||
sharp(Buffer.from(svg)).resize(80).png().toBuffer(),
|
||||
}
|
||||
: {
|
||||
mimeType: "image/svg+xml",
|
||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||
Promise.resolve(svg),
|
||||
};
|
||||
mimeType: "image/svg+xml",
|
||||
responseFormatter: (svg: string): Promise<Buffer | string> =>
|
||||
Promise.resolve(svg),
|
||||
};
|
||||
|
||||
return Promise.resolve(
|
||||
icon
|
||||
|
||||
@@ -32,6 +32,7 @@ import { b64Encode, b64Decode } from "./b64";
|
||||
import logger from "./logger";
|
||||
import { assertSystem, BUrn } from "./burn";
|
||||
import { artist } from "./smapi";
|
||||
import { URLBuilder } from "./url_builder";
|
||||
|
||||
export const BROWSER_HEADERS = {
|
||||
accept:
|
||||
@@ -344,28 +345,28 @@ export type ImageFetcher = (url: string) => Promise<CoverArt | undefined>;
|
||||
|
||||
export const cachingImageFetcher =
|
||||
(cacheDir: string, delegate: ImageFetcher) =>
|
||||
async (url: string): Promise<CoverArt | undefined> => {
|
||||
const filename = path.join(cacheDir, `${Md5.hashStr(url)}.png`);
|
||||
return fse
|
||||
.readFile(filename)
|
||||
.then((data) => ({ contentType: "image/png", data }))
|
||||
.catch(() =>
|
||||
delegate(url).then((image) => {
|
||||
if (image) {
|
||||
return sharp(image.data)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((png) => {
|
||||
return fse
|
||||
.writeFile(filename, png)
|
||||
.then(() => ({ contentType: "image/png", data: png }));
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
async (url: string): Promise<CoverArt | undefined> => {
|
||||
const filename = path.join(cacheDir, `${Md5.hashStr(url)}.png`);
|
||||
return fse
|
||||
.readFile(filename)
|
||||
.then((data) => ({ contentType: "image/png", data }))
|
||||
.catch(() =>
|
||||
delegate(url).then((image) => {
|
||||
if (image) {
|
||||
return sharp(image.data)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((png) => {
|
||||
return fse
|
||||
.writeFile(filename, png)
|
||||
.then(() => ({ contentType: "image/png", data: png }));
|
||||
});
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const axiosImageFetcher = (url: string): Promise<CoverArt | undefined> =>
|
||||
axios
|
||||
@@ -412,12 +413,12 @@ interface SubsonicMusicLibrary extends MusicLibrary {
|
||||
}
|
||||
|
||||
export class Subsonic implements MusicService {
|
||||
url: string;
|
||||
url: URLBuilder;
|
||||
streamClientApplication: StreamClientApplication;
|
||||
externalImageFetcher: ImageFetcher;
|
||||
|
||||
constructor(
|
||||
url: string,
|
||||
url: URLBuilder,
|
||||
streamClientApplication: StreamClientApplication = DEFAULT,
|
||||
externalImageFetcher: ImageFetcher = axiosImageFetcher
|
||||
) {
|
||||
@@ -433,7 +434,7 @@ export class Subsonic implements MusicService {
|
||||
config: AxiosRequestConfig | undefined = {}
|
||||
) =>
|
||||
axios
|
||||
.get(`${this.url}${path}`, {
|
||||
.get(this.url.append({ pathname: path }).href(), {
|
||||
params: asURLSearchParams({
|
||||
u: username,
|
||||
v: "1.16.1",
|
||||
|
||||
Reference in New Issue
Block a user