import { CmdList , CmdSet } from './commands' ; import { ChanLayer , LoadParams } from '../chanLayer' ; import { Channel } from '../channel' ; import { ConsumerRegistry } from '../consumer/consumer' ; import { ClJobs } from '../clJobQueue' ; import { ConfigParams } from '../config' ; export class BasicCmds implements CmdList { private readonly consumerRegistry : ConsumerRegistry ; private readonly channels : Array < Channel > ; private readonly clJobs : ClJobs ; constructor ( consumerRegistry , channels , clJobs ) { this . consumerRegistry = consumerRegistry ; this . channels = channels ; this . clJobs = clJobs ; } list ( ) { return { group : '' , entries : [ { cmd : 'LOADBG' , fn : this . loadbg . bind ( this ) } , { cmd : 'LOAD' , fn : this . load . bind ( this ) } , { cmd : 'PLAY' , fn : this . play . bind ( this ) } , { cmd : 'PAUSE' , fn : this . pause . bind ( this ) } , { cmd : 'RESUME' , fn : this . resume . bind ( this ) } , { cmd : 'STOP' , fn : this . stop . bind ( this ) } , { cmd : 'CLEAR' , fn : this . clear . bind ( this ) } , { cmd : 'ADD' , fn : this . add . bind ( this ) } , { cmd : 'REMOVE' , fn : this . remove . bind ( this ) } ] } ; } parseParams ( params ) { const paramsObj = { } ; const paramStr = params . join ( ' ' ) ; const re = /(?<name>[^-\s]+)(\s+(?<value>[^\s]+))?/g ; let matches = null ; while ( ( matches = re . exec ( paramStr ) ) && matches . groups ) { if ( matches . groups . value ) { const value = parseInt ( matches . groups . value ) ; paramsObj [ matches . groups . name . toLowerCase ( ) ] = isNaN ( value ) ? matches . groups . value . toLowerCase ( ) : value ; } } return paramsObj ; } async doLoad ( chanLay , params , preview ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; const url = params [ 0 ] ; const chanNum = url . toUpperCase ( ) === 'DECKLINK' ? + params [ 1 ] : 0 ; const loop = params . find ( ( param ) => param . toUpperCase ( ) === 'LOOP' ) !== undefined ; const autoPlay = params . find ( ( param ) => param . toUpperCase ( ) === 'AUTO' ) !== undefined ; const seekIndex = params . findIndex ( ( param ) => param . toUpperCase ( ) === 'SEEK' ) ; const seek = seekIndex < 0 ? 0 : + params [ seekIndex + 1 ] ; const lengthIndex = params . findIndex ( ( param ) => param . toUpperCase ( ) === 'LENGTH' ) ; const length = lengthIndex < 0 ? 0 : + params [ lengthIndex + 1 ] ; const loadParams = { url : url , layer : chanLay . layer , channel : chanNum , loop : loop , preview : preview , autoPlay : autoPlay , seek : seek , length : length } ; return channel . loadSource ( loadParams ) ; } /**
     * Loads a producer in the background and prepares it for playout. If no layer is specified the default layer index will be used.
     *
     * _clip_ will be parsed by available registered producer factories. If a successfully match is found, the producer will be loaded into the background.
     * If a file with the same name (extension excluded) but with the additional postfix _a is found this file will be used as key for the main clip.
     *
     * _loop_ will cause the clip to loop.
     * When playing and looping the clip will start at _frame_.
     * When playing and loop the clip will end after _frames_ number of frames.
     *
     * _auto_ will cause the clip to automatically start when foreground clip has ended (without play).
     * The clip is considered "started" after the optional transition has ended.
     *
     * Note: only one clip can be queued to play automatically per layer.
     */ async loadbg ( chanLay , params ) { return this . doLoad ( chanLay , params , false ) ; } /**
     * Loads a clip to the foreground and plays the first frame before pausing.
     * If any clip is playing on the target foreground then this clip will be replaced.
     */ async load ( chanLay , params ) { return this . doLoad ( chanLay , params , true ) ; } /**
     * Moves clip from background to foreground and starts playing it.
     * If a transition (see LOADBG) is prepared, it will be executed.
     * If additional parameters (see LOADBG) are provided then the provided clip will first be loaded to the background.
     */ async play ( chanLay , params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; if ( params . length !== 0 ) await this . loadbg ( chanLay , params ) ; return channel . play ( chanLay . layer ) ; } /**
     * Pauses playback of the foreground clip on the specified layer.
     * The RESUME command can be used to resume playback again.
     */ async pause ( chanLay , _params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; return channel . pause ( chanLay . layer ) ; } /** Resumes playback of a foreground clip previously paused with the PAUSE command. */ async resume ( chanLay , _params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; return channel . resume ( chanLay . layer ) ; } /** Removes the foreground clip of the specified layer */ async stop ( chanLay , _params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; return channel . stop ( chanLay . layer ) ; } /**
     * Removes all clips (both foreground and background) of the specified layer.
     * If no layer is specified then all layers in the specified video_channel are cleared.
     */ async clear ( chanLay , _params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; return channel . clear ( chanLay . layer ) ; } async add ( chanLay , params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; if ( params . length === 0 ) return Promise . resolve ( false ) ; let consumerName = params [ 0 ] . toLowerCase ( ) ; if ( consumerName === 'file' || consumerName === 'stream' ) consumerName = 'ffmpeg' ; const consumerIndex = chanLay . layer ? chanLay . layer : 0 ; const deviceIndex = + params [ 1 ] || 0 ; try { const consumer = this . consumerRegistry . createConsumer ( chanLay . channel , consumerIndex , this . parseParams ( params ) , { name : consumerName , deviceIndex : deviceIndex } , this . clJobs ) ; await consumer . initialise ( ) ; channel . addConsumer ( consumer ) ; } catch ( err ) { console . log ( `Error adding consumer to configured channel ${ chanLay . channel }: ${ err instanceof Error ? err . message : 'Unknown error' }` ) ; return Promise . resolve ( false ) ; } return Promise . resolve ( true ) ; } async remove ( chanLay , params ) { if ( ! chanLay . valid ) return Promise . resolve ( false ) ; const channel = this . channels [ chanLay . channel - 1 ] ; if ( ! channel ) return Promise . resolve ( false ) ; let consumerName = '' ; let deviceIndex = 0 ; if ( params . length > 0 ) { consumerName = params [ 0 ] . toLowerCase ( ) ; if ( consumerName === 'file' || consumerName === 'stream' ) consumerName = 'ffmpeg' ; deviceIndex = + params [ 1 ] || 0 ; } const consumerIndex = chanLay . layer ? chanLay . layer : 0 ; try { this . consumerRegistry . removeConsumer ( chanLay . channel , channel , consumerIndex , { name : consumerName , deviceIndex : deviceIndex } ) ; } catch ( err ) { console . log ( `Error removing consumer from configured channel ${ chanLay . channel }: ${ err instanceof Error ? err . message : 'Unknown error' }` ) ; return Promise . resolve ( false ) ; } return Promise . resolve ( true ) ; } }