Skip to content

Commit f153323

Browse files
committed
fix(media): use local volume mute behavior
1 parent 45b5201 commit f153323

8 files changed

Lines changed: 50 additions & 187 deletions

File tree

src/app/features/media/components/media-card/index.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ export const MediaCard = memo(function MediaCard({
6767
isMuted,
6868
isOpen,
6969
openDialog,
70-
thumbnailAlbumArt,
7170
toggleMute,
7271
togglePlay,
7372
volume,
@@ -126,7 +125,6 @@ export const MediaCard = memo(function MediaCard({
126125
<MediaSmallView
127126
entityId={id}
128127
artwork={resolvedAlbumArt}
129-
paletteArtwork={thumbnailAlbumArt}
130128
onArtworkError={handleArtworkError}
131129
title={title}
132130
artist={artist}
@@ -146,7 +144,6 @@ export const MediaCard = memo(function MediaCard({
146144
<MediaMediumView
147145
entityId={id}
148146
artwork={resolvedAlbumArt}
149-
paletteArtwork={thumbnailAlbumArt}
150147
onArtworkError={handleArtworkError}
151148
title={title}
152149
artist={artist}
@@ -168,7 +165,6 @@ export const MediaCard = memo(function MediaCard({
168165
<MediaMediumVerticalView
169166
entityId={id}
170167
artwork={resolvedAlbumArt}
171-
paletteArtwork={thumbnailAlbumArt}
172168
onArtworkError={handleArtworkError}
173169
title={title}
174170
artist={artist}
@@ -196,7 +192,6 @@ export const MediaCard = memo(function MediaCard({
196192
isOpen={isOpen}
197193
onOpenChange={closeDialog}
198194
artwork={resolvedAlbumArt}
199-
paletteArtwork={thumbnailAlbumArt}
200195
onArtworkError={handleArtworkError}
201196
title={title}
202197
artist={artist}

src/app/features/media/components/media-card/use-media-card-controller.ts

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { useCallback, useEffect, useState } from 'react';
22
import { toast } from 'sonner';
33
import { useAuth } from '@/app/contexts/auth-context';
4-
import { fetchMediaThumbnailDataUrl } from '@/app/features/media/utils/media-thumbnail';
5-
import { useHomeAssistant, useI18n } from '@/app/hooks';
4+
import { useI18n } from '@/app/hooks';
65
import { homeAssistantService } from '@/app/services/home-assistant.service';
7-
import { homeAssistantSelectors } from '@/app/stores/selectors';
86
import {
97
resolveHomeAssistantAbsoluteUrl,
108
resolveHomeAssistantProxyUrl,
@@ -41,18 +39,16 @@ export function useMediaCardController({
4139
initialPositionUpdatedAt,
4240
}: UseMediaCardControllerParams) {
4341
const { config: authConfig } = useAuth();
44-
const connected = useHomeAssistant(homeAssistantSelectors.connected);
45-
const connection = useHomeAssistant(homeAssistantSelectors.connection);
4642
const { t } = useI18n();
4743
const [state, setState] = useState(initialState);
4844
const [volume, setVolume] = useState(initialVolume);
4945
const [isMuted, setIsMuted] = useState(initialMuted);
46+
const [previousVolume, setPreviousVolume] = useState(initialVolume > 0 ? initialVolume : 50);
5047
const [isOpen, setIsOpen] = useState(false);
5148
const [elapsedSeconds, setElapsedSeconds] = useState(initialElapsedSeconds ?? 0);
5249
const [durationSeconds, setDurationSeconds] = useState(initialDurationSeconds ?? 0);
5350
const [failedArtworkUrl, setFailedArtworkUrl] = useState<string | null>(null);
5451
const [resolvedAlbumArt, setResolvedAlbumArt] = useState<string | null>(null);
55-
const [thumbnailAlbumArt, setThumbnailAlbumArt] = useState<string | null>(null);
5652
const artworkRequestKey = [entityId, artworkKey].filter(Boolean).join('::');
5753
const fallbackArtwork = entityPicture
5854
? import.meta.env.DEV
@@ -66,11 +62,14 @@ export function useMediaCardController({
6662

6763
useEffect(() => {
6864
setVolume(initialVolume);
65+
if (initialVolume > 0) {
66+
setPreviousVolume(initialVolume);
67+
}
6968
}, [initialVolume]);
7069

7170
useEffect(() => {
72-
setIsMuted(initialMuted);
73-
}, [initialMuted]);
71+
setIsMuted(initialMuted || initialVolume === 0);
72+
}, [initialMuted, initialVolume]);
7473

7574
useEffect(() => {
7675
setElapsedSeconds(initialElapsedSeconds ?? 0);
@@ -82,58 +81,22 @@ export function useMediaCardController({
8281

8382
useEffect(() => {
8483
if (!artworkRequestKey) {
85-
setThumbnailAlbumArt(null);
8684
setResolvedAlbumArt(
8785
isFailedArtworkCandidate(fallbackArtwork, failedArtworkUrl) ? null : fallbackArtwork
8886
);
8987
return;
9088
}
91-
92-
let cancelled = false;
93-
94-
void (async () => {
95-
if (!connected || !connection) {
96-
if (!cancelled) {
97-
setResolvedAlbumArt(
98-
isFailedArtworkCandidate(fallbackArtwork, failedArtworkUrl) ? null : fallbackArtwork
99-
);
100-
}
101-
return;
102-
}
103-
104-
const thumbnailDataUrl = await fetchMediaThumbnailDataUrl(entityId, connection).catch(
105-
() => null
106-
);
107-
if (thumbnailDataUrl && !isFailedArtworkCandidate(thumbnailDataUrl, failedArtworkUrl)) {
108-
if (cancelled) {
109-
return;
110-
}
111-
112-
setThumbnailAlbumArt(thumbnailDataUrl);
113-
setResolvedAlbumArt(thumbnailDataUrl);
114-
return;
115-
}
116-
117-
if (!cancelled) {
118-
setThumbnailAlbumArt(null);
119-
setResolvedAlbumArt(
120-
isFailedArtworkCandidate(fallbackArtwork, failedArtworkUrl) ? null : fallbackArtwork
121-
);
122-
}
123-
})();
124-
125-
return () => {
126-
cancelled = true;
127-
};
128-
}, [artworkRequestKey, connected, connection, entityId, failedArtworkUrl, fallbackArtwork]);
89+
setResolvedAlbumArt(
90+
isFailedArtworkCandidate(fallbackArtwork, failedArtworkUrl) ? null : fallbackArtwork
91+
);
92+
}, [artworkRequestKey, failedArtworkUrl, fallbackArtwork]);
12993

13094
useEffect(() => {
13195
if (!artworkRequestKey) {
13296
return;
13397
}
13498

13599
setFailedArtworkUrl(null);
136-
setThumbnailAlbumArt(null);
137100
}, [artworkRequestKey]);
138101

139102
const isPlaying = state === 'playing';
@@ -193,24 +156,45 @@ export function useMediaCardController({
193156
const toggleMute = useCallback(() => {
194157
const nextMuted = !isMuted;
195158
setIsMuted(nextMuted);
159+
if (nextMuted) {
160+
const nextPreviousVolume = volume > 0 ? volume : previousVolume;
161+
setPreviousVolume(nextPreviousVolume);
162+
setVolume(0);
163+
void runAction(
164+
() => homeAssistantService.setMediaPlayerVolume(entityId, 0),
165+
t('media.feedback.updateVolumeFailed')
166+
);
167+
return;
168+
}
169+
170+
const restoredVolume = previousVolume > 0 ? previousVolume : 50;
171+
setVolume(restoredVolume);
196172
void runAction(
197-
() => homeAssistantService.setMediaPlayerMute(entityId, nextMuted),
173+
() => homeAssistantService.setMediaPlayerVolume(entityId, restoredVolume),
198174
t('media.feedback.updateVolumeFailed')
199175
);
200-
}, [entityId, isMuted, runAction, t]);
176+
}, [entityId, isMuted, previousVolume, runAction, t, volume]);
201177

202178
const handleVolumeChange = useCallback(
203179
(nextVolume: number) => {
204180
setVolume(nextVolume);
181+
if (nextVolume > 0) {
182+
setPreviousVolume(nextVolume);
183+
}
184+
205185
if (nextVolume > 0 && isMuted) {
206186
setIsMuted(false);
207-
void runAction(async () => {
208-
await homeAssistantService.setMediaPlayerMute(entityId, false);
209-
await homeAssistantService.setMediaPlayerVolume(entityId, nextVolume);
210-
}, t('media.feedback.updateVolumeFailed'));
187+
void runAction(
188+
() => homeAssistantService.setMediaPlayerVolume(entityId, nextVolume),
189+
t('media.feedback.updateVolumeFailed')
190+
);
211191
return;
212192
}
213193

194+
if (nextVolume === 0) {
195+
setIsMuted(true);
196+
}
197+
214198
void runAction(
215199
() => homeAssistantService.setMediaPlayerVolume(entityId, nextVolume),
216200
t('media.feedback.updateVolumeFailed')
@@ -252,7 +236,6 @@ export function useMediaCardController({
252236

253237
return {
254238
albumArt: resolvedAlbumArt,
255-
thumbnailAlbumArt,
256239
closeDialog,
257240
durationSeconds,
258241
elapsedSeconds,

src/app/features/media/components/media/media-dialog.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ interface MediaDialogProps {
1313
isOpen: boolean;
1414
onOpenChange: (open: boolean) => void;
1515
artwork?: string | null;
16-
paletteArtwork?: string | null;
1716
onArtworkError?: (imageUrl?: string | null) => void;
1817
title: string;
1918
artist: string;
@@ -34,7 +33,6 @@ export function MediaDialog({
3433
isOpen,
3534
onOpenChange,
3635
artwork,
37-
paletteArtwork,
3836
onArtworkError,
3937
title,
4038
artist,
@@ -53,13 +51,7 @@ export function MediaDialog({
5351
const { t } = useI18n();
5452
const surface = getThemeSurfaceTokens(theme);
5553
const isGlass = theme === 'glass';
56-
const palette = useMediaArtworkColors(
57-
artwork,
58-
theme,
59-
entityId,
60-
`${title}::${artist}`,
61-
paletteArtwork
62-
);
54+
const palette = useMediaArtworkColors(artwork, theme, `${entityId}::${title}::${artist}`);
6355
const displayRemaining = formatMediaTime(Math.max(0, durationSeconds - elapsedSeconds));
6456
const displayDuration = durationSeconds > 0 ? formatMediaTime(durationSeconds) : '--:--';
6557
const presetButton = (isActive: boolean) =>

src/app/features/media/components/media/media-medium-vertical-view.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { useMediaArtworkColors, withAlpha } from './use-media-artwork-colors';
1313
interface MediaMediumVerticalViewProps {
1414
entityId: string;
1515
artwork?: string | null;
16-
paletteArtwork?: string | null;
1716
onArtworkError?: (imageUrl?: string | null) => void;
1817
title: string;
1918
artist: string;
@@ -34,7 +33,6 @@ interface MediaMediumVerticalViewProps {
3433
export function MediaMediumVerticalView({
3534
entityId,
3635
artwork,
37-
paletteArtwork,
3836
onArtworkError,
3937
title,
4038
artist,
@@ -54,13 +52,7 @@ export function MediaMediumVerticalView({
5452
const { t } = useI18n();
5553
const displayVolume = Math.max(0, Math.min(100, isMuted ? 0 : volume));
5654
const stateSurface = getCardStateSurfaceTokens(theme, isActive);
57-
const palette = useMediaArtworkColors(
58-
artwork,
59-
theme,
60-
entityId,
61-
`${title}::${artist}`,
62-
paletteArtwork
63-
);
55+
const palette = useMediaArtworkColors(artwork, theme, `${entityId}::${title}::${artist}`);
6456
const iconTone = stateSurface.primaryTextClassName;
6557
const subtitleTone = stateSurface.secondaryTextClassName;
6658
const displayRemaining = formatMediaTime(Math.max(0, durationSeconds - elapsedSeconds));

src/app/features/media/components/media/media-medium-view.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { useMediaArtworkColors, withAlpha } from './use-media-artwork-colors';
1212
interface MediaMediumViewProps {
1313
entityId: string;
1414
artwork?: string | null;
15-
paletteArtwork?: string | null;
1615
onArtworkError?: (imageUrl?: string | null) => void;
1716
title: string;
1817
artist: string;
@@ -34,7 +33,6 @@ interface MediaMediumViewProps {
3433
export function MediaMediumView({
3534
entityId,
3635
artwork,
37-
paletteArtwork,
3836
onArtworkError,
3937
title,
4038
artist,
@@ -55,13 +53,7 @@ export function MediaMediumView({
5553
const { t } = useI18n();
5654
const displayVolume = Math.max(0, Math.min(100, isMuted ? 0 : volume));
5755
const stateSurface = getCardStateSurfaceTokens(theme, isActive);
58-
const palette = useMediaArtworkColors(
59-
artwork,
60-
theme,
61-
entityId,
62-
`${title}::${artist}`,
63-
paletteArtwork
64-
);
56+
const palette = useMediaArtworkColors(artwork, theme, `${entityId}::${title}::${artist}`);
6557
const iconTone = stateSurface.primaryTextClassName;
6658
const subtitleTone = stateSurface.secondaryTextClassName;
6759
const displayRemaining = formatMediaTime(Math.max(0, durationSeconds - elapsedSeconds));

src/app/features/media/components/media/media-small-view.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { useMediaArtworkColors, withAlpha } from './use-media-artwork-colors';
1515
interface MediaSmallViewProps {
1616
entityId: string;
1717
artwork?: string | null;
18-
paletteArtwork?: string | null;
1918
onArtworkError?: (imageUrl?: string | null) => void;
2019
title: string;
2120
artist: string;
@@ -35,7 +34,6 @@ interface MediaSmallViewProps {
3534
export function MediaSmallView({
3635
entityId,
3736
artwork,
38-
paletteArtwork,
3937
onArtworkError,
4038
title,
4139
artist,
@@ -76,13 +74,7 @@ export function MediaSmallView({
7674
const subtitleTone = stateSurface.secondaryTextClassName;
7775
const displayRemaining = formatMediaTime(Math.max(0, durationSeconds - elapsedSeconds));
7876
const controls = getMediaControlStyles(theme);
79-
const palette = useMediaArtworkColors(
80-
artwork,
81-
theme,
82-
entityId,
83-
`${title}::${artist}`,
84-
paletteArtwork
85-
);
77+
const palette = useMediaArtworkColors(artwork, theme, `${entityId}::${title}::${artist}`);
8678
const controlSizes = getCardActionControlSizes('small');
8779
const primaryControlSizes = getCardActionControlSizes('medium');
8880
const subduedFallback = !artwork && !isActive;

0 commit comments

Comments
 (0)