import { Edge , findPipeline , StreamType , TransformerType } from './TransformerGraph' ; import { pipeline , Readable } from 'node:stream' ; import { noop } from '../util/util' ; import prism from 'prism-media' ; import { AudioPlayer , SILENCE_FRAME } from './AudioPlayer' ; template T export interface CreateAudioResourceOptions < T > { /**
     * The type of the input stream. Defaults to `StreamType.Arbitrary`.
     */ inputType ? : StreamType ; /**
     * Optional metadata that can be attached to the resource (e.g. track title, random id).
     * This is useful for identification purposes when the resource is passed around in events.
     * See  AudioResource . metadata metadata ? : T ; /**
     * Whether or not inline volume should be enabled. If enabled, you will be able to change the volume
     * of the stream on-the-fly. However, this also increases the performance cost of playback. Defaults to `false`.
     */ inlineVolume ? : boolean ; /**
     * The number of silence frames to append to the end of the resource's audio stream, to prevent interpolation glitches.
     * Defaults to 5.
     */ silencePaddingFrames ? : number ; } template T export class AudioResource < T = unknown > { /**
     * An object-mode Readable stream that emits Opus packets. This is what is played by audio players.
     */ public readonly playStream : Readable ; /**
     * The pipeline used to convert the input stream into a playable format. For example, this may
     * contain an FFmpeg component for arbitrary inputs, and it may contain a VolumeTransformer component
     * for resources with inline volume transformation enabled.
     */ public readonly edges : readonly Edge [ ] ; /**
     * Optional metadata that can be used to identify the resource.
     */ public metadata : T ; /**
     * If the resource was created with inline volume transformation enabled, then this will be a
     * prism-media VolumeTransformer. You can use this to alter the volume of the stream.
     */ public readonly volume ? : prism . VolumeTransformer ; /**
     * If using an Opus encoder to create this audio resource, then this will be a prism-media opus.Encoder.
     * You can use this to control settings such as bitrate, FEC, PLP.
     */ public readonly encoder ? : prism . opus . Encoder ; /**
     * The audio player that the resource is subscribed to, if any.
     */ public audioPlayer ? : AudioPlayer ; /**
     * The playback duration of this audio resource, given in milliseconds.
     */ public playbackDuration = 0 ; /**
     * Whether or not the stream for this resource has started (data has become readable)
     */ public started = false ; /**
     * The number of silence frames to append to the end of the resource's audio stream, to prevent interpolation glitches.
     */ public readonly silencePaddingFrames : number ; /**
     * The number of remaining silence frames to play. If -1, the frames have not yet started playing.
     */ public silenceRemaining = - 1 ; public constructor ( edges , streams , metadata , silencePaddingFrames ) { this . edges = edges ; this . playStream = streams . length > 1 ? ( pipeline ( streams , noop ) as any as Readable ) : streams [ 0 ] ; this . metadata = metadata ; this . silencePaddingFrames = silencePaddingFrames ; for ( const stream of streams ) { if ( stream instanceof prism . VolumeTransformer ) { this . volume = stream ; } else if ( stream instanceof prism . opus . Encoder ) { this . encoder = stream ; } } this . playStream . once ( 'readable' , ( ) => ( this . started = true ) ) ; } /**
     * Whether this resource is readable. If the underlying resource is no longer readable, this will still return true
     * while there are silence padding frames left to play.
     */ public get readable ( ) { if ( this . silenceRemaining === 0 ) return false ; const real = this . playStream . readable ; if ( ! real ) { if ( this . silenceRemaining === - 1 ) this . silenceRemaining = this . silencePaddingFrames ; return this . silenceRemaining !== 0 ; } return real ; } /**
     * Whether this resource has ended or not.
     */ public get ended ( ) { return this . playStream . readableEnded || this . playStream . destroyed || this . silenceRemaining === 0 ; } remarks internal public read ( ) { if ( this . silenceRemaining === 0 ) { return null ; } else if ( this . silenceRemaining > 0 ) { this . silenceRemaining -- ; return SILENCE_FRAME ; } const packet = this . playStream . read ( ) ; if ( packet ) { this . playbackDuration += 20 ; } return packet ; } } param path export const VOLUME_CONSTRAINT = ( path ) => path . some ( ( edge ) => edge . type === TransformerType . InlineVolume ) ; export const NO_CONSTRAINT = ( ) => true ; param stream export function inferStreamType ( stream ) { if ( stream instanceof prism . opus . Encoder ) { return { streamType : StreamType . Opus , hasVolume : false } ; } else if ( stream instanceof prism . opus . Decoder ) { return { streamType : StreamType . Raw , hasVolume : false } ; } else if ( stream instanceof prism . VolumeTransformer ) { return { streamType : StreamType . Raw , hasVolume : true } ; } else if ( stream instanceof prism . opus . OggDemuxer ) { return { streamType : StreamType . Opus , hasVolume : false } ; } else if ( stream instanceof prism . opus . WebmDemuxer ) { return { streamType : StreamType . Opus , hasVolume : false } ; } return { streamType : StreamType . Arbitrary , hasVolume : false } ; } remarks param input param options template T export function createAudioResource < T > ( input , options ) ; remarks param input param options template T export function createAudioResource < T extends null | undefined > ( input , options ? ) ; remarks param input param options template T export function createAudioResource < T > ( input , options = { } ) { let inputType = options . inputType ; let needsInlineVolume = Boolean ( options . inlineVolume ) ; if ( typeof input === 'string' ) { inputType = StreamType . Arbitrary ; } else if ( typeof inputType === 'undefined' ) { const analysis = inferStreamType ( input ) ; inputType = analysis . streamType ; needsInlineVolume = needsInlineVolume && ! analysis . hasVolume ; } const transformerPipeline = findPipeline ( inputType , needsInlineVolume ? VOLUME_CONSTRAINT : NO_CONSTRAINT ) ; if ( transformerPipeline . length === 0 ) { if ( typeof input === 'string' ) throw new Error ( `Invalid pipeline constructed for string resource '${ input }'` ) ; return new AudioResource < T > ( [ ] , [ input ] , ( options . metadata ?? null ) as T , options . silencePaddingFrames ?? 5 ) ; } const streams = transformerPipeline . map ( ( edge ) => edge . transformer ( input ) ) ; if ( typeof input !== 'string' ) streams . unshift ( input ) ; return new AudioResource < T > ( transformerPipeline , streams , ( options . metadata ?? null ) as T , options . silencePaddingFrames ?? 5 ) ; }