mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(core): derive effective mute state for volume UI components (#753)
This commit is contained in:
@@ -68,7 +68,7 @@ export class MuteButtonCore {
|
|||||||
getState(): MuteButtonState {
|
getState(): MuteButtonState {
|
||||||
const media = this.#media!;
|
const media = this.#media!;
|
||||||
return {
|
return {
|
||||||
muted: media.muted,
|
muted: media.muted || media.volume === 0,
|
||||||
volumeLevel: getVolumeLevel(media),
|
volumeLevel: getVolumeLevel(media),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,15 @@ describe('MuteButtonCore', () => {
|
|||||||
expect(core.getState().volumeLevel).toBe('off');
|
expect(core.getState().volumeLevel).toBe('off');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('derives muted as true when volume is 0 and not muted', () => {
|
||||||
|
const core = new MuteButtonCore();
|
||||||
|
core.setMedia(createMediaState({ volume: 0, muted: false }));
|
||||||
|
const state = core.getState();
|
||||||
|
|
||||||
|
expect(state.muted).toBe(true);
|
||||||
|
expect(state.volumeLevel).toBe('off');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns low when volume < 0.5', () => {
|
it('returns low when volume < 0.5', () => {
|
||||||
const core = new MuteButtonCore();
|
const core = new MuteButtonCore();
|
||||||
core.setMedia(createMediaState({ volume: 0.3 }));
|
core.setMedia(createMediaState({ volume: 0.3 }));
|
||||||
|
|||||||
@@ -105,6 +105,16 @@ describe('VolumeSliderCore', () => {
|
|||||||
|
|
||||||
expect(state.muted).toBe(false);
|
expect(state.muted).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('derives muted as true when volume is 0 and not muted', () => {
|
||||||
|
const core = new VolumeSliderCore();
|
||||||
|
core.setInput(createInput());
|
||||||
|
core.setMedia(createMediaState({ volume: 0, muted: false }));
|
||||||
|
const state = core.getState();
|
||||||
|
|
||||||
|
expect(state.muted).toBe(true);
|
||||||
|
expect(state.fillPercent).toBe(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAttrs', () => {
|
describe('getAttrs', () => {
|
||||||
@@ -150,14 +160,14 @@ describe('VolumeSliderCore', () => {
|
|||||||
expect(attrs['aria-label']).toBe('Audio');
|
expect(attrs['aria-label']).toBe('Audio');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows 0 percent when volume is 0', () => {
|
it('shows 0 percent muted when volume is 0', () => {
|
||||||
const core = new VolumeSliderCore();
|
const core = new VolumeSliderCore();
|
||||||
core.setInput(createInput());
|
core.setInput(createInput());
|
||||||
core.setMedia(createMediaState({ volume: 0 }));
|
core.setMedia(createMediaState({ volume: 0 }));
|
||||||
const state = core.getState();
|
const state = core.getState();
|
||||||
const attrs = core.getAttrs(state);
|
const attrs = core.getAttrs(state);
|
||||||
|
|
||||||
expect(attrs['aria-valuetext']).toBe('0 percent');
|
expect(attrs['aria-valuetext']).toBe('0 percent, muted');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export class VolumeSliderCore extends SliderCore {
|
|||||||
getState(): VolumeSliderState {
|
getState(): VolumeSliderState {
|
||||||
const media = this.#media!;
|
const media = this.#media!;
|
||||||
const { volume, muted } = media;
|
const { volume, muted } = media;
|
||||||
|
const effectivelyMuted = muted || volume === 0;
|
||||||
const { dragging, dragPercent } = this.input;
|
const { dragging, dragPercent } = this.input;
|
||||||
const volumePercent = volume * 100;
|
const volumePercent = volume * 100;
|
||||||
const value = dragging ? this.valueFromPercent(dragPercent) : volumePercent;
|
const value = dragging ? this.valueFromPercent(dragPercent) : volumePercent;
|
||||||
@@ -47,9 +48,9 @@ export class VolumeSliderCore extends SliderCore {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
fillPercent: muted ? 0 : base.fillPercent,
|
fillPercent: effectivelyMuted ? 0 : base.fillPercent,
|
||||||
volume,
|
volume,
|
||||||
muted,
|
muted: effectivelyMuted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,25 +114,27 @@ describe('volumeFeature', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('toggleMuted', () => {
|
describe('toggleMuted', () => {
|
||||||
it('toggles mute from false to true', async () => {
|
it('mutes when unmuted with volume > 0', async () => {
|
||||||
const video = createMockVideo({ muted: false });
|
const video = createMockVideo({ muted: false, volume: 0.8 });
|
||||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||||
store.attach({ media: video, container: null });
|
store.attach({ media: video, container: null });
|
||||||
|
|
||||||
const result = await store.toggleMuted();
|
const result = await store.toggleMuted();
|
||||||
|
|
||||||
expect(video.muted).toBe(true);
|
expect(video.muted).toBe(true);
|
||||||
|
expect(video.volume).toBe(0.8);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('toggles mute from true to false', async () => {
|
it('unmutes when muted with volume > 0', async () => {
|
||||||
const video = createMockVideo({ muted: true });
|
const video = createMockVideo({ muted: true, volume: 0.6 });
|
||||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||||
store.attach({ media: video, container: null });
|
store.attach({ media: video, container: null });
|
||||||
|
|
||||||
const result = await store.toggleMuted();
|
const result = await store.toggleMuted();
|
||||||
|
|
||||||
expect(video.muted).toBe(false);
|
expect(video.muted).toBe(false);
|
||||||
|
expect(video.volume).toBe(0.6);
|
||||||
expect(result).toBe(false);
|
expect(result).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,26 +149,16 @@ describe('volumeFeature', () => {
|
|||||||
expect(video.volume).toBe(0.25);
|
expect(video.volume).toBe(0.25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('preserves volume when unmuting with volume > 0', async () => {
|
it('unmutes and restores volume when volume is 0 and not muted', async () => {
|
||||||
const video = createMockVideo({ muted: true, volume: 0.6 });
|
const video = createMockVideo({ muted: false, volume: 0 });
|
||||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||||
store.attach({ media: video, container: null });
|
store.attach({ media: video, container: null });
|
||||||
|
|
||||||
await store.toggleMuted();
|
const result = await store.toggleMuted();
|
||||||
|
|
||||||
expect(video.muted).toBe(false);
|
expect(video.muted).toBe(false);
|
||||||
expect(video.volume).toBe(0.6);
|
expect(video.volume).toBe(0.25);
|
||||||
});
|
expect(result).toBe(false);
|
||||||
|
|
||||||
it('does not change volume when muting', async () => {
|
|
||||||
const video = createMockVideo({ muted: false, volume: 0.8 });
|
|
||||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
|
||||||
store.attach({ media: video, container: null });
|
|
||||||
|
|
||||||
await store.toggleMuted();
|
|
||||||
|
|
||||||
expect(video.muted).toBe(true);
|
|
||||||
expect(video.volume).toBe(0.8);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ export const volumeFeature = definePlayerFeature({
|
|||||||
|
|
||||||
toggleMuted() {
|
toggleMuted() {
|
||||||
const { media } = target();
|
const { media } = target();
|
||||||
const willUnmute = media.muted;
|
const effectivelyMuted = media.muted || media.volume === 0;
|
||||||
media.muted = !media.muted;
|
|
||||||
|
|
||||||
// Restore a sensible volume when unmuting at zero.
|
if (effectivelyMuted) {
|
||||||
if (willUnmute && media.volume === 0) {
|
media.muted = false;
|
||||||
media.volume = UNMUTE_VOLUME;
|
// Restore a sensible volume when unmuting at zero.
|
||||||
|
if (media.volume === 0) media.volume = UNMUTE_VOLUME;
|
||||||
|
} else {
|
||||||
|
media.muted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return media.muted;
|
return media.muted;
|
||||||
|
|||||||
Reference in New Issue
Block a user