mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +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 {
|
||||
const media = this.#media!;
|
||||
return {
|
||||
muted: media.muted,
|
||||
muted: media.muted || media.volume === 0,
|
||||
volumeLevel: getVolumeLevel(media),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,6 +50,15 @@ describe('MuteButtonCore', () => {
|
||||
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', () => {
|
||||
const core = new MuteButtonCore();
|
||||
core.setMedia(createMediaState({ volume: 0.3 }));
|
||||
|
||||
@@ -105,6 +105,16 @@ describe('VolumeSliderCore', () => {
|
||||
|
||||
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', () => {
|
||||
@@ -150,14 +160,14 @@ describe('VolumeSliderCore', () => {
|
||||
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();
|
||||
core.setInput(createInput());
|
||||
core.setMedia(createMediaState({ volume: 0 }));
|
||||
const state = core.getState();
|
||||
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 {
|
||||
const media = this.#media!;
|
||||
const { volume, muted } = media;
|
||||
const effectivelyMuted = muted || volume === 0;
|
||||
const { dragging, dragPercent } = this.input;
|
||||
const volumePercent = volume * 100;
|
||||
const value = dragging ? this.valueFromPercent(dragPercent) : volumePercent;
|
||||
@@ -47,9 +48,9 @@ export class VolumeSliderCore extends SliderCore {
|
||||
|
||||
return {
|
||||
...base,
|
||||
fillPercent: muted ? 0 : base.fillPercent,
|
||||
fillPercent: effectivelyMuted ? 0 : base.fillPercent,
|
||||
volume,
|
||||
muted,
|
||||
muted: effectivelyMuted,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -114,25 +114,27 @@ describe('volumeFeature', () => {
|
||||
});
|
||||
|
||||
describe('toggleMuted', () => {
|
||||
it('toggles mute from false to true', async () => {
|
||||
const video = createMockVideo({ muted: false });
|
||||
it('mutes when unmuted with volume > 0', async () => {
|
||||
const video = createMockVideo({ muted: false, volume: 0.8 });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.toggleMuted();
|
||||
|
||||
expect(video.muted).toBe(true);
|
||||
expect(video.volume).toBe(0.8);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('toggles mute from true to false', async () => {
|
||||
const video = createMockVideo({ muted: true });
|
||||
it('unmutes when muted with volume > 0', async () => {
|
||||
const video = createMockVideo({ muted: true, volume: 0.6 });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
const result = await store.toggleMuted();
|
||||
|
||||
expect(video.muted).toBe(false);
|
||||
expect(video.volume).toBe(0.6);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
@@ -147,26 +149,16 @@ describe('volumeFeature', () => {
|
||||
expect(video.volume).toBe(0.25);
|
||||
});
|
||||
|
||||
it('preserves volume when unmuting with volume > 0', async () => {
|
||||
const video = createMockVideo({ muted: true, volume: 0.6 });
|
||||
it('unmutes and restores volume when volume is 0 and not muted', async () => {
|
||||
const video = createMockVideo({ muted: false, volume: 0 });
|
||||
const store = createStore<PlayerTarget>()(volumeFeature);
|
||||
store.attach({ media: video, container: null });
|
||||
|
||||
await store.toggleMuted();
|
||||
const result = await store.toggleMuted();
|
||||
|
||||
expect(video.muted).toBe(false);
|
||||
expect(video.volume).toBe(0.6);
|
||||
});
|
||||
|
||||
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);
|
||||
expect(video.volume).toBe(0.25);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,12 +28,14 @@ export const volumeFeature = definePlayerFeature({
|
||||
|
||||
toggleMuted() {
|
||||
const { media } = target();
|
||||
const willUnmute = media.muted;
|
||||
media.muted = !media.muted;
|
||||
const effectivelyMuted = media.muted || media.volume === 0;
|
||||
|
||||
// Restore a sensible volume when unmuting at zero.
|
||||
if (willUnmute && media.volume === 0) {
|
||||
media.volume = UNMUTE_VOLUME;
|
||||
if (effectivelyMuted) {
|
||||
media.muted = false;
|
||||
// Restore a sensible volume when unmuting at zero.
|
||||
if (media.volume === 0) media.volume = UNMUTE_VOLUME;
|
||||
} else {
|
||||
media.muted = true;
|
||||
}
|
||||
|
||||
return media.muted;
|
||||
|
||||
Reference in New Issue
Block a user