From a9b5047df8afb7d39ebc6a5ba8d2ae8d9ac2161e Mon Sep 17 00:00:00 2001 From: Dzianis Dashkevich <98566601+dzianis-dashkevich@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:53:04 -0400 Subject: [PATCH] feat: add service locator (#26) * feat: add service locator * chore: fix build issue --------- Co-authored-by: Dzianis Dashkevich --- packages/playback/index.html | 39 ---------- .../lib/configuration/configurationManager.ts | 2 +- .../src/lib/network/networkManager.ts | 2 +- .../src/lib/pipelines/mse/mseManager.ts | 6 +- packages/playback/src/lib/player.ts | 57 ++++++-------- packages/playback/src/lib/serviceLocator.ts | 77 +++++++++++++++++++ .../lib/types/interceptors.declarations.ts | 13 ++++ .../playback/src/lib/utils/envCapabilities.ts | 2 +- .../playback/src/lib/utils/eventEmitter.ts | 2 +- .../src/lib/utils/interceptorsStorage.ts | 3 +- packages/playback/src/lib/utils/logger.ts | 22 ++++-- packages/playback/test/player.test.ts | 51 ++++++------ 12 files changed, 164 insertions(+), 112 deletions(-) delete mode 100644 packages/playback/index.html create mode 100644 packages/playback/src/lib/serviceLocator.ts create mode 100644 packages/playback/src/lib/types/interceptors.declarations.ts diff --git a/packages/playback/index.html b/packages/playback/index.html deleted file mode 100644 index aab45f9..0000000 --- a/packages/playback/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - TestPage - - - - - - diff --git a/packages/playback/src/lib/configuration/configurationManager.ts b/packages/playback/src/lib/configuration/configurationManager.ts index 55278ff..6324a3e 100644 --- a/packages/playback/src/lib/configuration/configurationManager.ts +++ b/packages/playback/src/lib/configuration/configurationManager.ts @@ -2,7 +2,7 @@ import type { PlayerConfiguration } from '../types/configuration.declarations'; import PlayerConfigurationImpl from './configurationNodes/playerConfigurationNode'; import { Store } from '../utils/store'; -export default class ConfigurationManager extends Store { +export class ConfigurationManager extends Store { public constructor() { super(() => PlayerConfigurationImpl.default()); } diff --git a/packages/playback/src/lib/network/networkManager.ts b/packages/playback/src/lib/network/networkManager.ts index 5f8997c..304fee6 100644 --- a/packages/playback/src/lib/network/networkManager.ts +++ b/packages/playback/src/lib/network/networkManager.ts @@ -11,7 +11,7 @@ import type { PlayerNetworkConfiguration } from '../types/configuration.declarat import type { IEventEmitter } from '../types/eventEmitter.declarations'; import type { NetworkEventMap } from '../types/eventTypeToEventMap.declarations'; -interface NetworkManagerDependencies { +export interface NetworkManagerDependencies { logger: ILogger; networkInterceptorsProvider: INetworkInterceptorsProvider; eventEmitter: IEventEmitter; diff --git a/packages/playback/src/lib/pipelines/mse/mseManager.ts b/packages/playback/src/lib/pipelines/mse/mseManager.ts index 358c9e1..42c4db5 100644 --- a/packages/playback/src/lib/pipelines/mse/mseManager.ts +++ b/packages/playback/src/lib/pipelines/mse/mseManager.ts @@ -1,4 +1,4 @@ -import type Logger from '../../utils/logger'; +import type { ILogger } from '../../types/logger.declarations'; enum OperationType { append, @@ -11,14 +11,14 @@ interface SourceBufferWrapper { } export default class MseManager { - private readonly logger_: Logger; + private readonly logger_: ILogger; //TODO: ManagedMediaSource private readonly mediaSource_: MediaSource = new MediaSource(); private readonly sourceBuffers_ = new Map(); private readonly sourceOpen_: Promise = this.initMediaSource_(); private readonly srcURL_: string = URL.createObjectURL(this.mediaSource_); - public constructor(logger: Logger) { + public constructor(logger: ILogger) { this.logger_ = logger.createSubLogger('MseManager'); } diff --git a/packages/playback/src/lib/player.ts b/packages/playback/src/lib/player.ts index f8cf2bb..03568de 100644 --- a/packages/playback/src/lib/player.ts +++ b/packages/playback/src/lib/player.ts @@ -1,13 +1,10 @@ import type { ILogger } from './types/logger.declarations'; -import Logger from './utils/logger'; import { LoggerLevel } from './consts/loggerLevel'; import type { PlayerConfiguration } from './types/configuration.declarations'; import type { IStore } from './types/store.declarations'; -import ConfigurationManager from './configuration/configurationManager'; import type { DeepPartial } from './types/utility.declarations'; import type { EventListener, IEventEmitter } from './types/eventEmitter.declarations'; import type { EventTypeToEventMap } from './types/eventTypeToEventMap.declarations'; -import EventEmitter from './utils/eventEmitter'; import { PlayerEventType } from './consts/events'; import { ConfigurationChangedEvent, @@ -17,7 +14,6 @@ import { VolumeChangedEvent, } from './events/playerEvents'; import type { CapabilitiesProbeResult, IEnvCapabilitiesProvider } from './types/envCapabilities.declarations'; -import EnvCapabilitiesProvider from './utils/envCapabilities'; import type { ILoadLocalSource, ILoadRemoteSource, ISourceModel } from './types/source.declarations'; import type { IPipeline, IPipelineFactory } from './types/pipeline.declarations'; import type PlayerTimeRange from './utils/timeRanges'; @@ -27,17 +23,16 @@ import type { IAudioTrack } from './types/tracks.declarations'; import { NoSupportedPipelineError } from './errors/pipelineErrors'; import { Source } from './utils/source'; import type { INetworkManager } from './types/network.declarations'; -import { NetworkManager } from './network/networkManager'; -import { InterceptorsStorage } from './utils/interceptorsStorage'; -import { InterceptorType } from './consts/interceptorType'; -import type { InterceptorTypeToInterceptorMap } from './types/interceptorTypeToInterceptorMap.declarations'; +import type { IInterceptorsStorage } from './types/interceptors.declarations'; +import { ServiceLocator } from './serviceLocator'; interface PlayerDependencies { - logger?: ILogger; - configurationManager?: IStore; - eventEmitter?: IEventEmitter; - envCapabilitiesProvider?: IEnvCapabilitiesProvider; - networkManager?: INetworkManager; + readonly logger: ILogger; + readonly interceptorsStorage: IInterceptorsStorage; + readonly configurationManager: IStore; + readonly eventEmitter: IEventEmitter; + readonly envCapabilitiesProvider: IEnvCapabilitiesProvider; + readonly networkManager: INetworkManager; } interface VersionInfo { @@ -58,6 +53,10 @@ export class Player { public static LoggerLevel = LoggerLevel; public static Event = PlayerEventType; + public static create(): Player { + return new Player(new ServiceLocator()); + } + /** * MARK: Private Properties */ @@ -77,30 +76,20 @@ export class Player { private readonly eventEmitter_: IEventEmitter; private readonly envCapabilitiesProvider_: IEnvCapabilitiesProvider; private readonly networkManager_: INetworkManager; - private readonly interceptorsStorage_: InterceptorsStorage; + private readonly interceptorsStorage_: IInterceptorsStorage; /** * You can pass your own implementations via dependencies. * - Pass your own logger, you have to implement ILogger interface. - * @param dependencies - optional dependencies - */ - public constructor(dependencies: PlayerDependencies = {}) { - this.interceptorsStorage_ = new InterceptorsStorage(); - this.logger_ = dependencies.logger ?? new Logger(console, 'Player'); - this.configurationManager_ = dependencies.configurationManager ?? new ConfigurationManager(); - this.eventEmitter_ = dependencies.eventEmitter ?? new EventEmitter(); - this.envCapabilitiesProvider_ = dependencies.envCapabilitiesProvider ?? new EnvCapabilitiesProvider(); - this.networkManager_ = - dependencies.networkManager ?? - new NetworkManager({ - logger: this.logger_.createSubLogger('NetworkManager'), - eventEmitter: this.eventEmitter_, - configuration: this.configurationManager_.getSnapshot().network, - networkInterceptorsProvider: { - getNetworkRequestInterceptors: (): Set => - this.interceptorsStorage_.getInterceptorsSet(InterceptorType.NetworkRequest), - }, - }); + * @param dependencies - player dependencies + */ + public constructor(dependencies: PlayerDependencies) { + this.interceptorsStorage_ = dependencies.interceptorsStorage; + this.logger_ = dependencies.logger; + this.configurationManager_ = dependencies.configurationManager; + this.eventEmitter_ = dependencies.eventEmitter; + this.envCapabilitiesProvider_ = dependencies.envCapabilitiesProvider; + this.networkManager_ = dependencies.networkManager; } /** @@ -110,7 +99,7 @@ export class Player { /** * interceptors storage getter */ - public getInterceptorsStorage(): InterceptorsStorage { + public getInterceptorsStorage(): IInterceptorsStorage { return this.interceptorsStorage_; } diff --git a/packages/playback/src/lib/serviceLocator.ts b/packages/playback/src/lib/serviceLocator.ts new file mode 100644 index 0000000..be48bda --- /dev/null +++ b/packages/playback/src/lib/serviceLocator.ts @@ -0,0 +1,77 @@ +// Interfaces: +import type { ILogger } from './types/logger.declarations'; +import type { LoggerDependencies } from './utils/logger'; +import type { IInterceptorsStorage } from './types/interceptors.declarations'; +import type { PlayerConfiguration } from './types/configuration.declarations'; +import type { IStore } from './types/store.declarations'; +import type { IEventEmitter } from './types/eventEmitter.declarations'; +import type { EventTypeToEventMap } from './types/eventTypeToEventMap.declarations'; +import type { IEnvCapabilitiesProvider } from './types/envCapabilities.declarations'; +import type { INetworkManager } from './types/network.declarations'; +import type { InterceptorTypeToInterceptorMap } from './types/interceptorTypeToInterceptorMap.declarations'; +import type { NetworkManagerDependencies } from './network/networkManager'; + +// Implementations +import { Logger } from './utils/logger'; +import { InterceptorsStorage } from './utils/interceptorsStorage'; +import { ConfigurationManager } from './configuration/configurationManager'; +import { EventEmitter } from './utils/eventEmitter'; +import { EnvCapabilitiesProvider } from './utils/envCapabilities'; +import { NetworkManager } from './network/networkManager'; +import { InterceptorType } from './consts/interceptorType'; + +export class ServiceLocator { + public readonly logger: ILogger; + public readonly interceptorsStorage: IInterceptorsStorage; + public readonly configurationManager: IStore; + public readonly eventEmitter: IEventEmitter; + public readonly envCapabilitiesProvider: IEnvCapabilitiesProvider; + public readonly networkManager: INetworkManager; + + public constructor() { + const { console } = window; + + this.configurationManager = this.createConfigurationManager_(); + + const configuration = this.configurationManager.getSnapshot(); + + this.logger = this.createLogger_({ console, label: 'Player', delimiter: '>' }); + + this.interceptorsStorage = this.createInterceptorsStorage_(); + this.eventEmitter = this.createEventEmitter_(); + this.envCapabilitiesProvider = this.createEnvCapabilitiesProvider_(); + this.networkManager = this.createNetworkManager_({ + logger: this.logger.createSubLogger('NetworkManager'), + eventEmitter: this.eventEmitter, + configuration: configuration.network, + networkInterceptorsProvider: { + getNetworkRequestInterceptors: (): Set => + this.interceptorsStorage.getInterceptorsSet(InterceptorType.NetworkRequest), + }, + }); + } + + protected createLogger_(dependencies: LoggerDependencies): ILogger { + return new Logger(dependencies); + } + + protected createInterceptorsStorage_(): IInterceptorsStorage { + return new InterceptorsStorage(); + } + + protected createConfigurationManager_(): IStore { + return new ConfigurationManager(); + } + + protected createEventEmitter_(): IEventEmitter { + return new EventEmitter(); + } + + protected createEnvCapabilitiesProvider_(): IEnvCapabilitiesProvider { + return new EnvCapabilitiesProvider(); + } + + protected createNetworkManager_(dependencies: NetworkManagerDependencies): INetworkManager { + return new NetworkManager(dependencies); + } +} diff --git a/packages/playback/src/lib/types/interceptors.declarations.ts b/packages/playback/src/lib/types/interceptors.declarations.ts new file mode 100644 index 0000000..8e7842d --- /dev/null +++ b/packages/playback/src/lib/types/interceptors.declarations.ts @@ -0,0 +1,13 @@ +import type { InterceptorType } from '../consts/interceptorType'; +import type { InterceptorTypeToInterceptorMap } from './interceptorTypeToInterceptorMap.declarations'; + +export interface IInterceptorsStorage { + addInterceptor(interceptorType: K, interceptor: InterceptorTypeToInterceptorMap[K]): void; + removeInterceptor( + interceptorType: K, + interceptor: InterceptorTypeToInterceptorMap[K] + ): void; + getInterceptorsSet(interceptorType: K): Set; + removeAllInterceptorsForType(interceptorType: K): void; + removeAllInterceptors(): void; +} diff --git a/packages/playback/src/lib/utils/envCapabilities.ts b/packages/playback/src/lib/utils/envCapabilities.ts index eb38552..a2bc50f 100644 --- a/packages/playback/src/lib/utils/envCapabilities.ts +++ b/packages/playback/src/lib/utils/envCapabilities.ts @@ -24,7 +24,7 @@ const persistentEmeConfig = { sessionTypes: ['persistent-license'], }; -export default class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider { +export class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider { private cache_: CapabilitiesProbeResult | null = null; public async probe(): Promise { diff --git a/packages/playback/src/lib/utils/eventEmitter.ts b/packages/playback/src/lib/utils/eventEmitter.ts index 41d8c3f..d8c5575 100644 --- a/packages/playback/src/lib/utils/eventEmitter.ts +++ b/packages/playback/src/lib/utils/eventEmitter.ts @@ -1,6 +1,6 @@ import type { IEventEmitter, EventListener } from '../types/eventEmitter.declarations'; -export default class EventEmitter implements IEventEmitter { +export class EventEmitter implements IEventEmitter { private events_ = new Map>>(); public addEventListener(event: K, eventListener: EventListener): void { diff --git a/packages/playback/src/lib/utils/interceptorsStorage.ts b/packages/playback/src/lib/utils/interceptorsStorage.ts index 5a60c42..78fb3c1 100644 --- a/packages/playback/src/lib/utils/interceptorsStorage.ts +++ b/packages/playback/src/lib/utils/interceptorsStorage.ts @@ -1,7 +1,8 @@ import type { InterceptorType } from '../consts/interceptorType'; import type { InterceptorTypeToInterceptorMap } from '../types/interceptorTypeToInterceptorMap.declarations'; +import type { IInterceptorsStorage } from '../types/interceptors.declarations'; -export class InterceptorsStorage { +export class InterceptorsStorage implements IInterceptorsStorage { private readonly storage_ = new Map>(); public addInterceptor( diff --git a/packages/playback/src/lib/utils/logger.ts b/packages/playback/src/lib/utils/logger.ts index 44c3836..c5fa3fa 100644 --- a/packages/playback/src/lib/utils/logger.ts +++ b/packages/playback/src/lib/utils/logger.ts @@ -3,19 +3,31 @@ import type { ILogger } from '../types/logger.declarations'; const style = 'background: #333; padding: 3px; color: #bada55'; -export default class Logger implements ILogger { +export interface LoggerDependencies { + console: Console; + label: string; + delimiter: string; +} + +export class Logger implements ILogger { private readonly console_: Console; private readonly label_: string; + private readonly delimiter_: string; private level_: LoggerLevel = LoggerLevel.Debug; - public constructor(console: Console, label: string) { - this.console_ = console; - this.label_ = `%c${label}`; + public constructor(dependencies: LoggerDependencies) { + this.console_ = dependencies.console; + this.label_ = `%c${dependencies.label}`; + this.delimiter_ = dependencies.delimiter; } public createSubLogger(subLabel: string): Logger { - return new Logger(this.console_, this.label_ + ' > ' + subLabel); + return new Logger({ + console: this.console_, + label: `${this.label_} ${this.delimiter_} ${subLabel}`, + delimiter: this.delimiter_, + }); } public setLoggerLevel(level: LoggerLevel): void { diff --git a/packages/playback/test/player.test.ts b/packages/playback/test/player.test.ts index 33bd0f0..692b9dd 100644 --- a/packages/playback/test/player.test.ts +++ b/packages/playback/test/player.test.ts @@ -2,10 +2,9 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { Player } from '../src/lib/player'; import type { PlayerConfiguration } from '../src/lib/types/configuration.declarations'; import { ConfigurationChangedEvent, LoggerLevelChangedEvent, VolumeChangedEvent } from '../src/lib/events/playerEvents'; -import EventEmitter from '../src/lib/utils/eventEmitter'; -import type { EventTypeToEventMap } from '../src/lib/types/eventTypeToEventMap.declarations'; import type { PlayerEvent } from '../src/lib/events/basePlayerEvent'; import { RequestType } from '../src/lib/consts/requestType'; +import { ServiceLocator } from '../src/lib/serviceLocator'; // import type { ILogger } from '../src/lib/types/logger'; // import { instance, mock, verify, when } from '@typestrong/ts-mockito'; @@ -58,11 +57,11 @@ const createPlayerDefaultConfiguration = (): PlayerConfiguration => ({ describe('Player spec', () => { let player: Player; - let eventEmitter: EventEmitter; + let serviceLocator: ServiceLocator; beforeEach(() => { - eventEmitter = new EventEmitter(); - player = new Player({ eventEmitter }); + serviceLocator = new ServiceLocator(); + player = new Player(serviceLocator); }); describe('getLoggerLevel', () => { @@ -186,10 +185,10 @@ describe('Player spec', () => { actualEvents.push(event); }); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); - eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); - eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); + serviceLocator.eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); expect(expectedEvents).toEqual(actualEvents); }); @@ -204,10 +203,10 @@ describe('Player spec', () => { actualEvents.push(event); }); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); - eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); - eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); + serviceLocator.eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); expect(expectedEvents).toEqual(actualEvents); }); @@ -225,18 +224,18 @@ describe('Player spec', () => { player.addEventListener(Player.Event.LoggerLevelChanged, listener); player.addEventListener(Player.Event.VolumeChanged, listener); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); player.removeEventListener(Player.Event.LoggerLevelChanged, listener); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); - eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); player.removeEventListener(Player.Event.VolumeChanged, listener); - eventEmitter.emitEvent(new VolumeChangedEvent(0.6)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.6)); - eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); + serviceLocator.eventEmitter.emitEvent(new ConfigurationChangedEvent(player.getConfigurationSnapshot())); expect(expectedEvents).toEqual(actualEvents); }); @@ -267,13 +266,13 @@ describe('Player spec', () => { player.addEventListener(Player.Event.LoggerLevelChanged, listener2); player.addEventListener(Player.Event.LoggerLevelChanged, listener3); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); player.removeAllEventListenersForType(Player.Event.LoggerLevelChanged); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Info)); expect(expectedEvents).toEqual(actualEvents); }); @@ -291,13 +290,13 @@ describe('Player spec', () => { player.addEventListener(Player.Event.LoggerLevelChanged, listener); player.addEventListener(Player.Event.VolumeChanged, listener); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); - eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); player.removeAllEventListeners(); - eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); - eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); + serviceLocator.eventEmitter.emitEvent(new LoggerLevelChangedEvent(Player.LoggerLevel.Warn)); + serviceLocator.eventEmitter.emitEvent(new VolumeChangedEvent(0.5)); expect(expectedEvents).toEqual(actualEvents); });