From 501def3a9479c1bd4df37420cc3ce160a9b02a27 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:21:05 +0100 Subject: [PATCH] [feature-107] Audio player can now take list of songs to play --- assets/jsons/bs-versions.json | 4 +- package.json | 2 +- .../maps/map-item.component.tsx | 2 +- ...local-playlist-details-modal.component.tsx | 59 +++++---- src/renderer/services/audio-player.service.ts | 120 ++++++++++-------- 5 files changed, 102 insertions(+), 85 deletions(-) diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index 6484682a..719425bf 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -700,7 +700,7 @@ "ReleaseDate": "1708091473", "year": "2024" }, - { + { "BSVersion": "1.35.0", "BSManifest": "1490986193481243578", "OculusBinaryId": "6720809361352119", @@ -709,4 +709,4 @@ "ReleaseDate": "1709831174", "year": "2024" } -] +] \ No newline at end of file diff --git a/package.json b/package.json index 02badae0..b56ab474 100644 --- a/package.json +++ b/package.json @@ -170,8 +170,8 @@ "@types/color": "^3.0.3", "@types/crypto-js": "^4.2.1", "@types/dateformat": "^5.0.0", - "@types/got": "^9.6.12", "@types/dompurify": "^3.0.5", + "@types/got": "^9.6.12", "@types/jest": "^29.5.11", "@types/node": "20.11.15", "@types/node-fetch": "^2.6.3", diff --git a/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx b/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx index 48ba662b..213157eb 100644 --- a/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps/map-item.component.tsx @@ -109,7 +109,7 @@ export const MapItem = memo(({ hash, title, autor, songAutor, coverUrl, songUrl, if (!audioPlayer.playing && audioPlayer.src === songUrl) { return audioPlayer.resume(); } - audioPlayer.play(songUrl, bpm); + audioPlayer.play([{ src: songUrl, bpm }]); }; const bottomBarHoverStart = () => { diff --git a/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx b/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx index 39c9a2e5..87eed38d 100644 --- a/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/playlist/local-playlist-details-modal.component.tsx @@ -6,6 +6,8 @@ import { BSVersion } from "shared/bs-version.interface"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { MapItem } from "renderer/components/maps-playlists-panel/maps/map-item.component"; import { extractMapDiffs } from "renderer/components/maps-playlists-panel/maps/maps-row.component"; +import { useService } from "renderer/hooks/use-service.hook"; +import { AudioPlayerService } from "renderer/services/audio-player.service"; interface Props extends Omit { version: BSVersion @@ -14,39 +16,46 @@ interface Props extends Omit { export const LocalPlaylistDetailsModal: ModalComponent = ({resolver, options}) => { + const audioPlayer = useService(AudioPlayerService); + const installedMaps = useObservable(() => options.data.installedMaps$, undefined); - console.log(installedMaps); + const playPlaylist = () => { + if (!installedMaps) { + return; + } + audioPlayer.play(installedMaps.map(map => ({ src: map.songUrl, bpm: map.rawInfo?._beatsPerMinute ?? 0}))); + }; const renderMaps = () => { if (!installedMaps) { return null; } - return ( -
    - {installedMaps.map(map => ( - - ))} -
- ) + return ( +
    + {installedMaps.map(map => ( + + ))} +
+ ) } return ( diff --git a/src/renderer/services/audio-player.service.ts b/src/renderer/services/audio-player.service.ts index 2d7fec72..42ad7708 100644 --- a/src/renderer/services/audio-player.service.ts +++ b/src/renderer/services/audio-player.service.ts @@ -1,5 +1,15 @@ -import { Observable, BehaviorSubject } from "rxjs"; -import { ConfigurationService } from "./configuration.service"; +import { BehaviorSubject, Observable, map } from 'rxjs'; +import { ConfigurationService } from './configuration.service'; + +interface PlayerVolume { + volume: number; + muted: boolean; +} + +interface PlayerSound { + src: string; + bpm?: number; +} export class AudioPlayerService { private static instance: AudioPlayerService; @@ -11,52 +21,50 @@ export class AudioPlayerService { return AudioPlayerService.instance; } - private readonly config: ConfigurationService; + private player = new Audio(); + private config = ConfigurationService.getInstance(); - private readonly player: HTMLAudioElement; + private _soundsIndex$ = new BehaviorSubject(0); + private _sounds$ = new BehaviorSubject([]); + private _currentSound$ = this._soundsIndex$.pipe( + map(index => this._sounds$.value[index]), + ); - private readonly _src$: BehaviorSubject = new BehaviorSubject(""); - private readonly _playing$: BehaviorSubject = new BehaviorSubject(false); - private readonly _bpm$: BehaviorSubject = new BehaviorSubject(0); - private readonly _volume$: BehaviorSubject; - - private lastVolume: number; + private _playing$ = new BehaviorSubject(false); + private _bpm$ = new BehaviorSubject(0); + private _volume$ = new BehaviorSubject( + this.config.get('audio-level') || { volume: 0.5, muted: false }, + ); private constructor() { - this.config = ConfigurationService.getInstance(); - - this._volume$ = new BehaviorSubject(this.config.get("audio-level") || { volume: 0.5, muted: false }); - - this.lastVolume = this._volume$.value.volume; - - this.player = new Audio(); - this.player.onplay = () => this._playing$.next(true); this.player.onpause = () => this._playing$.next(false); - this.player.onended = () => this._playing$.next(false); + this.player.onended = () => { + this._playing$.next(false); + const nextIndex = (this._soundsIndex$.value + 1) % this._sounds$.value.length; + if (nextIndex !== 0 || this._sounds$.value.length > 1) { + this._soundsIndex$.next(nextIndex); + } + }; + + this._currentSound$.subscribe(sound => { + if(!sound){ return; } + this.player.src = sound.src; + this._bpm$.next(sound.bpm || 0); + this.player.play().catch(error => console.error('Error playing sound:', error)); + }); this._volume$.subscribe(volume => { this.player.volume = volume.volume; this.player.muted = volume.muted; - this.config.set("audio-level", volume); + this.config.set('audio-level', volume); }); } - public play(src: string, bpm = 0): Promise { + public play(sounds: PlayerSound[]): void { this.pause(); - this.player.src = src; - this._src$.next(src); - this._bpm$.next(bpm); - return this.player.play(); - } - - public playlist(songs: {src: string, bpm: number}[], index: number): void { - this.play(songs[index].src, songs[index].bpm); - this.player.onended = () => { - if (index < songs.length - 1) { - this.playlist(songs, index + 1); - } - }; + this._sounds$.next(sounds); + this._soundsIndex$.next(0); // Ensures we start from the first sound } public pause(): void { @@ -65,7 +73,13 @@ export class AudioPlayerService { } public resume(): Promise { - return this.player.play(); + if(this._playing$.value){ + return Promise.resolve(); + } + + return this.player.play().then(() => { + this._playing$.next(true); + }).catch(error => console.error('Error resuming sound:', error)); } public setVolume(volume: number): void { @@ -73,14 +87,6 @@ export class AudioPlayerService { this._volume$.next(playerVolume); } - public setFinalVolume(volume: number): void { - if (volume > 0) { - this.lastVolume = volume; - } - const playerVolume: PlayerVolume = { muted: volume <= 0, volume: this.lastVolume }; - this._volume$.next(playerVolume); - } - public mute(): void { const playerVolume = { ...this._volume$.value, muted: true }; this._volume$.next(playerVolume); @@ -92,46 +98,48 @@ export class AudioPlayerService { } public toggleMute(): void { - if(this.muted){ - return this.unmute(); + const isMuted = this._volume$.value.muted; + if (isMuted) { + this.unmute(); + } else { + this.mute(); } - this.mute(); } + // Getter methods to expose Observables for external use public get src$(): Observable { - return this._src$.asObservable(); + return this._currentSound$.pipe(map(sound => sound?.src || '')); } + public get playing$(): Observable { return this._playing$.asObservable(); } + public get bpm$(): Observable { return this._bpm$.asObservable(); } + public get volume$(): Observable { return this._volume$.asObservable(); } public get src(): string { - return this._src$.value; + return this.player.src; } + public get playing(): boolean { return this._playing$.value; } + public get bpm(): number { return this._bpm$.value; } + public get volume(): PlayerVolume { return this._volume$.value; } + public get muted(): boolean { return this.player.muted; } - public get paused(): boolean { - return this.player.paused; - } -} - -interface PlayerVolume { - volume: number; - muted: boolean; }