From 4223a173414e6f21c103dbb2fad91c22275045a7 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Fri, 11 Nov 2022 02:43:30 +0100 Subject: [PATCH] play song and add data to map item --- .../local-maps-list-panel.component.tsx | 6 +- .../map-item.component.tsx | 61 ++++++++++++++++--- .../nav-bar/bsmanager-icon.component.tsx | 27 +++++++- src/renderer/services/audio-player.service.ts | 58 ++++++++++++++++++ 4 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 src/renderer/services/audio-player.service.ts diff --git a/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx b/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx index 93cffab7..85d639bc 100644 --- a/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx +++ b/src/renderer/components/maps-mangement-components/local-maps-list-panel.component.tsx @@ -47,14 +47,14 @@ export function LocalMapsListPanel({version, className} : Props) { return {}} />; } diff --git a/src/renderer/components/maps-mangement-components/map-item.component.tsx b/src/renderer/components/maps-mangement-components/map-item.component.tsx index a627b627..b68515b5 100644 --- a/src/renderer/components/maps-mangement-components/map-item.component.tsx +++ b/src/renderer/components/maps-mangement-components/map-item.component.tsx @@ -4,10 +4,13 @@ import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; import { BsmLink } from "../shared/bsm-link.component"; import { BsmIcon } from "../svgs/bsm-icon.component"; import { BsmButton } from "../shared/bsm-button.component"; -import { animate, motion } from "framer-motion"; +import { motion } from "framer-motion"; import { useState } from "react"; import { LinkOpenerService } from "renderer/services/link-opener.service"; import dateFormat from "dateformat"; +import { AudioPlayerService } from "renderer/services/audio-player.service"; +import { useObservable } from "renderer/hooks/use-observable.hook"; +import { map } from "rxjs/operators"; export type MapItemProps = { hash: string, @@ -24,25 +27,37 @@ export type MapItemProps = { bpm: number, duration: number, likes: number, - plays: number, createdAt: string, onDelete?: (hash: string) => void, onDownload?: (id: string) => void, } -export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autorId, mapId, diffs, qualified, ranked, bpm, duration, likes, plays, createdAt, onDelete, onDownload}: MapItemProps) { +export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autorId, mapId, diffs, qualified, ranked, bpm, duration, likes, createdAt, onDelete, onDownload}: MapItemProps) { const linkOpener = LinkOpenerService.getInstance(); + const audioPlayer = AudioPlayerService.getInstance(); const color = useThemeColor("first-color"); const [hovered, setHovered] = useState(false); + const songPlaying = useObservable(audioPlayer.playing$.pipe(map(playing => playing && audioPlayer.src === songUrl ? true : false))); + const zipUrl = `https://r2cdn.beatsaver.com/${hash}.zip`; const previewUrl = `https://skystudioapps.com/bs-viewer/?url=${zipUrl}`; const mapUrl = mapId ? `https://beatsaver.com/maps/${mapId}` : null; const authorUrl = autorId ? `https://beatsaver.com/profile/${autorId}` : null; const createdDate = createdAt ? dateFormat(createdAt, "d mmm yyyy") : null; + const likesText = likes ? Intl.NumberFormat(undefined, {notation: "compact"}).format(likes).split(" ").join("") : null; + + const durationText = (() => { + if(!duration){ return null; } + const date = new Date(0); + date.setSeconds(duration); + return duration > 3600 ? dateFormat(date, "h:MM:ss") : dateFormat(date, "MM:ss"); + })() + + console.log(hash); const openMapPage = () => { if(mapUrl){ @@ -52,23 +67,51 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor const openPreview = () => linkOpener.open(previewUrl, true); const copyBsr = () => navigator.clipboard.writeText(`!bsr ${mapId}`); - const playSong = () => new Audio(songUrl).play(); + const toogleMusic = () => { + if(songPlaying){ + return audioPlayer.pause(); + } + if(!audioPlayer.playing && audioPlayer.src === songUrl){ + return audioPlayer.resume(); + } + audioPlayer.play(songUrl, bpm); + } return ( setHovered(true)} onHoverEnd={() => setHovered(false)} onClick={openMapPage}> -
-
+
+
- {e.stopPropagation(); playSong()}}> + {e.stopPropagation(); toogleMusic()}}>
-
+

{title}

-

par {songAutor}

+

par {songAutor}

mappée par {autor}

+
+ {likesText && ( +
+ + {likesText} +
+ )} + {durationText && ( +
+ + {durationText} +
+ )} + {createdAt && ( +
+ + +
+ )} +
diff --git a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx index f938016d..d914eeea 100644 --- a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx +++ b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx @@ -1,13 +1,36 @@ import { memo } from "react" import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; -import { motion } from "framer-motion"; +import { motion, Variants } from "framer-motion"; +import { useObservable } from "renderer/hooks/use-observable.hook"; +import { AudioPlayerService } from "renderer/services/audio-player.service"; export const BsManagerIcon = memo(({className}: {className?: string}) => { + const audioPlayer = AudioPlayerService.getInstance(); + const {firstColor, secondColor} = useThemeColor(); + const playing= useObservable(audioPlayer.playing$); + + const bpm = audioPlayer.bpm; + + console.log("bpm", bpm, playing); + + const transitions: Variants = { + playing: { + scale: [1, 1.05, 1], + transition: {repeat: Infinity, duration: ((60/bpm))/2, repeatDelay: ((60/bpm))/2} + }, + idle: {} + } + + const clickAction = () => { + if(playing){ + audioPlayer.pause(); + } + } return ( - + 0 ? "playing" : "idle"} onClick={clickAction}> diff --git a/src/renderer/services/audio-player.service.ts b/src/renderer/services/audio-player.service.ts new file mode 100644 index 00000000..753d4679 --- /dev/null +++ b/src/renderer/services/audio-player.service.ts @@ -0,0 +1,58 @@ +import { Observable } from "rxjs"; +import { BehaviorSubject } from "rxjs"; + +export class AudioPlayerService{ + + private static instance: AudioPlayerService; + + public static getInstance(): AudioPlayerService{ + if(!AudioPlayerService.instance){ AudioPlayerService.instance = new AudioPlayerService(); } + return AudioPlayerService.instance; + } + + private readonly player: HTMLAudioElement; + + private readonly _src$: BehaviorSubject = new BehaviorSubject(""); + private readonly _playing$: BehaviorSubject = new BehaviorSubject(false); + private readonly _bpm$: BehaviorSubject = new BehaviorSubject(0); + + private constructor(){ + 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); + } + + public play(src: string, bpm = 0): Promise{ + this.pause(); + this.player.src = src; + this._src$.next(src); + this._bpm$.next(bpm) + return this.player.play() + } + + public pause(): void{ + this._playing$.next(false); + this.player.pause(); + } + + public resume(): Promise{ + return this.player.play(); + } + + public get src$(): Observable{ + return this._src$.asObservable(); + } + public get playing$(): Observable{ + return this._playing$.asObservable(); + } + public get bpm$(): Observable{ + return this._bpm$.asObservable(); + } + + public get src(): string{ return this._src$.value; } + public get playing(): boolean{ return this._playing$.value; } + public get bpm(): number{ return this._bpm$.value; } + +} \ No newline at end of file