You've already forked Nitro_Render_V3
mirror of
https://github.com/duckietm/Nitro_Render_V3.git
synced 2026-06-20 15:36:18 +00:00
Move to Renderer V2
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './JukeboxPlaylistController';
|
||||
export * from './MusicController';
|
||||
export * from './MusicPriorities';
|
||||
Reference in New Issue
Block a user