{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export interface IAudioMergerNode {\n    id: string;\n    name: string;\n    analyser: AnalyserNode;\n    audioData: Uint8Array;\n    audioSource: MediaStreamAudioSourceNode | MediaElementAudioSourceNode;\n    gainNode: GainNode;\n}\n\nexport default class AudioMerger {\n    private readonly audioContext: AudioContext;\n\n    private readonly videoSyncDelayNode: DelayNode;\n\n    public audioDestination: MediaStreamAudioDestinationNode;\n\n    readonly #sources = new Map<string, IAudioMergerNode>();\n\n    constructor() {\n        this.audioContext = new AudioContext();\n        this.audioDestination = this.audioContext.createMediaStreamDestination();\n        // Delay node for video sync\n        this.videoSyncDelayNode = this.audioContext.createDelay(5.0);\n        this.videoSyncDelayNode.connect(this.audioDestination);\n        // HACK for wowza #7, #10\n        // Gain node prevents quality drop\n        const gain = this.audioContext.createGain();\n        const constantAudioNode = this.audioContext.createConstantSource();\n        gain.gain.value = 0;\n        constantAudioNode.start();\n        constantAudioNode.connect(gain);\n        gain.connect(this.videoSyncDelayNode);\n        // Stop browser from throttling timers by playing almost-silent audio\n        const source = this.audioContext.createConstantSource();\n        const gainNode = this.audioContext.createGain();\n        // Required to prevent popping on start\n        gainNode.gain.value = 0.001;\n        source.connect(gainNode);\n        gainNode.connect(this.audioContext.destination);\n        source.start();\n    }\n\n    getContext(): AudioContext {\n        return this.audioContext;\n    }\n\n    getOutputStream(): MediaStream {\n        return this.audioDestination.stream;\n    }\n\n    getSources(): Map<string, IAudioMergerNode>{\n        return this.#sources;\n    }\n\n    getSource(id: string): IAudioMergerNode | undefined {\n        return this.#sources.get(id);\n    }\n\n    addSource(name: string, source: MediaStream | HTMLMediaElement): IAudioMergerNode | null {\n        if (!(source instanceof MediaStream) && !(source instanceof HTMLMediaElement)) {\n            console.warn('AudioMixer: You can only add MediaStream & HTMLMediaElement sources!');\n            return null;\n        }\n        const foundedStreamNode = this.#sources.get(source.id);\n        if (foundedStreamNode) {\n            console.warn('AudioMixer: This source has been added before!', source.id);\n            return foundedStreamNode;\n        }\n\n        const audioSource =\n            source instanceof MediaStream\n                ? this.audioContext.createMediaStreamSource(source)\n                : this.audioContext.createMediaElementSource(source);\n        const audioOutput = this.audioContext.createGain();\n        const gainNode = audioSource.context.createGain();\n        const analyser = this.audioContext.createAnalyser();\n\n        audioOutput.gain.value = 1;\n        gainNode.gain.setValueAtTime(1, (audioSource.context as unknown as BaseAudioContext).currentTime);\n        audioSource.connect(gainNode);\n        gainNode.connect(audioOutput);\n        audioOutput.connect(this.videoSyncDelayNode);\n        analyser.fftSize = 256;\n        audioSource.connect(analyser);\n\n        const node = {\n            id: source.id,\n            name,\n            audioSource,\n            analyser,\n            audioData: new Uint8Array(analyser.frequencyBinCount),\n            gainNode,\n        } as IAudioMergerNode;\n\n        this.#sources.set(source.id, node);\n\n        return node;\n    }\n\n    removeSource(id: string) {\n        const {audioSource, analyser} = this.#sources.get(id) || {};\n        if (audioSource) {\n            if (audioSource instanceof MediaStream) {\n                audioSource.getTracks().forEach(track => {\n                    track.stop();\n                });\n            }\n            if (audioSource instanceof MediaElementAudioSourceNode) {\n                audioSource.mediaElement.pause();\n                audioSource.mediaElement.remove();\n            }\n            if (analyser) audioSource.disconnect(analyser);\n            this.#sources.delete(id);\n        }\n    }\n\n    updateVolume(id: string, volume: number) {\n        const context = this.getContext();\n        const mediaSource = this.getSource(id);\n        if (context && mediaSource) {\n            mediaSource.gainNode.gain.setValueAtTime(volume / 100, context.currentTime);\n        }\n    }\n\n    getVolumeInDecibels(id: string) {\n        const node = this.#sources.get(id);\n        if (node) {\n            node.analyser.getByteFrequencyData(node.audioData);\n            // Calculate the average amplitude of the audio signal\n            let sum = 0;\n            node.audioData.forEach(value => {\n                sum += value;\n            });\n            const averageAmplitude = sum / node.analyser.frequencyBinCount;\n            // Convert the average amplitude to dB\n            const val = Math.floor(20 * Math.log10(averageAmplitude / 255));\n            return val === -Infinity ? 0 : val;\n        }\n        return null;\n    }\n\n    destroy() {\n        // Remove all added sources\n        Array.from(this.#sources).map(([, source]) => this.removeSource(source.id));\n        // Destroy context and nodes\n        this.audioDestination.stream.getTracks().forEach((track: MediaStreamTrack) => track.stop());\n        this.audioDestination.disconnect();\n        this.videoSyncDelayNode.disconnect();\n        void this.audioContext.close();\n    }\n}\n"],"mappings":"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","names":["_sources","AudioMerger","__privateAdd","gain","constantAudioNode","source","gainNode","__privateGet","id","name","foundedStreamNode","audioSource","audioOutput","analyser","node","track","volume","context","mediaSource","sum","value","averageAmplitude","val"]}