fix: Added close button for audio player
This commit is contained in:
parent
03134c37d6
commit
895dda9258
@ -16,7 +16,11 @@ const initialState: State = {
|
|||||||
songs: [],
|
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 => {
|
const reducer = (state: State, action: Action): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -26,6 +30,8 @@ const reducer = (state: State, action: Action): State => {
|
|||||||
return { ...state, songs: [...state.songs].sort(() => Math.random() - 0.5) };
|
return { ...state, songs: [...state.songs].sort(() => Math.random() - 0.5) };
|
||||||
case 'ADD_SONG':
|
case 'ADD_SONG':
|
||||||
return { ...state, songs: [...state.songs, action.song] };
|
return { ...state, songs: [...state.songs, action.song] };
|
||||||
|
case 'RESET_CURRENT_SONG':
|
||||||
|
return { ...state, currentSong: undefined };
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
import { useGlobalContext } from '../GlobalState';
|
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 dayjs from 'dayjs';
|
||||||
import duration from 'dayjs/plugin/duration';
|
import duration from 'dayjs/plugin/duration';
|
||||||
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
|
||||||
const AudioPlayer: React.FC = () => {
|
const AudioPlayer: React.FC = () => {
|
||||||
const { state } = useGlobalContext();
|
const { state, dispatch } = useGlobalContext();
|
||||||
const { currentSong } = state;
|
const { currentSong } = state;
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [volume, setVolume] = useState(1);
|
const [volume, setVolume] = useState(1);
|
||||||
@ -25,9 +23,19 @@ const AudioPlayer: React.FC = () => {
|
|||||||
setProgress(((audio.currentTime || 0) / audio.duration) * 100);
|
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 () => {
|
return () => {
|
||||||
audio.removeEventListener('timeupdate', updateProgress);
|
audio.removeEventListener('play', updateProgressFrame);
|
||||||
|
audio.removeEventListener('pause', updateProgressFrame);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [currentSong]);
|
}, [currentSong]);
|
||||||
@ -61,36 +69,44 @@ const AudioPlayer: React.FC = () => {
|
|||||||
tuneVolume(newVolume);
|
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 (
|
return (
|
||||||
currentSong && (
|
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} />
|
<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" />}
|
{isPlaying ? <PauseIcon className="h-6 w-6" /> : <PlayIcon className="h-6 w-6" />}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span className="w-10 hidden md:block">
|
<span className="w-10 hidden md:block">
|
||||||
{' '}
|
|
||||||
{dayjs.duration(audioRef.current?.currentTime || 0, 'seconds').format('m:ss')}
|
{dayjs.duration(audioRef.current?.currentTime || 0, 'seconds').format('m:ss')}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex-grow w-60 hidden md:block cursor-pointer">
|
<div className="flex-grow w-60 hidden md:block">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={progress}
|
value={progress}
|
||||||
onChange={e =>
|
onChange={e => {
|
||||||
audioRef.current &&
|
if (audioRef.current) {
|
||||||
(audioRef.current.currentTime = (parseInt(e.target.value) / 100) * audioRef.current.duration)
|
audioRef.current.currentTime = (parseInt(e.target.value) / 100) * audioRef.current.duration;
|
||||||
}
|
}
|
||||||
className="progress progress-primary w-full"
|
}}
|
||||||
|
className="progress progress-primary w-full cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center space-x-2 cursor-pointer">
|
<div className="flex items-center space-x-2" >
|
||||||
{volume == 0 ? (
|
{volume === 0 ? (
|
||||||
<SpeakerXMarkIcon
|
<SpeakerXMarkIcon
|
||||||
className="h-6 w-6 text-gray-500"
|
className="h-6 w-6 text-gray-500"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -114,20 +130,25 @@ const AudioPlayer: React.FC = () => {
|
|||||||
step="0.01"
|
step="0.01"
|
||||||
value={volume}
|
value={volume}
|
||||||
onChange={changeVolume}
|
onChange={changeVolume}
|
||||||
className="progress progress-primary"
|
className="progress progress-primary cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{currentSong.id3 && (
|
{currentSong.id3 && (
|
||||||
<>
|
<>
|
||||||
<div>
|
<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>
|
||||||
<div className="flex flex-col text-sm">
|
<div className="flex flex-col text-sm">
|
||||||
<div className="text-white">{currentSong?.id3.title}</div>
|
<div className="text-white">{currentSong.id3.title}</div>
|
||||||
<div>{currentSong?.id3.artist}</div>
|
<div>{currentSong.id3.artist}</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<button className="btn btn-icon ml-auto" onClick={resetPlayer}>
|
||||||
|
<XMarkIcon className="h-6 w-6 text-gray-500" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -6,10 +6,12 @@ import { useEffect } from 'react';
|
|||||||
import ThemeSwitcher from '../ThemeSwitcher';
|
import ThemeSwitcher from '../ThemeSwitcher';
|
||||||
import AudioPlayer from '../AudioPlayer';
|
import AudioPlayer from '../AudioPlayer';
|
||||||
import BottomNavbar from '../BottomNavBar/BottomNavBar';
|
import BottomNavbar from '../BottomNavBar/BottomNavBar';
|
||||||
|
import { useGlobalContext } from '../../GlobalState';
|
||||||
|
|
||||||
export const Layout = () => {
|
export const Layout = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { loginWithExtension, user } = useNDK();
|
const { loginWithExtension, user } = useNDK();
|
||||||
|
const { state } = useGlobalContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) loginWithExtension();
|
if (!user) loginWithExtension();
|
||||||
@ -69,9 +71,11 @@ export const Layout = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="content">{<Outlet />}</div>
|
<div className="content">{<Outlet />}</div>
|
||||||
|
{state.currentSong && (
|
||||||
<BottomNavbar>
|
<BottomNavbar>
|
||||||
<AudioPlayer />
|
<AudioPlayer />
|
||||||
</BottomNavbar>
|
</BottomNavbar>
|
||||||
|
)}
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
<span className="whitespace-nowrap block">
|
<span className="whitespace-nowrap block">
|
||||||
made with 💜 by{' '}
|
made with 💜 by{' '}
|
||||||
|
@ -8,6 +8,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import { useNDK } from '../../utils/ndk';
|
import { useNDK } from '../../utils/ndk';
|
||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk';
|
||||||
|
|
||||||
type ServerListProps = {
|
type ServerListProps = {
|
||||||
servers: ServerInfo[];
|
servers: ServerInfo[];
|
||||||
@ -49,7 +50,7 @@ export const ServerList = ({
|
|||||||
|
|
||||||
const handleSaveServers = async (newServers: ServerType[]) => {
|
const handleSaveServers = async (newServers: ServerType[]) => {
|
||||||
const ev = new NDKEvent(ndk, {
|
const ev = new NDKEvent(ndk, {
|
||||||
kind: 10063,
|
kind: USER_BLOSSOM_SERVER_LIST_KIND,
|
||||||
created_at: dayjs().unix(),
|
created_at: dayjs().unix(),
|
||||||
content: '',
|
content: '',
|
||||||
pubkey: user?.pubkey || '',
|
pubkey: user?.pubkey || '',
|
||||||
|
@ -4,7 +4,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
|
import { BlobDescriptor, BlossomClient } from 'blossom-client-sdk';
|
||||||
import { useNDK } from '../utils/ndk';
|
import { useNDK } from '../utils/ndk';
|
||||||
import BlobList from '../components/BlobList/BlobList';
|
import BlobList from '../components/BlobList/BlobList';
|
||||||
import { ServerInfo, useServerInfo } from '../utils/useServerInfo';
|
import { useServerInfo } from '../utils/useServerInfo';
|
||||||
import { ServerList } from '../components/ServerList/ServerList';
|
import { ServerList } from '../components/ServerList/ServerList';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import { NDKKind } from '@nostr-dev-kit/ndk';
|
|||||||
import countBy from 'lodash/countBy';
|
import countBy from 'lodash/countBy';
|
||||||
import sortBy from 'lodash/sortBy';
|
import sortBy from 'lodash/sortBy';
|
||||||
import toPairs from 'lodash/toPairs';
|
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 useBlossomServerEvents = () => {
|
||||||
const blossomServerEvents = useEvents(blossomServerListFilter);
|
const blossomServerEvents = useEvents(blossomServerListFilter);
|
||||||
|
@ -3,6 +3,7 @@ import { useNDK } from '../utils/ndk';
|
|||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import useEvent from '../utils/useEvent';
|
import useEvent from '../utils/useEvent';
|
||||||
|
import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk';
|
||||||
|
|
||||||
export type Server = {
|
export type Server = {
|
||||||
name: string;
|
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 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 servers = useMemo(() => {
|
||||||
const serverUrls = (serverListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s =>
|
const serverUrls = (serverListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s =>
|
||||||
|
Loading…
Reference in New Issue
Block a user