furkleindustries/sound-manager

View on GitHub
src/Manager/PlayerSubmanager.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import {
  createPlaylist,
} from '../Playlist/createPlaylist';
import {
  doToOneOrMany,
} from '../functions/doToOneOrMany';
import {
  getFrozenObject,
} from '../functions/getFrozenObject';
import {
  getPlaylistMessage,
} from './getPlaylistMessage';
import {
  ICollectionSubmanager,
} from './ICollectionSubmanager';
import {
  IGroup,
} from '../Group/IGroup';
import {
  IPlaylist,
} from '../Playlist/IPlaylist';
import {
  IPlaylistsMap,
} from './IPlaylistsMap';
import {
  IPlaylistOptions,
} from '../Playlist/IPlaylistOptions';
import {
  IPlayerSubmanager,
} from './IPlayerSubmanager';
import {
  ISoundGroupIdentifier,
} from '../interfaces/ISoundGroupIdentifier';
import {
  log,
} from 'colorful-logging';
import {
  nameOrAllKeys,
} from '../functions/nameOrAllKeys';
import {
  shallowFlattenArray,
} from '../functions/shallowFlattenArray';
import {
  shouldLoopPlaylist,
} from './shouldLoopPlaylist';
import {
  assert,
  assertValid,
} from 'ts-assertions';

export class PlayerSubmanager implements IPlayerSubmanager {
  /* Player */
  private __playlists: IPlaylistsMap = getFrozenObject();
  get playlists() {
    return this.__playlists;
  }

  private readonly __getCollection: () => ICollectionSubmanager;

  constructor(
    { getCollection }: { getCollection: () => ICollectionSubmanager },
  )
  {
    this.__getCollection = assertValid<() => ICollectionSubmanager>(getCollection);
  }

  public readonly playGroup = async (name: string): Promise<void[]> => (
    this.__getCollection().getGroup(name).playAllSounds()
  );

  public readonly playGroups = async (names: string[]): Promise<void[]> => {
    assert(Array.isArray(names));
    const val = await Promise.all(names.map((name) => this.playGroup(name)));
    return shallowFlattenArray(val);
  };

  public readonly playSound = (
    name: string,
    groupName: string = 'default',
  ): Promise<void> => (
    this.__getCollection().getGroup(groupName).playSound(name)
  );

  public readonly playSounds = (
    names: string[],
    groupName: string = 'default',
  ): Promise<void[]> => {
    assert(Array.isArray(names));
    return this.__getCollection().getGroup(groupName).playSounds(names);
  };

  public readonly playAllSounds = async (
    groupName?: string,
  ): Promise<void[]> => {
    if (groupName) {
      return this.playGroup(groupName);
    } else {
      const val = await Promise.all(
        this.__getCollection().getAllGroups().map(({ playAllSounds }) => (
          playAllSounds()
        ))
      );

      return shallowFlattenArray(val);
    }
  };

  public readonly pauseSound = (
    name: string,
    groupName: string = 'default',
  ) => {
    this.__getCollection().getGroup(groupName).pauseSound(name);
    return this;
  };

  public readonly pauseSounds = (
    names: string[],
    groupName: string = 'default',
  ) => {
    assert(Array.isArray(names));    
    this.__getCollection().getGroup(groupName).pauseSounds(names);

    return this;
  };

  public readonly pauseAllSounds = (groupName?: string) => {
    const oneOrMany = nameOrAllKeys(groupName, this.__getCollection().groups);
    doToOneOrMany(this.__getCollection().groups, oneOrMany, 'pauseAllSounds');

    return this;
  };

  public readonly stopSound = (
    name: string,
    groupName: string = 'default',
  ) => {
    this.__getCollection().getGroup(groupName).stopSound(name);
    return this;
  };

  public readonly stopSounds = (
    names: string[],
    groupName: string = 'default',
  ) => {
    this.__getCollection().getGroup(groupName).stopSounds(names);
    return this;
  };

  public readonly stopAllSounds = (hard = false, groupName?: string) => {
    if (hard === true) {
      let groups: IGroup[];
      if (groupName) {
        groups = this.__getCollection().getGroups([ groupName ]);
      } else {
        groups = this.__getCollection().getAllGroups();
      }

      for (const group of groups) {
        for (const soundName of Object.keys(group.sounds)) {
          const sound = group.getSound(soundName);
          sound.stop(true);
        }
      }
    } else {
      const oneOrMany = nameOrAllKeys(groupName, this.__getCollection().groups);
      doToOneOrMany(this.__getCollection().groups, oneOrMany, 'stopAllSounds');
    }

    return this;
  };

  public readonly addPlaylist = (
    name: string,
    options: Array<ISoundGroupIdentifier | string> | IPlaylistOptions,
  ) => {
    const playlist = Array.isArray(options) ?
      createPlaylist({ ids: getFrozenObject(options) as any }) :
      createPlaylist(getFrozenObject(options));

    this.addPlaylists({ [name]: playlist });

    return playlist;
  };

  public readonly addPlaylists = (playlists: IPlaylistsMap) => {
    const playls = playlists || {};
    const names = Object.keys(playls);
    names.forEach((playlistName) => assert(!(playlistName in this.playlists)));
    this.__playlists = getFrozenObject(this.playlists, playls);

    return this;
  };

  public readonly hasPlaylist = (name: string) => (name in this.playlists);

  public readonly getPlaylist = (name: string) => (
    assertValid<IPlaylist>(this.playlists[name])
  );

  public readonly hasPlaylists = (names: string[]) => (
    names.filter((playlistName) => (
      !(playlistName in this.playlists)
    )).length === 0
  );

  public readonly getPlaylists = (names: string[]) => (
    names.map((name) => this.getPlaylist(name))
  );

  public readonly removePlaylist = (name: string) => (
    this.removePlaylists([ name ])
  );

  public readonly removePlaylists = (names: string | string[]) => {
    const playls = { ...this.playlists, };
    if (typeof names === 'string') {
      delete playls[names];
    } else {
      names.forEach((name) => delete playls[name]);
    }

    this.__playlists = getFrozenObject(playls);

    return this;
  };

  public readonly removeAllPlaylists = () => (
    this.removePlaylists(Object.keys(this.playlists))
  );

  public readonly playPlaylist = async (name: string) => {
    const playlist = this.getPlaylist(name);
    log(`Playing playlist ${name}.`);
    let playIndex = 0;
    let loopedTimes = 0;
    let sentinel = true;
    while (sentinel) {
      const id = playlist.ids[playIndex];
      log(`${id.groupName}.${id.soundName} starting.`);
      const {
        ended,
        looped,
      } = await this.__playlistPlaySound(
        playlist,
        playIndex,
        loopedTimes,
        name,
      );

      log(`${id.groupName}.${id.soundName} ending.`);

      playIndex += 1;
      if (looped) {
        log(`Looping playlist ${name}.`);
        playIndex = 0;
        loopedTimes += 1;
      }

      sentinel = ended;
    }
  };

  private readonly  __playlistPlaySound = async (
    playlist: IPlaylist,
    playIndex: number,
    loopedTimes: number,
    name?: string,
  ) => {
    const id = playlist.ids[playIndex];
    const sound = this.__getCollection().getSound(id.soundName, id.groupName);

    await sound.play(
      /* Overrides the sound's fade with the playlist fade. This argument is
        * ignored if it's falsy. */
      { fadeOverride: playlist.fade },
    );

    if (playIndex === playlist.ids.length - 1) {
      playlist.tryCallback(name);

      if (shouldLoopPlaylist(playlist, loopedTimes)) {
        /* This value is incremented when the loop begins a new iteration so
          * it must be -1 rather than 0. */
        return getPlaylistMessage(/* ended */ false, /* looped */ true);
      }

      return getPlaylistMessage(/* ended */ true, /* looped */ false);
    }

    return getPlaylistMessage(/* ended */ false, /* looped */ false);
  };

  public readonly playPlaylists = async (names: string[]) => {
    assert(Array.isArray(names));
    await Promise.all(names.map(this.playPlaylist));
  };

  public readonly stopPlaylist = (name: string) => {
    this.getPlaylist(name).ids.forEach(({ groupName, soundName, }) => (
      this.__getCollection().getSound(soundName, groupName).stop()  
    ));

    return this;
  };

  public readonly stopPlaylists = (names: string[]) => {
    assert(Array.isArray(names));
    names.map((name) => this.stopPlaylist(name));

    return this;
  };
}