import { Filter } from "./Filter" ; import { OBS } from "./OBS" ; import { Scene } from "./Scene" ; import { SceneItem , SceneItemTransform } from "./SceneItem" ; import { Settings } from "./types" ; export type SourceFilters = Record < string , Filter > ; export type SourceRefs = Record < string , Record < string , number > > ; export interface SourceArgs < Filters extends SourceFilters > { name : string ; kind : string ; filters ? : Filters ; } export abstract class Source < Filters extends SourceFilters = { } > { name : string ; kind : string ; internal filters : Filter [ ] = [ ] ; /**
     * Whether this source has at least one scene item in OBS
     */ get exists ( ) { return this . _exists ; } protected _exists = false ; /**
     * Whether `initialize` has been called on this source yet
     */ get initalized ( ) { return this . _initialized ; } protected _initialized = false ; internal private filtersMap : Filters & Record < string , Filter > = { } as Filters ; internal obs ! : OBS ; internal linked = false ; internal itemInstances = new Set < SceneItem > ( ) ; internal _settingsType ! : Settings ; protected refs : SourceRefs = { } ; constructor ( args ) { this . name = args . name ; this . kind = args . kind ; this . filtersMap = args . filters ?? ( { } as Filters ) ; } private filter < R extends keyof Filters > ( ref ) ; private filter ( ref ) ; /**
     * Gets a filter from the input by its ref
     */ private filter ( ref ) { return this . filtersMap [ ref ] ; } /**
     * Adds a filter to this source, provided that 1. The filter has not already been applied
     * to another source, and 2. The source in OBS does not have a filter with a different type
     * but the same name as the filter being added.
     */ private async addFilter ( ref , filter , index ? ) { if ( filter . source ) { throw new Error ( `Filter ${ this . name } has already been applied to source ${ filter . source . name }` ) ; } const exists = await this . obs . call ( "GetSourceFilter" , { sourceName : this . name , filterName : filter . name , } ) . then ( ( f ) => { if ( filter . kind !== f . filterKind ) throw new Error ( `Filter ${ this . name } already exists but has different kind. Expected ${ filter . kind }, found ${ f . filterKind }` ) ; return true ; } ) . catch ( ( ) => false ) ; filter . source = this ; if ( ! exists ) await this . obs . call ( "CreateSourceFilter" , { filterName : filter . name , filterKind : filter . kind , filterIndex : index , filterSettings : filter . initialSettings , sourceName : this . name , } ) ; Object . assign ( this . filtersMap , { [ ref ] : filter } ) ; if ( index !== undefined ) this . filters . splice ( index ?? this . filters . length , 0 , filter ) ; } /**
     * Updates the source's filters in OBS so that they match the filters defined in `this.filters`.
     * This is done by removing filters that are present on the source in OBS but not on `this`, and adding filters that are present on `this` but not on the source.
     *
     * This shouldn't be required very often, probably only on source initialization.
     */ private async cleanFilters ( updateSettings = true ) { if ( ! this . exists ) return ; const { filters } = await this . obs . call ( "GetSourceFilterList" , { sourceName : this . name , } ) ; const filtersToRemove = filters . filter ( ( obsFilter ) => this . filters . every ( ( filter ) => filter . name !== obsFilter . filterName || ( filter . name === obsFilter . filterName && filter . kind !== obsFilter . filterKind ) ) ) ; const filtersToAdd = this . filters . filter ( ( filter ) => filters . every ( ( obsFilter ) => filter . name !== obsFilter . filterName || ( filter . name === obsFilter . filterName && filter . kind !== obsFilter . filterKind ) ) ) ; const filtersToUpdateSettings = this . filters . filter ( ( filter ) => filters . some ( ( sourceFilter ) => filter . name === sourceFilter . filterName && filter . kind === sourceFilter . filterKind ) ) ; await Promise . all ( filtersToRemove . map ( ( filter ) => this . obs . call ( "RemoveSourceFilter" , { sourceName : this . name , filterName : filter . filterName , } ) ) ) ; await Promise . all ( filtersToAdd . map ( ( filter ) => this . obs . call ( "CreateSourceFilter" , { sourceName : this . name , filterName : filter . name , filterKind : filter . kind , filterSettings : filter . initialSettings , filterIndex : filter . index , } ) ) ) ; if ( updateSettings ) await Promise . all ( filtersToUpdateSettings . map ( ( filter ) => filter . setSettings ( filter . settings ) ) ) ; } internal async initialize ( obs ) { if ( this . initalized ) return ; this . obs = obs ; const exists = await this . fetchExists ( ) ; if ( exists ) await this . fetchRefs ( ) ; this . _exists = exists ; this . _initialized = true ; } internal linkItem ( scene , id , ref ) { this . linked = true ; this . _exists = true ; this . _initialized = true ; this . obs = scene . obs ; return this . createSceneItemObject ( scene , id , ref ) ; } returns internal async createSceneItem ( ref , scene ) { if ( ! this . initalized ) throw new Error ( `Cannot create item of source ${ this . name } as it is not initialized` ) ; let itemId ; let transform = null ; if ( this . exists ) { const id = this . getRef ( scene . name , ref ) ; if ( id !== undefined ) { try { const res = await this . obs . call ( "GetSceneItemTransform" , { sceneItemId : id , sceneName : scene . name , } ) ; transform = res . sceneItemTransform as SceneItemTransform ; itemId = id ; } catch { this . removeRef ( scene . name , ref ) ; const { sceneItemId } = await this . obs . call ( "CreateSceneItem" , { sceneName : scene . name , sourceName : this . name , } ) ; itemId = sceneItemId ; } } else { const { sceneItemId } = await this . obs . call ( "CreateSceneItem" , { sceneName : scene . name , sourceName : this . name , } ) ; itemId = sceneItemId ; } } else { itemId = await this . createFirstSceneItem ( scene ) ; this . _exists = true ; for ( let ref in this . filters ) { let filter = this . filters [ ref ] ; filter . source = this ; filter . settings = filter . initialSettings ; } } this . addRef ( scene . name , ref , itemId ) ; const item = this . createSceneItemObject ( scene , itemId , ref ) ; if ( transform !== null ) item . transform = transform ; return item ; } /**
     * Overridable function for creating  SceneItem instances for a source.
     * Doesn't create any objects in OBS. Instead, creates  SceneItem objects that can
     * override default  SceneItem behaviours.
     *
     *  returns An instance of  SceneItem or a class that extends it.
      createSceneItemObject ( scene , id , ref ) { return new SceneItem ( this , scene , id , ref ) ; } /**
     * Creates the source and a scene item of it.
     * This is abstract since scenes start with 0 items,
     * but inputs start with 1, and scenes have to create
     * their items before creating scene items of themselves
     */ protected abstract createFirstSceneItem ( scene ) ; internal protected abstract fetchExists ( ) ; protected async setPrivateSettings ( settings ) { await this . obs . call ( "SetSourcePrivateSettings" , { sourceName : this . name , sourceSettings : settings , } ) ; } protected getRef ( scene , ref ) { return this . refs [ scene ] ?. [ ref ] ; } protected async addRef ( scene , ref , id ) { ( this . refs [ scene ] ||= { } ) [ ref ] = id ; await this . sendRefs ( ) . catch ( ( e ) => console . warn ( `Failed to add ref ${ ref } -> ${ id }` , e ) ) ; } protected async removeRef ( scene , ref ) { delete this . refs [ scene ] ?. [ ref ] ; await this . sendRefs ( ) . catch ( ( e ) => console . warn ( `Failed to remove ref ${ ref }` , e ) ) ; } private async sendRefs ( ) { await this . obs . call ( "SetSourcePrivateSettings" , { sourceName : this . name , sourceSettings : { SIMPLE_OBS_REFS : this . refs , } , } ) ; } private async fetchRefs ( ) { const { sourceSettings } = await this . obs . call ( "GetSourcePrivateSettings" , { sourceName : this . name , } ) ; this . refs = sourceSettings . SIMPLE_OBS_REFS || { } ; console . log ( this . name , this . refs ) ; } internal pushRefs ( ) { let refs = { } ; this . itemInstances . forEach ( ( item ) => ( ( refs [ item . scene . name ] ||= { } ) [ item . ref ] = item . id ) ) ; this . refs = refs ; return this . sendRefs ( ) ; } }