diff --git a/src/voice/Playback.ts b/src/voice/Playback.ts index cb198e4ff7..33d346629a 100644 --- a/src/voice/Playback.ts +++ b/src/voice/Playback.ts @@ -59,9 +59,10 @@ export class Playback extends EventEmitter implements IDestroyable { public readonly thumbnailWaveform: number[]; private readonly context: AudioContext; - private source: AudioBufferSourceNode; + private source: AudioBufferSourceNode | MediaElementAudioSourceNode; private state = PlaybackState.Decoding; private audioBuf: AudioBuffer; + private element: HTMLAudioElement; private resampledWaveform: number[]; private waveformObservable = new SimpleObservable(); private readonly clock: PlaybackClock; @@ -129,41 +130,64 @@ export class Playback extends EventEmitter implements IDestroyable { this.removeAllListeners(); this.clock.destroy(); this.waveformObservable.close(); + if (this.element) { + URL.revokeObjectURL(this.element.src); + this.element.remove(); + } } public async prepare() { - // Safari compat: promise API not supported on this function - this.audioBuf = await new Promise((resolve, reject) => { - this.context.decodeAudioData(this.buf, b => resolve(b), async e => { - try { - // This error handler is largely for Safari as well, which doesn't support Opus/Ogg - // very well. - console.error("Error decoding recording: ", e); - console.warn("Trying to re-encode to WAV instead..."); - - const wav = await decodeOgg(this.buf); - - // noinspection ES6MissingAwait - not needed when using callbacks - this.context.decodeAudioData(wav, b => resolve(b), e => { - console.error("Still failed to decode recording: ", e); - reject(e); - }); - } catch (e) { - console.error("Caught decoding error:", e); - reject(e); - } + // The point where we use an audio element is fairly arbitrary, though we don't want + // it to be too low. As of writing, voice messages want to show a waveform but audio + // messages do not. Using an audio element means we can't show a waveform preview, so + // we try to target the difference between a voice message file and large audio file. + // Overall, the point of this is to avoid memory-related issues due to storing a massive + // audio buffer in memory, as that can balloon to far greater than the input buffer's + // byte length. + if (this.buf.byteLength > 5 * 1024 * 1024) { // 5mb + console.log("Audio file too large: processing through