diff --git a/package.json b/package.json index 01b4c68..30413f7 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-timeago": "^8.3.0", "react-use-cookie": "^1.6.1", "redux": "^4.2.1", + "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", "thenby": "^1.3.4", "ua-parser-js": "^1.0.37", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d59300..26d14f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ importers: redux: specifier: ^4.2.1 version: 4.2.1 + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) tailwind-merge: specifier: ^3.3.1 version: 3.3.1 @@ -2538,6 +2541,12 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -5597,6 +5606,11 @@ snapshots: is-arrayish: 0.3.2 optional: true + sonner@2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + source-map-js@1.2.1: {} spdx-correct@3.2.0: diff --git a/public/gapless.cjs b/public/gapless.cjs index dab8d95..9f44d49 100644 --- a/public/gapless.cjs +++ b/public/gapless.cjs @@ -42,6 +42,7 @@ onPlayPreviousTrack, onStartNewTrack, webAudioIsDisabled = false, + onError, } = props; this.props = { @@ -50,6 +51,7 @@ onPlayNextTrack, onPlayPreviousTrack, onStartNewTrack, + onError, }; this.state = { @@ -209,6 +211,10 @@ this.tracks.map((track) => track.setVolume(nextVolume)); } + + onError() { + if (this.props.onError) this.props.onError(); + } } class Track { @@ -485,6 +491,7 @@ // basic event handlers audioOnError = (e) => { this.debug('audioOnError', e); + this.queue.onError(); }; onEnded(from) { diff --git a/src/app/(browse)/layout.tsx b/src/app/(browse)/layout.tsx index 3daf129..61fba98 100644 --- a/src/app/(browse)/layout.tsx +++ b/src/app/(browse)/layout.tsx @@ -3,6 +3,7 @@ import NavBar from '@/components/NavBar'; import cn from '@/lib/cn'; import { getIsInIframe } from '@/lib/isInIframe'; import { ReactNode } from 'react'; +import { Toaster } from 'sonner'; export default async function BrowseLayout({ children, @@ -23,6 +24,7 @@ export default async function BrowseLayout({ return ( +
; diff --git a/src/lib/player.ts b/src/lib/player.ts index 3ba9b2a..b79bdf3 100644 --- a/src/lib/player.ts +++ b/src/lib/player.ts @@ -5,6 +5,7 @@ import { splitShowDate } from './utils'; import { scrobblePlay } from '../redux/modules/live'; import { updatePlayback } from '../redux/modules/playback'; import { ActiveTrack, GaplessMetadata } from '../types'; +import { toast } from 'sonner'; declare global { interface Window { @@ -156,6 +157,12 @@ const player = new Gapless.Queue({ } } }, + onError: () => { + toast.error('There was an error loading your audio', { + toasterId: 'audio-error', + duration: Infinity, + }); + }, }); export function initGaplessPlayer(nextStore, changeURL) {