[feature-107] Audio player can now take list of songs to play

This commit is contained in:
MathieuG-P
2024-03-08 18:21:05 +01:00
parent f2cd9a1174
commit 501def3a94
5 changed files with 102 additions and 85 deletions
+2 -2
View File
@@ -700,7 +700,7 @@
"ReleaseDate": "1708091473",
"year": "2024"
},
{
{
"BSVersion": "1.35.0",
"BSManifest": "1490986193481243578",
"OculusBinaryId": "6720809361352119",
@@ -709,4 +709,4 @@
"ReleaseDate": "1709831174",
"year": "2024"
}
]
]
+1 -1
View File
@@ -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",
@@ -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 = () => {
@@ -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<PlaylistDetailsTemplateProps, "children"> {
version: BSVersion
@@ -14,39 +16,46 @@ interface Props extends Omit<PlaylistDetailsTemplateProps, "children"> {
export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({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 (
<ul className="space-y-2 overflow-y-scroll pr-2 pl-2.5 pt-2 pb-5 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
{installedMaps.map(map => (
<MapItem
key={map.path}
hash={map.hash}
title={map.rawInfo._songName}
coverUrl={map.coverUrl}
songUrl={map.songUrl}
autor={map.rawInfo._levelAuthorName}
songAutor={map.rawInfo._songAuthorName}
bpm={map.rawInfo._beatsPerMinute}
duration={map.songDetails?.metadata.duration}
diffs={extractMapDiffs({ rawMapInfo: map.rawInfo, songDetails: map.songDetails })}
mapId={map.songDetails?.id}
ranked={map.songDetails?.ranked}
autorId={map.songDetails?.uploader.id}
likes={map.songDetails?.upVotes}
createdAt={map.songDetails?.uploadedAt}
callBackParam={null}
/>
))}
</ul>
)
return (
<ul className="space-y-2 overflow-y-scroll pr-2 pl-2.5 pt-2 pb-5 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
{installedMaps.map(map => (
<MapItem
key={map.path}
hash={map.hash}
title={map.rawInfo._songName}
coverUrl={map.coverUrl}
songUrl={map.songUrl}
autor={map.rawInfo._levelAuthorName}
songAutor={map.rawInfo._songAuthorName}
bpm={map.rawInfo._beatsPerMinute}
duration={map.songDetails?.metadata.duration}
diffs={extractMapDiffs({ rawMapInfo: map.rawInfo, songDetails: map.songDetails })}
mapId={map.songDetails?.id}
ranked={map.songDetails?.ranked}
autorId={map.songDetails?.uploader.id}
likes={map.songDetails?.upVotes}
createdAt={map.songDetails?.uploadedAt}
callBackParam={null}
/>
))}
</ul>
)
}
return (
+64 -56
View File
@@ -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<number>(0);
private _sounds$ = new BehaviorSubject<PlayerSound[]>([]);
private _currentSound$ = this._soundsIndex$.pipe(
map(index => this._sounds$.value[index]),
);
private readonly _src$: BehaviorSubject<string> = new BehaviorSubject("");
private readonly _playing$: BehaviorSubject<boolean> = new BehaviorSubject(false);
private readonly _bpm$: BehaviorSubject<number> = new BehaviorSubject(0);
private readonly _volume$: BehaviorSubject<PlayerVolume>;
private lastVolume: number;
private _playing$ = new BehaviorSubject<boolean>(false);
private _bpm$ = new BehaviorSubject<number>(0);
private _volume$ = new BehaviorSubject<PlayerVolume>(
this.config.get<PlayerVolume>('audio-level') || { volume: 0.5, muted: false },
);
private constructor() {
this.config = ConfigurationService.getInstance();
this._volume$ = new BehaviorSubject(this.config.get<PlayerVolume>("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<void> {
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<void> {
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<string> {
return this._src$.asObservable();
return this._currentSound$.pipe(map(sound => sound?.src || ''));
}
public get playing$(): Observable<boolean> {
return this._playing$.asObservable();
}
public get bpm$(): Observable<number> {
return this._bpm$.asObservable();
}
public get volume$(): Observable<PlayerVolume> {
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;
}