import { ContentCache } from '../ContentCache' ; import { TimeInstant } from '../time' ; import { FileNode } from 'nf-grapher' ; import { SPNode , NodePlaybackDescription } from './SPNode' ; import { debug as Debug } from 'debug' ; import { XAudioBuffer } from '../XAudioBuffer' ; const DBG_STR = 'nf:filenode' ; const dbg = Debug ( DBG_STR ) ; export class SPFileNode extends SPNode { protected ab : XAudioBuffer = new XAudioBuffer ( { numberOfChannels : this . info . channelCount , length : 1 , sampleRate : this . info . sampleRate } ) ; getPlaybackDescription ( renderTime , descriptions ) { const { id , kind } = this . node ; descriptions . push ( { id , kind , time : renderTime , file : { maxDuration : TimeInstant . fromSeconds ( this . ab . duration ) } } ) ; } feed ( renderTime , buffers , sampleCount ) { const ab = this . ab ; if ( ab . length === 1 ) { return ; } const node = FileNode . from ( this . node ) ; const when = TimeInstant . fromNanos ( node . when ) ; const offset = TimeInstant . fromNanos ( node . offset ) ; const duration = TimeInstant . fromNanos ( node . duration ) ; const hz = this . info . sampleRate ; const nowSamples = renderTime . asSamples ( hz ) ; const whenSamples = when . asSamples ( hz ) ; const offsetSamples = offset . asSamples ( hz ) ; const durationSamples = duration . asSamples ( hz ) ; if ( whenSamples >= nowSamples + sampleCount ) { return ; } if ( whenSamples + durationSamples < nowSamples ) { return ; } let contentBufferStartIndex = 0 ; let outputBufferStartIndex = 0 ; if ( whenSamples > nowSamples ) { outputBufferStartIndex = Math . floor ( 0 + ( whenSamples - nowSamples ) ) ; } else { contentBufferStartIndex = Math . floor ( nowSamples - whenSamples + ( 0 + offsetSamples ) ) ; } if ( contentBufferStartIndex > ab . length ) { return ; } const output = new XAudioBuffer ( { numberOfChannels : ab . numberOfChannels , length : sampleCount , sampleRate : hz } ) ; for ( let i = 0 ; i < output . numberOfChannels ; i ++ ) { if ( outputBufferStartIndex > 0 ) { const offsetted = ab . getChannelData ( i ) . subarray ( offsetSamples , offsetSamples + ( sampleCount - outputBufferStartIndex ) ) ; output . copyToChannel ( offsetted , i , outputBufferStartIndex ) ; } else if ( contentBufferStartIndex + sampleCount > ab . length ) { ab . copyFromChannel ( output . getChannelData ( i ) . subarray ( 0 , ab . length - contentBufferStartIndex ) , i , contentBufferStartIndex ) ; } else { ab . copyFromChannel ( output . getChannelData ( i ) , i , contentBufferStartIndex ) ; } } buffers . push ( output ) ; } async timeChange ( renderTime , cache , quantumSize ) { await Promise . all ( [ super . timeChange ( renderTime , cache , quantumSize ) , this . load ( cache ) ] ) ; } private async load ( cache ) { const node = FileNode . from ( this . node ) ; const uri = node . file ; if ( uri . match ( /spotify:/ ) ) { throw new Error ( 'Spotify file URIs are not supported' ) ; } const content = await cache . get ( uri , this . dscore . graphId ( ) , ( ) => { dbg ( 'loading file %s' , uri ) ; return fetch ( uri ) . then ( res => { if ( ! res . ok ) throw new Error ( `Failed to load ${ uri }. Status: ${ res . status } ${ res . statusText }` ) ; return res . arrayBuffer ( ) ; } ) . then ( ab => { dbg ( 'loaded file %s' , uri ) ; return this . info . decode ( uri , ab ) ; } ) . then ( ab => XAudioBuffer . fromAudioBuffer ( ab ) ) ; } ) ; this . ab = content ; return content ; } }