Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { info } from "../Common/Utils";
import type { BaseAudioDecoder } from "./BaseAudioDecoder";
import { WebAudioDecoder } from "./WebAudioDecoder";
import { AudioDecoder } from "./AudioDecoder";
 
export type DecoderCache = Map<string, BaseAudioDecoder>;
export type DecoderProxy = ReturnType<typeof decoderProxy>;
 
const REMOVAL_GRACE_PERIOD = 5000; // 5s grace period for removal of the decoder from the cache
 
function decoderProxy(
  cache: DecoderCache,
  src: string,
  splitChannels: boolean,
  decoderType: "webaudio" | "ffmpeg" = "ffmpeg",
) {
  const key = `${src}:${splitChannels}:${decoderType}`;
  const decoder = cache.get(key) ?? (decoderType === "ffmpeg" ? new AudioDecoder(src) : new WebAudioDecoder(src));
 
  decoder.renew();
  cache.set(key, decoder);
 
  return new Proxy(decoder, {
    get(target, prop) {
      if (prop in target) {
        // Operate on the instance, and cache it
        const instance = cache.get(key) as BaseAudioDecoder;
 
        // Cancel the removal of the decoder from the cache
        // It is still in use
        if (instance?.removalId) {
          clearTimeout(instance.removalId);
          info("decode:renew", key);
          instance.removalId = null;
          instance.renew();
          cache.set(key, instance);
        }
 
        const val = instance[prop as keyof BaseAudioDecoder];
 
        // When the instance is no longer in use, remove it from the cache
        // Allow for a grace period before removal so that the decoded results can be reused
        if (prop === "destroy" && typeof val === "function") {
          return (...args: any[]) => {
            instance.removalId = setTimeout(() => {
              info("decodepool:destroy", key);
              cache.delete(key);
            }, REMOVAL_GRACE_PERIOD);
            cache.set(key, instance);
            return (val.bind(instance) as any)(...args);
          };
        }
 
        return val;
      }
      return undefined;
    },
  });
}
 
export class AudioDecoderPool {
  static cache: DecoderCache = new Map();
 
  getDecoder(src: string, splitChannels: boolean, decoderType: "webaudio" | "ffmpeg" = "ffmpeg"): DecoderProxy {
    const decoder = decoderProxy(AudioDecoderPool.cache, src, splitChannels, decoderType);
 
    return decoder;
  }
}
 
export const audioDecoderPool = new AudioDecoderPool();