From dd140b50fb79fd66c65aa8c22e0ebbb6109075f0 Mon Sep 17 00:00:00 2001 From: Max Isom Date: Sat, 13 May 2023 18:34:29 -0700 Subject: [PATCH] Fix caching (#941) --- CHANGELOG.md | 3 +++ src/services/file-cache.ts | 28 +++++++++++-------------- src/services/player.ts | 42 ++++++++++++++++---------------------- 3 files changed, 33 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0151d69..b548aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - 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 ### Fixed - Bumped ytdl-core diff --git a/src/services/file-cache.ts b/src/services/file-cache.ts index 98a079c..3906772 100644 --- a/src/services/file-cache.ts +++ b/src/services/file-cache.ts @@ -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. * @param hash lookup key */ - async getPathFor(hash: string): Promise { + async getPathFor(hash: string): Promise { const model = await prisma.fileCache.findUnique({ where: { hash, @@ -30,7 +30,7 @@ export default class FileCacheProvider { }); if (!model) { - throw new Error('File is not cached'); + return null; } 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({ @@ -76,19 +76,15 @@ export default class FileCacheProvider { const stats = await fs.stat(tmpPath); if (stats.size !== 0) { - try { - await fs.rename(tmpPath, finalPath); + await fs.rename(tmpPath, finalPath); - await prisma.fileCache.create({ - data: { - hash, - accessedAt: new Date(), - bytes: stats.size, - }, - }); - } catch (error) { - debug('Errored when moving a finished cache file:', error); - } + await prisma.fileCache.create({ + data: { + hash, + accessedAt: new Date(), + bytes: stats.size, + }, + }); } await this.evictOldestIfNecessary(); diff --git a/src/services/player.ts b/src/services/player.ts index 239953d..c888896 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -410,26 +410,18 @@ export default class { private async getStream(song: QueuedSong, options: {seek?: number; to?: number} = {}): Promise { 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[] = []; let shouldCacheVideo = false; 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) { - ffmpegInputOptions.push('-ss', options.seek.toString()); - } - - if (options.to) { - ffmpegInputOptions.push('-to', options.to.toString()); - } - } catch { + if (!ffmpegInput) { // Not yet cached, must download const info = await ytdl.getInfo(song.url); @@ -485,17 +477,19 @@ export default class { '-reconnect_delay_max', '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, cache: shouldCacheVideo, 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 { + private async createReadStream(options: {url: string; cacheKey: string; ffmpegInputOptions?: string[]; cache?: boolean; volumeAdjustment?: string}): Promise { return new Promise((resolve, reject) => { const capacitor = new WriteStream(); if (options?.cache) { - const cacheStream = this.fileCache.createWriteStream(this.getHashForCache(url)); + const cacheStream = this.fileCache.createWriteStream(this.getHashForCache(options.cacheKey)); capacitor.createReadStream().pipe(cacheStream); } const returnedStream = capacitor.createReadStream(); let hasReturnedStreamClosed = false; - const stream = ffmpeg(url) + const stream = ffmpeg(options.url) .inputOptions(options?.ffmpegInputOptions ?? ['-re']) .noVideo() .audioCodec('libopus')