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:
Simon J
2023-10-10 11:25:55 +11:00
committed by GitHub
parent b8caf90e06
commit a5689c3d4b
5 changed files with 176 additions and 178 deletions

View File

@@ -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"),
},

View File

@@ -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

View File

@@ -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",