Move to Renderer V2

This commit is contained in:
duckietm
2024-04-03 09:27:56 +02:00
parent 110c3ad393
commit b3134ce50b
4080 changed files with 115593 additions and 66375 deletions
@@ -0,0 +1,190 @@
import { IMessageEvent, IPlaylistController, ISongInfo } from '@nitrots/api';
import { GetCommunication, GetJukeboxPlayListMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, NowPlayingMessageEvent } from '@nitrots/communication';
import { GetEventDispatcher, NowPlayingEvent, PlayListStatusEvent, SongInfoReceivedEvent } from '@nitrots/events';
import { GetSoundManager } from '../GetSoundManager';
import { SongDataEntry } from '../common';
import { MusicPriorities } from './MusicPriorities';
export class JukeboxPlaylistController implements IPlaylistController
{
private _isPlaying = false;
private _entries: ISongInfo[];
private _currentSongId: number = -1;
private _missingSongInfo: number[] = [];
private _playPosition: number = -1;
private _disposed: boolean = false;
private _messageEvents: IMessageEvent[] = [];
constructor()
{
this.onSongInfoReceivedEvent = this.onSongInfoReceivedEvent.bind(this);
}
public init(): void
{
this._messageEvents = [
new NowPlayingMessageEvent(this.onNowPlayingMessageEvent.bind(this)),
new JukeboxSongDisksMessageEvent(this.onJukeboxSongDisksMessageEvent.bind(this)),
new JukeboxPlayListFullMessageEvent(this.onJukeboxPlayListFullMessageEvent.bind(this))
];
this._messageEvents.forEach(event => GetCommunication().registerMessageEvent(event));
GetEventDispatcher().addEventListener(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, this.onSongInfoReceivedEvent);
}
public dispose(): void
{
if(this._disposed) return;
this.stopPlaying();
GetEventDispatcher().removeEventListener(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, this.onSongInfoReceivedEvent);
this._messageEvents.forEach(event => GetCommunication().removeMessageEvent(event));
this._disposed = true;
}
private onNowPlayingMessageEvent(event: NowPlayingMessageEvent): void
{
const parser = event.getParser();
this._isPlaying = (parser.currentSongId !== -1);
if(parser.currentSongId >= 0)
{
GetSoundManager().musicController.playSong(parser.currentSongId, MusicPriorities.PRIORITY_ROOM_PLAYLIST, (parser.syncCount / 1000), 0, 1, 1);
this._currentSongId = parser.currentSongId;
}
else
{
this.stopPlaying();
}
if(parser.nextSongId >= 0) GetSoundManager().musicController.addSongInfoRequest(parser.nextSongId);
this._playPosition = parser.currentPosition;
GetEventDispatcher().dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPE_SONG_CHANGED, MusicPriorities.PRIORITY_ROOM_PLAYLIST, parser.currentSongId, parser.currentPosition));
}
private onJukeboxSongDisksMessageEvent(event: JukeboxSongDisksMessageEvent): void
{
const parser = event.getParser();
this._entries = [];
for(let i = 0; i < parser.songDisks.length; i++)
{
const songId = parser.songDisks.getWithIndex(i);
const diskId = parser.songDisks.getKey(i);
let songInfo = (GetSoundManager().musicController.getSongInfo(songId) as SongDataEntry);
if(!songInfo)
{
songInfo = new SongDataEntry(songId, -1, null, null, null);
if(this._missingSongInfo.indexOf(songId) < 0)
{
this._missingSongInfo.push(songId);
GetSoundManager().musicController.requestSongInfoWithoutSamples(songId);
}
}
songInfo.diskId = diskId;
this._entries.push(songInfo);
}
if(!this._missingSongInfo.length) GetEventDispatcher().dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED));
}
private onJukeboxPlayListFullMessageEvent(event: JukeboxPlayListFullMessageEvent): void
{
GetEventDispatcher().dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_FULL));
}
private onSongInfoReceivedEvent(event: SongInfoReceivedEvent): void
{
for(let i = 0; i < this.length; i++)
{
const songData = this._entries[i];
if(songData.id === event.id)
{
const diskId = songData.diskId;
const updatedSongData = GetSoundManager().musicController.getSongInfo(event.id);
if(updatedSongData)
{
updatedSongData.diskId = diskId;
this._entries[i] = updatedSongData;
}
break;
}
}
const missingIndex = this._missingSongInfo.indexOf(event.id);
if(missingIndex >= 0) this._missingSongInfo.splice(missingIndex, 1);
if(!this._missingSongInfo.length) GetEventDispatcher().dispatchEvent(new PlayListStatusEvent(PlayListStatusEvent.PLUE_PLAY_LIST_UPDATED));
}
public stopPlaying(): void
{
GetSoundManager().musicController.stop(this.priority);
this._currentSongId = -1;
this._playPosition = -1;
this._isPlaying = false;
}
public getEntry(index: number): ISongInfo
{
if((index < 0) || (index >= this._entries.length)) return null;
return this._entries[index];
}
public requestPlayList(): void
{
GetCommunication().connection.send(new GetJukeboxPlayListMessageComposer());
}
public get priority(): number
{
return MusicPriorities.PRIORITY_ROOM_PLAYLIST;
}
public get length(): number
{
if(!this._entries) return 0;
return this._entries.length;
}
public get playPosition(): number
{
return this._playPosition;
}
public get currentSongId(): number
{
return this._currentSongId;
}
public get isPlaying(): boolean
{
return this._isPlaying;
}
public get entries(): ISongInfo[]
{
return this._entries;
}
}
+560
View File
@@ -0,0 +1,560 @@
import { IAdvancedMap, IMusicController, IPlaylistController, ISongInfo } from '@nitrots/api';
import { GetCommunication, GetNowPlayingMessageComposer, GetSongInfoMessageComposer, GetUserSongDisksMessageComposer, TraxSongInfoMessageEvent, UserSongDisksInventoryMessageEvent } from '@nitrots/communication';
import { GetConfiguration } from '@nitrots/configuration';
import { GetEventDispatcher, NotifyPlayedSongEvent, NowPlayingEvent, RoomObjectSoundMachineEvent, SongDiskInventoryReceivedEvent, SongInfoReceivedEvent, SoundManagerEvent } from '@nitrots/events';
import { AdvancedMap } from '@nitrots/utils';
import { GetSoundManager } from '../GetSoundManager';
import { SongDataEntry, SongStartRequestData } from '../common';
import { TraxData } from '../trax/TraxData';
import { JukeboxPlaylistController } from './JukeboxPlaylistController';
import { MusicPlayer } from './MusicPlayer';
import { MusicPriorities } from './MusicPriorities';
export class MusicController implements IMusicController
{
public static readonly SKIP_POSITION_SET: number = -1;
private static readonly MAXIMUM_NOTIFY_PRIORITY: number = MusicPriorities.PRIORITY_ROOM_PLAYLIST;
private _timerInstance: number = 1;
private _songRequestList: number[] = [];
private _requestedSongs: Map<number, boolean> = new Map();
private _availableSongs: Map<number, SongDataEntry> = new Map();
private _songRequestsPerPriority: SongStartRequestData[] = [];
private _songRequestCountsPerPriority: number[] = [];
private _diskInventoryMissingData: number[] = [];
private _songDiskInventory: IAdvancedMap<number, number> = new AdvancedMap();
private _priorityPlaying: number = -1;
private _requestNumberPlaying: number = -1;
private _roomItemPlaylist: IPlaylistController;
private _musicPlayer: MusicPlayer;
private _songIdPlaying: number = 1;
private _previousNotifiedSongId: number = -1;
private _previousNotificationTime: number = -1;
constructor()
{
this.onJukeboxInit = this.onJukeboxInit.bind(this);
this.onJukeboxDispose = this.onJukeboxDispose.bind(this);
this.onSoundMachineInit = this.onSoundMachineInit.bind(this);
this.onSoundMachineDispose = this.onSoundMachineDispose.bind(this);
this.onTraxSongComplete = this.onTraxSongComplete.bind(this);
}
public init(): void
{
GetCommunication().registerMessageEvent(new TraxSongInfoMessageEvent(this.onTraxSongInfoMessageEvent.bind(this)));
GetCommunication().registerMessageEvent(new UserSongDisksInventoryMessageEvent(this.onSongDiskInventoryMessage.bind(this)));
this._timerInstance = window.setInterval(this.onTick.bind(this), 1000);
this._musicPlayer = new MusicPlayer(GetConfiguration().getValue<string>('external.samples.url'));
GetEventDispatcher().addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onJukeboxInit);
GetEventDispatcher().addEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onJukeboxDispose);
GetEventDispatcher().addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onSoundMachineInit);
GetEventDispatcher().addEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onSoundMachineDispose);
GetEventDispatcher().addEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onTraxSongComplete);
}
public getRoomItemPlaylist(_arg_1?: number): IPlaylistController
{
return this._roomItemPlaylist;
}
public get songDiskInventory(): IAdvancedMap<number, number>
{
return this._songDiskInventory;
}
public getSongDiskInventorySize(): number
{
return this._songDiskInventory.length;
}
public getSongDiskInventoryDiskId(k: number): number
{
if(((k >= 0) && (k < this._songDiskInventory.length)))
{
return this._songDiskInventory.getKey(k);
}
return -1;
}
public getSongDiskInventorySongId(k: number): number
{
if(((k >= 0) && (k < this._songDiskInventory.length)))
{
return this._songDiskInventory.getWithIndex(k);
}
return -1;
}
public getSongInfo(songId: number): ISongInfo
{
const _local_2: SongDataEntry = this.getSongDataEntry(songId);
if(!_local_2)
{
this.requestSongInfoWithoutSamples(songId);
}
return _local_2;
}
public getSongIdPlayingAtPriority(priority: number): number
{
if(priority !== this._priorityPlaying)
{
return -1;
}
return this._songIdPlaying;
}
public stop(priority: number): void
{
const isCurrentPlayingPriority = (priority === this._priorityPlaying);
const isTopRequestPriority = (this.getTopRequestPriority() === priority);
if(isCurrentPlayingPriority)
{
this.resetSongStartRequest(priority);
this.stopSongAtPriority(priority);
}
else
{
this.resetSongStartRequest(priority);
if(isTopRequestPriority)
{
this.reRequestSongAtPriority(this._priorityPlaying);
}
}
}
public addSongInfoRequest(k: number): void
{
this.requestSong(k, true);
}
public requestSongInfoWithoutSamples(k: number): void
{
this.requestSong(k, false);
}
public requestUserSongDisks(): void
{
GetCommunication().connection.send(new GetUserSongDisksMessageComposer());
}
public updateVolume(_arg_1: number): void
{
this._musicPlayer.setVolume(_arg_1);
}
public dispose(): void
{
if(this._timerInstance)
{
clearInterval(this._timerInstance);
this._timerInstance = undefined;
}
GetEventDispatcher().removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_INIT, this.onJukeboxInit);
GetEventDispatcher().removeEventListener(RoomObjectSoundMachineEvent.JUKEBOX_DISPOSE, this.onJukeboxDispose);
GetEventDispatcher().removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_INIT, this.onSoundMachineInit);
GetEventDispatcher().removeEventListener(RoomObjectSoundMachineEvent.SOUND_MACHINE_DISPOSE, this.onSoundMachineDispose);
GetEventDispatcher().removeEventListener(SoundManagerEvent.TRAX_SONG_COMPLETE, this.onTraxSongComplete);
}
public get samplesIdsInUse(): number[]
{
let _local_3: SongStartRequestData;
let _local_4: SongDataEntry;
let k = [];
for(let i = 0; i < this._songRequestsPerPriority.length; i++)
{
if(this._songRequestsPerPriority[i])
{
_local_3 = this._songRequestsPerPriority[i];
_local_4 = this._availableSongs.get(_local_3.songId);
if(_local_4)
{
const songData = _local_4.songData;
if(songData.length > 0)
{
const traxData = new TraxData(songData);
k = k.concat(traxData.getSampleIds());
}
}
}
}
return k;
}
public onSongLoaded(songId: number): void
{
const priority = this.getTopRequestPriority();
if(priority >= 0)
{
const songIdAtTopPriority = this.getSongIdRequestedAtPriority(priority);
if(songId === songIdAtTopPriority)
{
this.playSongObject(priority, songId);
}
}
}
public samplesUnloaded(_arg_1: number[]): void
{
throw new Error('Method not implemented.');
}
protected onTraxSongComplete(k: SoundManagerEvent): void
{
if(this.getSongIdPlayingAtPriority(this._priorityPlaying) === k.id)
{
if(((this.getTopRequestPriority() === this._priorityPlaying) && (this.getSongRequestCountAtPriority(this._priorityPlaying) == this._requestNumberPlaying)))
{
this.resetSongStartRequest(this._priorityPlaying);
}
const priorityPlaying = this._priorityPlaying;
this.playSongWithHighestPriority();
if(priorityPlaying >= MusicPriorities.PRIORITY_SONG_PLAY)
{
GetEventDispatcher().dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPW_USER_STOP_SONG, priorityPlaying, k.id, -1));
}
}
}
private onTraxSongInfoMessageEvent(event: TraxSongInfoMessageEvent): void
{
const parser = event.getParser();
for(const song of parser.songs)
{
const songAvailable = !!this.getSongDataEntry(song.id);
const areSamplesRequested = !!this.areSamplesRequested(song.id);
if(!songAvailable)
{
if(!areSamplesRequested)
{
//_local_9 = this._soundManager.loadTraxSong(_local_6.id, _local_6.data);
}
const songInfoEntry: SongDataEntry = new SongDataEntry(song.id, song.length, song.name, song.creator, song.data);
this._availableSongs.set(song.id, songInfoEntry);
const topRequestPriotityIndex: number = this.getTopRequestPriority();
const songId: number = this.getSongIdRequestedAtPriority(topRequestPriotityIndex);
if(song.id === songId)
{
this.playSongObject(topRequestPriotityIndex, songId);
}
GetEventDispatcher().dispatchEvent(new SongInfoReceivedEvent(SongInfoReceivedEvent.SIR_TRAX_SONG_INFO_RECEIVED, song.id));
while(this._diskInventoryMissingData.indexOf(song.id) != -1)
{
this._diskInventoryMissingData.splice(this._diskInventoryMissingData.indexOf(song.id), 1);
if(this._diskInventoryMissingData.length === 0)
{
GetEventDispatcher().dispatchEvent(new SongDiskInventoryReceivedEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT));
}
}
}
}
}
private onSongDiskInventoryMessage(event: UserSongDisksInventoryMessageEvent): void
{
const parser = event.getParser();
this._songDiskInventory.reset();
for(let i = 0; i < parser.songDiskCount; i++)
{
const diskId = parser.getDiskId(i);
const songId = parser.getSongId(i);
this._songDiskInventory.add(diskId, songId);
if(!this._availableSongs.get(songId))
{
this._diskInventoryMissingData.push(songId);
this.requestSongInfoWithoutSamples(songId);
}
}
if(this._diskInventoryMissingData.length === 0)
{
GetEventDispatcher().dispatchEvent(new SongDiskInventoryReceivedEvent(SongDiskInventoryReceivedEvent.SDIR_SONG_DISK_INVENTORY_RECEIVENT_EVENT));
}
}
private onTick(): void
{
if(this._songRequestList.length === 0) return;
GetCommunication().connection.send(new GetSongInfoMessageComposer(...this._songRequestList));
this._songRequestList = [];
}
private requestSong(songId: number, arg2: boolean): void
{
if(this._requestedSongs.get(songId) === undefined)
{
this._requestedSongs.set(songId, arg2);
this._songRequestList.push(songId);
}
}
private areSamplesRequested(k: number): boolean
{
if(!this._requestedSongs.get(k))
{
return false;
}
return this._requestedSongs.get(k);
}
private processSongEntryForPlaying(k: number, _arg_2: boolean = true): boolean
{
const songData: SongDataEntry = this.getSongDataEntry(k);
if(!songData)
{
this.addSongInfoRequest(k);
return false;
}
/* if(_local_3.soundObject == null)
{
_local_3.soundObject = this._soundManager.loadTraxSong(_local_3.id, _local_3.songData);
}
const _local_4:IHabboSound = _local_3.soundObject;
if(!_local_4.ready)
{
return false;
} */
return true;
}
public playSong(songId: number, priority: number, startPos: number = 0, playLength: number = 0, fadeInSeconds: number = 0.5, fadeOutSeconds: number = 0.5): boolean
{
if(!this.addSongStartRequest(priority, songId, startPos, playLength, fadeInSeconds, fadeOutSeconds))
{
return false;
}
if(!this.processSongEntryForPlaying(songId))
{
return false;
}
if(priority >= this._priorityPlaying)
{
this.playSongObject(priority, songId);
}
return true;
}
private playSongObject(priority: number, songId: number): boolean
{
if((((songId === -1) || (priority < 0)) || (priority >= MusicPriorities.PRIORITY_COUNT)))
{
return false;
}
let _local_3 = false;
if(this.stopSongAtPriority(this._priorityPlaying))
{
_local_3 = true;
}
const songData: SongDataEntry = this.getSongDataEntry(songId);
if(!songData)
{
return false;
}
if(_local_3)
{
return true;
}
this._musicPlayer.setVolume(GetSoundManager().traxVolume);
let startPos = MusicController.SKIP_POSITION_SET;
let playLength = 0;
let fadeInSeconds = 2;
let fadeOutSeconds = 1;
const songRequestData: SongStartRequestData = this.getSongStartRequest(priority);
if(songRequestData)
{
startPos = songRequestData.startPos;
playLength = songRequestData.playLength;
fadeInSeconds = songRequestData.fadeInSeconds;
fadeOutSeconds = songRequestData.fadeOutSeconds;
}
if(startPos >= (songData.length / 1000))
{
return false;
}
if(startPos <= MusicController.SKIP_POSITION_SET)
{
startPos = 0;
}
startPos = Math.trunc(startPos);
/*
_local_5.fadeInSeconds = _local_8;
_local_5.fadeOutSeconds = _local_9;
_local_5.position = _local_6;
_local_5.play(_local_7);
*/
this._priorityPlaying = priority;
this._requestNumberPlaying = this.getSongRequestCountAtPriority(priority);
this._songIdPlaying = songId;
if(this._priorityPlaying <= MusicController.MAXIMUM_NOTIFY_PRIORITY)
{
this.notifySongPlaying(songData);
}
this._musicPlayer.preloadSamplesForSong(songData.songData).then(() => this._musicPlayer.play(songData.songData, songData.id, startPos, playLength));
if(priority > MusicPriorities.PRIORITY_ROOM_PLAYLIST)
{
GetEventDispatcher().dispatchEvent(new NowPlayingEvent(NowPlayingEvent.NPE_USER_PLAY_SONG, priority, songData.id, -1));
}
return true;
}
private notifySongPlaying(k: SongDataEntry): void
{
const _local_2 = 8000;
const timeNow = Date.now();
if(((k.length >= _local_2) && ((!(this._previousNotifiedSongId == k.id)) || (timeNow > (this._previousNotificationTime + _local_2)))))
{
GetEventDispatcher().dispatchEvent(new NotifyPlayedSongEvent(k.name, k.creator));
this._previousNotifiedSongId = k.id;
this._previousNotificationTime = timeNow;
}
}
private addSongStartRequest(priority: number, songId: number, startPos: number, playLength: number, fadeInSeconds: number, fadeOutSeconds: number): boolean
{
if(((priority < 0) || (priority >= MusicPriorities.PRIORITY_COUNT)))
{
return false;
}
const songStartRequest = new SongStartRequestData(songId, startPos, playLength, fadeInSeconds, fadeOutSeconds);
this._songRequestsPerPriority[priority] = songStartRequest;
this._songRequestCountsPerPriority[priority] = (this._songRequestCountsPerPriority[priority] + 1);
return true;
}
private getSongDataEntry(k: number): SongDataEntry
{
let entry: SongDataEntry;
if(this._availableSongs)
{
entry = (this._availableSongs.get(k));
}
return entry;
}
private getSongStartRequest(k: number): SongStartRequestData
{
return this._songRequestsPerPriority[k];
}
private getTopRequestPriority(): number
{
return this._songRequestsPerPriority.length - 1;
}
private getSongIdRequestedAtPriority(priorityIndex: number): number
{
if(priorityIndex < 0 || priorityIndex >= MusicPriorities.PRIORITY_COUNT) return -1;
if(!this._songRequestsPerPriority[priorityIndex]) return -1;
return this._songRequestsPerPriority[priorityIndex].songId;
}
private getSongRequestCountAtPriority(k: number): number
{
if(((k < 0) || (k >= MusicPriorities.PRIORITY_COUNT)))
{
return -1;
}
return this._songRequestCountsPerPriority[k];
}
private playSongWithHighestPriority(): void
{
let _local_3: number;
this._priorityPlaying = -1;
this._songIdPlaying = -1;
this._requestNumberPlaying = -1;
const k = this.getTopRequestPriority();
let _local_2 = k;
while(_local_2 >= 0)
{
_local_3 = this.getSongIdRequestedAtPriority(_local_2);
if(((_local_3 >= 0) && (this.playSongObject(_local_2, _local_3))))
{
return;
}
_local_2--;
}
}
private resetSongStartRequest(priority: number): void
{
if(((priority >= 0) && (priority < MusicPriorities.PRIORITY_COUNT)))
{
this._songRequestsPerPriority[priority] = undefined;
}
}
private reRequestSongAtPriority(k: number): void
{
this._songRequestCountsPerPriority[k] = (this._songRequestCountsPerPriority[k] + 1);
}
private stopSongAtPriority(priority: number): boolean
{
if(((priority === this._priorityPlaying) && (this._priorityPlaying >= 0)))
{
const songIdAtPriority = this.getSongIdPlayingAtPriority(priority);
if(songIdAtPriority >= 0)
{
const songData = this.getSongDataEntry(songIdAtPriority);
//this.stopSongDataEntry(_local_3);
this._musicPlayer.stop();
return true;
}
}
return false;
}
private onSoundMachineInit(k: Event): void
{
this.disposeRoomPlaylist();
//this._roomItemPlaylist = (new SoundMachinePlayListController(this._soundManager, this, this._events) as IPlaylistController);
}
private onSoundMachineDispose(k: Event): void
{
this.disposeRoomPlaylist();
}
private onJukeboxInit(k: Event): void
{
this.disposeRoomPlaylist();
this._roomItemPlaylist = (new JukeboxPlaylistController() as IPlaylistController);
this._roomItemPlaylist.init();
GetCommunication().connection.send(new GetNowPlayingMessageComposer());
}
private onJukeboxDispose(k: Event): void
{
this.disposeRoomPlaylist();
}
private disposeRoomPlaylist(): void
{
if(this._roomItemPlaylist)
{
this._roomItemPlaylist.dispose();
this._roomItemPlaylist = undefined;
}
}
}
+240
View File
@@ -0,0 +1,240 @@
import { GetEventDispatcher, SoundManagerEvent } from '@nitrots/events';
import { NitroLogger } from '@nitrots/utils';
import { Howl, Howler } from 'howler';
import { TraxData } from '../trax/TraxData';
export class MusicPlayer
{
private _currentSong: TraxData | undefined;
private _currentSongId: number;
private _startPos: number;
private _playLength: number;
private _isPlaying: boolean;
private _currentPos: number;
private _cache: Map<number, Howl>;
private _sampleUrl: string;
private _tickerInterval: number | undefined;
private _sequence: ISequenceEntry[][];
constructor(sampleUrl: string)
{
this._sampleUrl = sampleUrl;
this._isPlaying = false;
this._startPos = 0;
this._currentPos = 0;
this._playLength = 0;
this._sequence = [];
this._cache = new Map<number, Howl>();
}
public async play(song: string, currentSongId: number, startPos: number = 0, playLength: number = -1): Promise<void>
{
this.reset();
this._currentSong = new TraxData(song);
this._startPos = Math.trunc(startPos);
this._playLength = playLength;
this._currentPos = this._startPos;
this._currentSongId = currentSongId;
//this.emit('loading');
await this.preload();
this._isPlaying = true;
//this.emit('playing', this._currentPos, this._playLength - 1);
this.tick(); // to evade initial 1 sec delay
this._tickerInterval = window.setInterval(() => this.tick(), 1000);
}
private reset(): void
{
this._isPlaying = false;
window.clearInterval(this._tickerInterval);
Howler.stop();
this._currentSongId = -1;
this._currentSong = undefined;
this._tickerInterval = undefined;
this._startPos = 0;
this._playLength = 0;
this._sequence = [];
this._currentPos = 0;
}
public pause(): void
{
this._isPlaying = false;
//this.emit('paused', this._currentPos);
Howler.stop();
}
public resume(): void
{
this._isPlaying = true;
//this.emit('playing', this._currentPos, this._playLength - 1 );
}
public stop(): void
{
const songId = this._currentSongId;
this.reset();
GetEventDispatcher().dispatchEvent(new SoundManagerEvent(SoundManagerEvent.TRAX_SONG_COMPLETE, songId));
//this.emit('stopped');
}
/**
* Sets global howler volume for all sounds
* @param volume value from 0.0 to 1.0
*/
public setVolume(volume: number): void
{
Howler.volume(volume);
}
/**
* Gets global howler volume for all sounds
* @returns value from 0.0 to 1.0
*/
public getVolume(): number
{
return Howler.volume();
}
/**
* Gets sample from cache or loads it if not in cache
* @param id sample id
* @returns howl sound object
*/
public async getSample(id: number): Promise<Howl>
{
let sample = this._cache.get(id);
if(!sample) sample = await this.loadSong(id);
return Promise.resolve(sample);
}
private async preload(): Promise<void>
{
this._sequence = [];
if(!this._currentSong) return;
for(const channel of this._currentSong.channels)
{
const sequenceEntryArray: ISequenceEntry[] = [];
for(const sample of channel.items)
{
const sampleSound = await this.getSample(sample.id);
const sampleCount = Math.ceil((sample.length * 2) / Math.ceil(sampleSound.duration()));
for(let i = 0; i < sampleCount; i++)
{
for(let j = 0; j < Math.ceil(sampleSound.duration()); j++)
{
sequenceEntryArray.push({ sampleId: sample.id, offset: j });
}
}
}
this._sequence.push(sequenceEntryArray);
}
if(this._playLength <= 0) this._playLength = Math.max(...this._sequence.map((value: ISequenceEntry[]) => value.length));
}
public async preloadSamplesForSong(song: string): Promise<void>
{
const traxData = new TraxData(song);
await Promise.all(traxData.getSampleIds().map(id => this.getSample(id)));
}
private async loadSong(songId: number): Promise<Howl>
{
return new Promise<Howl>((resolve, reject) =>
{
const sample = new Howl({
src: [this._sampleUrl.replace('%sample%', songId.toString())],
preload: true,
});
sample.once('load', () =>
{
this._cache.set(songId, sample);
resolve(sample);
});
sample.once('loaderror', () =>
{
NitroLogger.error('failed to load sample ' + songId);
reject('failed to load sample ' + songId);
});
});
}
private tick(): void
{
if(this._currentPos > this._playLength - 1)
{
this.stop();
}
if(this._isPlaying)
{
if(this._currentSong)
{
//this.emit('time', this._currentPos);
this.playPosition(this._currentPos);
}
this._currentPos++;
}
}
private playPosition(pos: number): void
{
if(!this._currentSong || !this._sequence) return;
//@ts-ignore
if(!Howler._audioUnlocked)
{
//console.log('skipping due to locked audio');
return;
}
for(const sequencyEntry of this._sequence)
{
const entry = sequencyEntry[pos];
if(!entry) continue;
// sample -1 is play none
// sample 0 is 1 second of empty noise
if(entry.sampleId === -1 || entry.sampleId === 0) continue;
const sampleAudio = this._cache.get(entry.sampleId);
if(!sampleAudio) continue;
if(entry.offset === 0)
{
sampleAudio.play();
}
else if(!sampleAudio.playing())
{
sampleAudio.seek(entry.offset);
sampleAudio.play();
}
}
}
}
interface ISequenceEntry
{
sampleId: number;
offset: number;
}
@@ -0,0 +1,8 @@
export class MusicPriorities
{
public static readonly PRIORITY_ROOM_PLAYLIST: number = 0;
public static readonly PRIORITY_USER_PLAYLIST: number = 1;
public static readonly PRIORITY_SONG_PLAY: number = 2;
public static readonly PRIORITY_PURCHASE_PREVIEW: number = 3;
public static readonly PRIORITY_COUNT: number = 4;
}
+3
View File
@@ -0,0 +1,3 @@
export * from './JukeboxPlaylistController';
export * from './MusicController';
export * from './MusicPriorities';