Fix caching (#941)

This commit is contained in:
Max Isom 2023-05-13 18:34:29 -07:00 committed by GitHub
parent f54d7caa72
commit dd140b50fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 40 deletions

View file

@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Muse now normalizes playback volume across tracks. Thanks to @UniversalSuperBox for sponsoring this feature! - Muse now normalizes playback volume across tracks. Thanks to @UniversalSuperBox for sponsoring this feature!
### Fixed
- Fixed a bug where tracks wouldn't be cached
## [2.2.4] - 2023-04-17 ## [2.2.4] - 2023-04-17
### Fixed ### Fixed
- Bumped ytdl-core - Bumped ytdl-core

View file

@ -18,11 +18,11 @@ export default class FileCacheProvider {
} }
/** /**
* Returns path to cached file if it exists, otherwise throws an error. * Returns path to cached file if it exists, otherwise returns null.
* Updates the `accessedAt` property of the cached file. * Updates the `accessedAt` property of the cached file.
* @param hash lookup key * @param hash lookup key
*/ */
async getPathFor(hash: string): Promise<string> { async getPathFor(hash: string): Promise<string | null> {
const model = await prisma.fileCache.findUnique({ const model = await prisma.fileCache.findUnique({
where: { where: {
hash, hash,
@ -30,7 +30,7 @@ export default class FileCacheProvider {
}); });
if (!model) { if (!model) {
throw new Error('File is not cached'); return null;
} }
const resolvedPath = path.join(this.config.CACHE_DIR, hash); const resolvedPath = path.join(this.config.CACHE_DIR, hash);
@ -44,7 +44,7 @@ export default class FileCacheProvider {
}, },
}); });
throw new Error('File is not cached'); return null;
} }
await prisma.fileCache.update({ await prisma.fileCache.update({
@ -76,19 +76,15 @@ export default class FileCacheProvider {
const stats = await fs.stat(tmpPath); const stats = await fs.stat(tmpPath);
if (stats.size !== 0) { if (stats.size !== 0) {
try { await fs.rename(tmpPath, finalPath);
await fs.rename(tmpPath, finalPath);
await prisma.fileCache.create({ await prisma.fileCache.create({
data: { data: {
hash, hash,
accessedAt: new Date(), accessedAt: new Date(),
bytes: stats.size, bytes: stats.size,
}, },
}); });
} catch (error) {
debug('Errored when moving a finished cache file:', error);
}
} }
await this.evictOldestIfNecessary(); await this.evictOldestIfNecessary();

View file

@ -410,26 +410,18 @@ export default class {
private async getStream(song: QueuedSong, options: {seek?: number; to?: number} = {}): Promise<Readable> { private async getStream(song: QueuedSong, options: {seek?: number; to?: number} = {}): Promise<Readable> {
if (song.source === MediaSource.HLS) { if (song.source === MediaSource.HLS) {
return this.createReadStream(song.url); return this.createReadStream({url: song.url, cacheKey: song.url});
} }
let ffmpegInput = ''; let ffmpegInput: string | null;
const ffmpegInputOptions: string[] = []; const ffmpegInputOptions: string[] = [];
let shouldCacheVideo = false; let shouldCacheVideo = false;
let format: YTDLVideoFormat | undefined; let format: YTDLVideoFormat | undefined;
try { ffmpegInput = await this.fileCache.getPathFor(this.getHashForCache(song.url));
ffmpegInput = await this.fileCache.getPathFor(this.getHashForCache(song.url));
if (options.seek) { if (!ffmpegInput) {
ffmpegInputOptions.push('-ss', options.seek.toString());
}
if (options.to) {
ffmpegInputOptions.push('-to', options.to.toString());
}
} catch {
// Not yet cached, must download // Not yet cached, must download
const info = await ytdl.getInfo(song.url); const info = await ytdl.getInfo(song.url);
@ -485,17 +477,19 @@ export default class {
'-reconnect_delay_max', '-reconnect_delay_max',
'5', '5',
]); ]);
if (options.seek) {
ffmpegInputOptions.push('-ss', options.seek.toString());
}
if (options.to) {
ffmpegInputOptions.push('-to', options.to.toString());
}
} }
return this.createReadStream(ffmpegInput, { if (options.seek) {
ffmpegInputOptions.push('-ss', options.seek.toString());
}
if (options.to) {
ffmpegInputOptions.push('-to', options.to.toString());
}
return this.createReadStream({
url: ffmpegInput,
cacheKey: song.url,
ffmpegInputOptions, ffmpegInputOptions,
cache: shouldCacheVideo, cache: shouldCacheVideo,
volumeAdjustment: format?.loudnessDb ? `${-format.loudnessDb}dB` : undefined, volumeAdjustment: format?.loudnessDb ? `${-format.loudnessDb}dB` : undefined,
@ -556,19 +550,19 @@ export default class {
} }
} }
private async createReadStream(url: string, options: {ffmpegInputOptions?: string[]; cache?: boolean; volumeAdjustment?: string} = {}): Promise<Readable> { private async createReadStream(options: {url: string; cacheKey: string; ffmpegInputOptions?: string[]; cache?: boolean; volumeAdjustment?: string}): Promise<Readable> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const capacitor = new WriteStream(); const capacitor = new WriteStream();
if (options?.cache) { if (options?.cache) {
const cacheStream = this.fileCache.createWriteStream(this.getHashForCache(url)); const cacheStream = this.fileCache.createWriteStream(this.getHashForCache(options.cacheKey));
capacitor.createReadStream().pipe(cacheStream); capacitor.createReadStream().pipe(cacheStream);
} }
const returnedStream = capacitor.createReadStream(); const returnedStream = capacitor.createReadStream();
let hasReturnedStreamClosed = false; let hasReturnedStreamClosed = false;
const stream = ffmpeg(url) const stream = ffmpeg(options.url)
.inputOptions(options?.ffmpegInputOptions ?? ['-re']) .inputOptions(options?.ffmpegInputOptions ?? ['-re'])
.noVideo() .noVideo()
.audioCodec('libopus') .audioCodec('libopus')