mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] Audio player can now take list of songs to play
This commit is contained in:
@@ -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
@@ -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 = () => {
|
||||
|
||||
+34
-25
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user