import { EventEmitter , once } from 'events' ; import { clContext as nodenCLContext , OpenCLBuffer } from 'nodencl' ; import { RedioPipe , RedioEnd } from 'redioactive' ; import { Frame } from 'beamcoder' ; import { Producer } from './producer/producer' ; import { MixerDefaults } from './producer/mixer' ; import { Transitioner , TransitionSpec } from './transitioner' ; import { ConsumerConfig } from './config' ; import { ClJobs } from './clJobQueue' ; import { SourcePipes } from './routeSource' ; export const DefaultTransitionSpec = '{ "type": "cut", "len": 0 }' ; type SourceSpec = { source : Producer | undefined ; transition : TransitionSpec ; firstTs : number | undefined ; } ; export class Layer { private readonly clContext : nodenCLContext ; private readonly layerID : string ; private readonly consumerConfig : ConsumerConfig ; private readonly clJobs : ClJobs ; private readonly endEvent : EventEmitter ; private mixerParams = JSON . parse ( MixerDefaults ) ; private curSrcSpec : SourceSpec ; private nextSrcSpec : SourceSpec ; private transitioner : Transitioner | null ; private channelUpdate : ( ) => void ; private layerTick : ( ( t : string ) => void ) | undefined ; private autoPlay = false ; constructor ( clContext , layerID , consumerConfig , clJobs ) { this . clContext = clContext ; this . layerID = layerID ; this . consumerConfig = consumerConfig ; this . clJobs = clJobs ; this . endEvent = new EventEmitter ( ) ; this . curSrcSpec = { source : undefined , transition : JSON . parse ( DefaultTransitionSpec ) , firstTs : undefined } ; this . nextSrcSpec = { source : undefined , transition : JSON . parse ( DefaultTransitionSpec ) , firstTs : undefined } ; this . channelUpdate = ( ) => { } ; this . transitioner = new Transitioner ( this . clContext , this . layerID , this . consumerConfig . format , this . clJobs , this . endEvent , this . layerUpdate . bind ( this ) ) ; } async initialise ( ) { await this . transitioner ?. initialise ( ) ; } update ( ) { const audioPipes = [ ] ; const transitionSpec = this . curSrcSpec . transition ; if ( this . curSrcSpec . source ) { audioPipes . push ( this . curSrcSpec . source . getMixer ( ) . getAudioPipe ( ) ) ; if ( transitionSpec . source && transitionSpec . type !== 'cut' ) { audioPipes . push ( transitionSpec . source . getMixer ( ) . getAudioPipe ( ) ) ; } } const videoPipes = [ ] ; if ( this . curSrcSpec . source ) { videoPipes . push ( this . curSrcSpec . source . getMixer ( ) . getVideoPipe ( ) ) ; if ( transitionSpec . source && transitionSpec . type !== 'cut' ) { videoPipes . push ( transitionSpec . source . getMixer ( ) . getVideoPipe ( ) ) ; if ( transitionSpec . mask ) videoPipes . push ( transitionSpec . mask . getMixer ( ) . getVideoPipe ( ) ) ; } } this . transitioner ?. update ( transitionSpec . type , transitionSpec . len , audioPipes , videoPipes ) ; } layerUpdate ( ts ) { if ( this . layerTick && ts . length ) this . layerTick ( 'tick' ) ; if ( this . curSrcSpec . transition . type !== 'cut' && ts . length > 1 ) { if ( ! this . curSrcSpec . firstTs ) this . curSrcSpec . firstTs = ts [ 1 ] ; const numEnds = ts . reduce ( ( n , t ) => ( n += t < 0 ? 1 : 0 ) , 0 ) ; if ( ts [ 1 ] - this . curSrcSpec . firstTs === this . curSrcSpec . transition . len - 2 ) { this . curSrcSpec . transition . mask ?. release ( ) ; this . curSrcSpec . source ?. release ( ) ; this . curSrcSpec . source = undefined ; } else if ( ( this . curSrcSpec . transition . type === 'wipe' && numEnds > 1 ) || ( this . curSrcSpec . transition . type === 'dissolve' && numEnds > 0 ) ) { this . curSrcSpec . source = this . curSrcSpec . transition . source ; this . curSrcSpec . transition = JSON . parse ( DefaultTransitionSpec ) ; this . update ( ) ; this . endEvent . emit ( 'transitionComplete' ) ; } } else if ( this . curSrcSpec . source && this . curSrcSpec . transition . type === 'cut' && ts . length === 1 && ts [ 0 ] < 0 ) { this . curSrcSpec . transition . mask ?. release ( ) ; this . curSrcSpec . source ?. release ( ) ; this . curSrcSpec . source = undefined ; this . curSrcSpec . transition = JSON . parse ( DefaultTransitionSpec ) ; this . endEvent . emit ( 'end' ) ; if ( this . nextSrcSpec . source === undefined && this . layerTick ) this . layerTick ( 'end' ) ; } } async load ( producer , transitionSpec , preview , autoPlay , channelUpdate ) { this . nextSrcSpec = { source : producer , transition : transitionSpec , firstTs : undefined } ; this . autoPlay = autoPlay ; this . channelUpdate = channelUpdate ; if ( this . autoPlay ) { if ( this . curSrcSpec . source ) { this . endEvent . once ( 'end' , ( ) => { this . curSrcSpec . source = undefined ; this . play ( ) ; } ) ; } else { this . play ( ) ; } } else if ( preview ) { if ( this . curSrcSpec . source ) { this . curSrcSpec . source . release ( ) ; this . curSrcSpec . source = undefined ; await once ( this . endEvent , 'end' ) ; } this . curSrcSpec . source = this . nextSrcSpec . source ; this . nextSrcSpec . source = undefined ; await this . update ( ) ; this . channelUpdate ( ) ; } return true ; } async play ( ticker ? ) { if ( this . nextSrcSpec . source && this . nextSrcSpec . transition ?. type === 'cut' ) { if ( this . curSrcSpec . source ) { this . curSrcSpec . source . release ( ) ; await once ( this . endEvent , 'end' ) ; } this . curSrcSpec . source = this . nextSrcSpec . source ; } this . curSrcSpec . transition = this . nextSrcSpec . transition ; if ( this . curSrcSpec . transition . type !== 'cut' ) { this . curSrcSpec . transition . source = this . nextSrcSpec . source ; } this . nextSrcSpec . source = undefined ; this . nextSrcSpec . transition = JSON . parse ( DefaultTransitionSpec ) ; this . autoPlay = false ; this . layerTick = ticker ; this . curSrcSpec . source ?. setPaused ( false ) ; this . curSrcSpec . transition ?. source ?. setPaused ( false ) ; this . curSrcSpec . transition ?. mask ?. setPaused ( false ) ; await this . update ( ) ; this . channelUpdate ( ) ; if ( this . curSrcSpec . transition . type !== 'cut' ) await once ( this . endEvent , 'transitionComplete' ) ; } pause ( ) { this . curSrcSpec . source ?. setPaused ( true ) ; } resume ( ) { this . curSrcSpec . source ?. setPaused ( false ) ; } async stop ( ) { if ( this . curSrcSpec . source ) { this . curSrcSpec . source . release ( ) ; this . curSrcSpec . source = undefined ; await once ( this . endEvent , 'end' ) ; } this . autoPlay = false ; } anchor ( params ) { const mixer = this . curSrcSpec . source ? this . curSrcSpec . source . getMixer ( ) : this . nextSrcSpec . source ?. getMixer ( ) ; if ( params . length ) { this . mixerParams . anchor = { x : + params [ 0 ] , y : + params [ 1 ] } ; mixer ?. setMixParams ( this . mixerParams ) ; } else { console . dir ( this . mixerParams . anchor , { colors : true } ) ; } } rotation ( params ) { const mixer = this . curSrcSpec . source ? this . curSrcSpec . source . getMixer ( ) : this . nextSrcSpec . source ?. getMixer ( ) ; if ( params . length ) { this . mixerParams . rotation = + params [ 0 ] ; mixer ?. setMixParams ( this . mixerParams ) ; } else { console . dir ( this . mixerParams . rotation , { colors : true } ) ; } } fill ( params ) { const mixer = this . curSrcSpec . source ? this . curSrcSpec . source . getMixer ( ) : this . nextSrcSpec . source ?. getMixer ( ) ; if ( params . length ) { this . mixerParams . fill = { xOffset : + params [ 0 ] , yOffset : + params [ 1 ] , xScale : + params [ 2 ] , yScale : + params [ 3 ] } ; mixer ?. setMixParams ( this . mixerParams ) ; } else { console . dir ( this . mixerParams . fill , { colors : true } ) ; } } volume ( params ) { const mixer = this . curSrcSpec . source ? this . curSrcSpec . source . getMixer ( ) : this . nextSrcSpec . source ?. getMixer ( ) ; if ( params . length ) { this . mixerParams . volume = + params [ 0 ] ; mixer ?. setMixParams ( this . mixerParams ) ; } else { console . dir ( this . mixerParams . volume , { colors : true } ) ; } } async getSourcePipes ( ) { return this . curSrcSpec . source ?. getSourcePipes ( ) ; } getAudioPipe ( ) { return this . transitioner ?. getAudioPipe ( ) ; } getVideoPipe ( ) { return this . transitioner ?. getVideoPipe ( ) ; } getEndEvent ( ) { return this . endEvent ; } async release ( ) { this . curSrcSpec . source = undefined ; await this . transitioner ?. release ( ) ; this . transitioner = null ; } }