fix: Added close button for audio player

This commit is contained in:
florian 2024-05-25 14:29:37 +02:00
parent 03134c37d6
commit 895dda9258
7 changed files with 65 additions and 31 deletions

View File

@ -16,7 +16,11 @@ const initialState: State = {
songs: [],
};
type Action = { type: 'SET_CURRENT_SONG'; song: Song } | { type: 'SHUFFLE_SONGS' } | { type: 'ADD_SONG'; song: Song };
type Action =
| { type: 'SET_CURRENT_SONG'; song: Song }
| { type: 'SHUFFLE_SONGS' }
| { type: 'RESET_CURRENT_SONG' }
| { type: 'ADD_SONG'; song: Song };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
@ -26,6 +30,8 @@ const reducer = (state: State, action: Action): State => {
return { ...state, songs: [...state.songs].sort(() => Math.random() - 0.5) };
case 'ADD_SONG':
return { ...state, songs: [...state.songs, action.song] };
case 'RESET_CURRENT_SONG':
return { ...state, currentSong: undefined };
default:
return state;
}

View File

@ -1,15 +1,13 @@
import React, { useState, useRef, useEffect } from 'react';
import { useGlobalContext } from '../GlobalState';
import { PauseIcon, PlayIcon, SpeakerWaveIcon, SpeakerXMarkIcon } from '@heroicons/react/24/outline';
import { PauseIcon, PlayIcon, SpeakerWaveIcon, SpeakerXMarkIcon, XMarkIcon } from '@heroicons/react/24/outline';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
dayjs.extend(duration);
const AudioPlayer: React.FC = () => {
const { state } = useGlobalContext();
const { state, dispatch } = useGlobalContext();
const { currentSong } = state;
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(1);
@ -25,9 +23,19 @@ const AudioPlayer: React.FC = () => {
setProgress(((audio.currentTime || 0) / audio.duration) * 100);
}
};
audio.addEventListener('timeupdate', updateProgress);
const updateProgressFrame = () => {
updateProgress();
requestAnimationFrame(updateProgressFrame);
};
audio.addEventListener('play', updateProgressFrame);
audio.addEventListener('pause', updateProgressFrame);
requestAnimationFrame(updateProgressFrame);
return () => {
audio.removeEventListener('timeupdate', updateProgress);
audio.removeEventListener('play', updateProgressFrame);
audio.removeEventListener('pause', updateProgressFrame);
};
}
}, [currentSong]);
@ -61,36 +69,44 @@ const AudioPlayer: React.FC = () => {
tuneVolume(newVolume);
};
const resetPlayer = () => {
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
setIsPlaying(false);
setProgress(0);
dispatch({ type: 'RESET_CURRENT_SONG' }); // Assuming this resets the current song in global state
};
return (
currentSong && (
<div className="audio-player flex items-center space-x-4">
<div className="audio-player flex items-center space-x-4 w-full">
<audio ref={audioRef} />
{/*currentSong && <span className="font-semibold">{currentSong}</span>
*/}
<button className="btn btn-icon" onClick={playPause}>
<button className="btn btn-icon" onClick={playPause}>
{isPlaying ? <PauseIcon className="h-6 w-6" /> : <PlayIcon className="h-6 w-6" />}
</button>
<span className="w-10 hidden md:block">
{' '}
{dayjs.duration(audioRef.current?.currentTime || 0, 'seconds').format('m:ss')}
</span>
<div className="flex-grow w-60 hidden md:block cursor-pointer">
<div className="flex-grow w-60 hidden md:block">
<input
type="range"
min="0"
max="100"
value={progress}
onChange={e =>
audioRef.current &&
(audioRef.current.currentTime = (parseInt(e.target.value) / 100) * audioRef.current.duration)
}
className="progress progress-primary w-full"
onChange={e => {
if (audioRef.current) {
audioRef.current.currentTime = (parseInt(e.target.value) / 100) * audioRef.current.duration;
}
}}
className="progress progress-primary w-full cursor-pointer"
/>
</div>
<div className="flex items-center space-x-2 cursor-pointer">
{volume == 0 ? (
<div className="flex items-center space-x-2" >
{volume === 0 ? (
<SpeakerXMarkIcon
className="h-6 w-6 text-gray-500"
onClick={() => {
@ -114,20 +130,25 @@ const AudioPlayer: React.FC = () => {
step="0.01"
value={volume}
onChange={changeVolume}
className="progress progress-primary"
className="progress progress-primary cursor-pointer"
/>
</div>
{currentSong.id3 && (
<>
<div>
<img className="w-12 h-12" src={currentSong.id3?.cover}></img>
<img className="w-12 h-12" src={currentSong.id3.cover} alt={currentSong.id3.title} />
</div>
<div className="flex flex-col text-sm">
<div className="text-white">{currentSong?.id3.title}</div>
<div>{currentSong?.id3.artist}</div>
<div className="text-white">{currentSong.id3.title}</div>
<div>{currentSong.id3.artist}</div>
</div>
</>
)}
<button className="btn btn-icon ml-auto" onClick={resetPlayer}>
<XMarkIcon className="h-6 w-6 text-gray-500" />
</button>
</div>
)
);

View File

@ -6,10 +6,12 @@ import { useEffect } from 'react';
import ThemeSwitcher from '../ThemeSwitcher';
import AudioPlayer from '../AudioPlayer';
import BottomNavbar from '../BottomNavBar/BottomNavBar';
import { useGlobalContext } from '../../GlobalState';
export const Layout = () => {
const navigate = useNavigate();
const { loginWithExtension, user } = useNDK();
const { state } = useGlobalContext();
useEffect(() => {
if (!user) loginWithExtension();
@ -69,9 +71,11 @@ export const Layout = () => {
</div>
<div className="content">{<Outlet />}</div>
<BottomNavbar>
<AudioPlayer />
</BottomNavbar>
{state.currentSong && (
<BottomNavbar>
<AudioPlayer />
</BottomNavbar>
)}
<div className="footer">
<span className="whitespace-nowrap block">
made with 💜 by{' '}

View File

@ -8,6 +8,7 @@ import { useMemo, useState } from 'react';
import { useNDK } from '../../utils/ndk';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import dayjs from 'dayjs';
import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk';
type ServerListProps = {
servers: ServerInfo[];
@ -49,7 +50,7 @@ export const ServerList = ({
const handleSaveServers = async (newServers: ServerType[]) => {
const ev = new NDKEvent(ndk, {
kind: 10063,
kind: USER_BLOSSOM_SERVER_LIST_KIND,
created_at: dayjs().unix(),
content: '',
pubkey: user?.pubkey || '',

View File

@ -4,7 +4,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
import { useNDK } from '../utils/ndk';
import BlobList from '../components/BlobList/BlobList';
import { ServerInfo, useServerInfo } from '../utils/useServerInfo';
import { useServerInfo } from '../utils/useServerInfo';
import { ServerList } from '../components/ServerList/ServerList';
import { useNavigate } from 'react-router-dom';

View File

@ -4,8 +4,9 @@ import { NDKKind } from '@nostr-dev-kit/ndk';
import countBy from 'lodash/countBy';
import sortBy from 'lodash/sortBy';
import toPairs from 'lodash/toPairs';
import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk';
const blossomServerListFilter = { kinds: [10063 as NDKKind] };
const blossomServerListFilter = { kinds: [USER_BLOSSOM_SERVER_LIST_KIND as NDKKind] };
const useBlossomServerEvents = () => {
const blossomServerEvents = useEvents(blossomServerListFilter);

View File

@ -3,6 +3,7 @@ import { useNDK } from '../utils/ndk';
import { nip19 } from 'nostr-tools';
import { NDKKind } from '@nostr-dev-kit/ndk';
import useEvent from '../utils/useEvent';
import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk';
export type Server = {
name: string;
@ -14,7 +15,7 @@ export const useUserServers = (): Server[] => {
const pubkey = user?.npub && (nip19.decode(user?.npub).data as string); // TODO validate type
const serverListEvent = useEvent({ kinds: [10063 as NDKKind], authors: [pubkey!] }, { disable: !pubkey });
const serverListEvent = useEvent({ kinds: [USER_BLOSSOM_SERVER_LIST_KIND as NDKKind], authors: [pubkey!] }, { disable: !pubkey });
const servers = useMemo(() => {
const serverUrls = (serverListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s =>