Skip to content

Commit

Permalink
#455: Click the volume icon to mute/unmute
Browse files Browse the repository at this point in the history
  • Loading branch information
digimezzo committed Oct 8, 2023
1 parent 5a46048 commit 2c3d21b
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 133 deletions.
1 change: 1 addition & 0 deletions src/app/common/settings/base-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,5 @@ export abstract class BaseSettings {
public abstract showLove: boolean;
public abstract enableMultimediaKeys: boolean;
public abstract downloadArtistInformationFromLastFm: boolean;
public abstract isMuted: boolean;
}
13 changes: 13 additions & 0 deletions src/app/common/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,15 @@ export class Settings implements BaseSettings {
this.settings.set('downloadArtistInformationFromLastFm', v);
}

// isMuted
public get isMuted(): boolean {
return <boolean>this.settings.get('isMuted');
}

public set isMuted(v: boolean) {
this.settings.set('isMuted', v);
}

// Initialize
private initialize(): void {
if (!this.settings.has('language')) {
Expand Down Expand Up @@ -871,5 +880,9 @@ export class Settings implements BaseSettings {
if (!this.settings.has('downloadArtistInformationFromLastFm')) {
this.settings.set('downloadArtistInformationFromLastFm', true);
}

if (!this.settings.has('isMuted')) {
this.settings.set('isMuted', false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ describe('AlbumBrowserComponent', () => {
component.onAddToQueue(album1);

// Assert
playbackServiceMock.verify((x) => x.addAlbumToQueue(album1), Times.once());
playbackServiceMock.verify((x) => x.addAlbumToQueueAsync(album1), Times.once());
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,6 @@ export class AlbumBrowserComponent implements OnInit, AfterViewInit {
}

public onAddToQueue(album: AlbumModel): void {
this.playbackService.addAlbumToQueue(album);
this.playbackService.addAlbumToQueueAsync(album);
}
}
66 changes: 37 additions & 29 deletions src/app/components/slider/slider.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class SliderComponent implements AfterViewInit {
@Input()
public set value(v: number) {
this._value = v;
this.applyPositionFromValue(v);
}

@Output()
Expand Down Expand Up @@ -88,7 +89,7 @@ export class SliderComponent implements AfterViewInit {
}

public onSliderContainerMouseDown(e: MouseEvent): void {
this.applyPosition(this.getMouseXPositionRelativeToSlider(e.clientX));
this.applyPositionAndValue(this.getMouseXPositionRelativeToSlider(e.clientX));
}

@HostListener('document:mousedown', ['$event'])
Expand All @@ -114,15 +115,15 @@ export class SliderComponent implements AfterViewInit {
@HostListener('document:mousemove', ['$event'])
public onDocumentMouseMove(e: MouseEvent): void {
if (this.isSliderThumbDown) {
this.applyPosition(this.getMouseXPositionRelativeToSlider(e.clientX));
this.applyPositionAndValue(this.getMouseXPositionRelativeToSlider(e.clientX));
}
}

@HostListener('document:touchmove', ['$event'])
public onDocumentTouchMove(e: TouchEvent): void {
if (this.isSliderThumbDown) {
const touch: Touch = e.touches[0] != undefined ? e.touches[0] : e.changedTouches[0];
this.applyPosition(this.getMouseXPositionRelativeToSlider(touch.pageX));
this.applyPositionAndValue(this.getMouseXPositionRelativeToSlider(touch.pageX));
}
}

Expand All @@ -132,7 +133,7 @@ export class SliderComponent implements AfterViewInit {
if (event.deltaY > 0) {
newPosition = this.sliderBarPosition - mouseStepConvertedToSliderScale;
}
this.applyPosition(newPosition);
this.applyPositionAndValue(newPosition);
}

private getMouseStepConvertedToSliderScale(): number {
Expand All @@ -142,24 +143,6 @@ export class SliderComponent implements AfterViewInit {
return mouseStepUsingSliderScale;
}

private applyPosition(position: number): void {
try {
const sliderWidth: number = this.nativeElementProxy.getElementWidth(this.sliderTrack);

this.sliderBarPosition = this.mathExtensions.clamp(position, 0, sliderWidth);

this.sliderThumbPosition = this.mathExtensions.clamp(
position - this.sliderThumbWidth / 2,
this.sliderThumbMargin - this.sliderThumbWidth / 2,
sliderWidth - this.sliderThumbMargin - this.sliderThumbWidth / 2
);

this.calculateValue();
} catch (e: unknown) {
this.logger.error(e, 'Could not apply position', 'SliderComponent', 'applyPosition');
}
}

private getMouseXPositionRelativeToSlider(clientX: number): number {
const rect: DOMRect | undefined = this.nativeElementProxy.getBoundingRectangle(this.sliderTrack);

Expand All @@ -170,7 +153,19 @@ export class SliderComponent implements AfterViewInit {
return clientX - rect.left;
}

private calculateValue(): void {
private applyPosition(position: number): void {
const sliderWidth: number = this.nativeElementProxy.getElementWidth(this.sliderTrack);

this.sliderBarPosition = this.mathExtensions.clamp(position, 0, sliderWidth);

this.sliderThumbPosition = this.mathExtensions.clamp(
position - this.sliderThumbWidth / 2,
this.sliderThumbMargin - this.sliderThumbWidth / 2,
sliderWidth - this.sliderThumbMargin - this.sliderThumbWidth / 2
);
}

private applyValue(): void {
const sliderWidth: number = this.nativeElementProxy.getElementWidth(this.sliderTrack);

const valueFraction: number = this.sliderBarPosition / sliderWidth;
Expand All @@ -181,14 +176,27 @@ export class SliderComponent implements AfterViewInit {
this.valueChange.emit(this._value);
}

private applyPositionAndValue(position: number): void {
try {
this.applyPosition(position);
this.applyValue();
} catch (e: unknown) {
this.logger.error(e, 'Could not apply position', 'SliderComponent', 'applyPosition');
}
}

private applyPositionFromValue(value: number): void {
const sliderWidth: number = this.nativeElementProxy.getElementWidth(this.sliderTrack);
let position: number = 0;
try {
const sliderWidth: number = this.nativeElementProxy.getElementWidth(this.sliderTrack);
let position: number = 0;

if (this.maximum > 0) {
position = (value / this.maximum) * sliderWidth;
}
if (this.maximum > 0) {
position = (value / this.maximum) * sliderWidth;
}

this.applyPosition(position);
this.applyPosition(position);
} catch (e: unknown) {
this.logger.error(e, 'Could not apply position from value', 'SliderComponent', 'applyPositionFromValue');
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<div class="app-volume-control">
<i class="app-volume-control__icon__alt las la-volume-mute" *ngIf="this.volume === 0"></i>
<i class="app-volume-control__icon__alt las la-volume-down" *ngIf="this.volume > 0 && this.volume < 0.5"></i>
<i class="app-volume-control__icon las la-volume-up" *ngIf="this.volume >= 0.5"></i>
<div class="pointer" (click)="this.toggleMute()">
<i class="app-volume-control__icon__alt las la-volume-mute" *ngIf="this.volume === 0"></i>
<i class="app-volume-control__icon__alt las la-volume-down" *ngIf="this.volume > 0 && this.volume < 0.5"></i>
<i class="app-volume-control__icon las la-volume-up" *ngIf="this.volume >= 0.5"></i>
</div>
<app-slider
class="app-volume-control__slider ml-3 mr-2"
[stepSize]="0.01"
Expand Down
31 changes: 22 additions & 9 deletions src/app/components/volume-control/volume-control.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Mock, Times } from 'typemoq';
import { BasePlaybackService } from '../../services/playback/base-playback.service';
import { VolumeControlComponent } from './volume-control.component';

describe('VolumeControlComponent', () => {
Expand All @@ -7,16 +9,13 @@ describe('VolumeControlComponent', () => {
playbackServiceMock = { volume: 0 };
});

function createVolumeControl(): VolumeControlComponent {
return new VolumeControlComponent(playbackServiceMock);
}

describe('constructor', () => {
it('should create', () => {
// Arrange
const playbackServiceMock = Mock.ofType<BasePlaybackService>();

// Act
const component: VolumeControlComponent = createVolumeControl();
const component: VolumeControlComponent = new VolumeControlComponent(playbackServiceMock.object);

// Assert
expect(component).toBeDefined();
Expand All @@ -26,8 +25,8 @@ describe('VolumeControlComponent', () => {
describe('volume', () => {
it('should set playbackService.volume', () => {
// Arrange
playbackServiceMock.volume = 50;
const component: VolumeControlComponent = createVolumeControl();
const playbackServiceMock: any = { volume: 50 };
const component: VolumeControlComponent = new VolumeControlComponent(playbackServiceMock);

// Act
component.volume = 20;
Expand All @@ -38,11 +37,25 @@ describe('VolumeControlComponent', () => {

it('should get playbackService.volume', () => {
// Arrange
playbackServiceMock.volume = 40;
const component: VolumeControlComponent = createVolumeControl();
const playbackServiceMock: any = { volume: 40 };
const component: VolumeControlComponent = new VolumeControlComponent(playbackServiceMock);

// Act & Assert
expect(component.volume).toEqual(40);
});
});

describe('toggleMute', () => {
it('should call playbackService.toggleMute()', () => {
// Arrange
const playbackServiceMock = Mock.ofType<BasePlaybackService>();
const component: VolumeControlComponent = new VolumeControlComponent(playbackServiceMock.object);

// Act
component.toggleMute();

// Assert
playbackServiceMock.verify((x) => x.toggleMute(), Times.once());
});
});
});
4 changes: 4 additions & 0 deletions src/app/components/volume-control/volume-control.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ export class VolumeControlComponent {
public set volume(v: number) {
this.playbackService.volume = v;
}

public toggleMute(): void {
this.playbackService.toggleMute();
}
}
5 changes: 4 additions & 1 deletion src/app/services/playback/base-playback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ export abstract class BasePlaybackService {
public abstract addTracksToQueueAsync(tracksToAdd: TrackModel[]): Promise<void>;
public abstract addArtistToQueueAsync(artistToAdd: ArtistModel, artistType: ArtistType): Promise<void>;
public abstract addGenreToQueueAsync(genreToAdd: GenreModel): Promise<void>;
public abstract addAlbumToQueue(albumToAdd: AlbumModel): void;
public abstract addAlbumToQueueAsync(albumToAdd: AlbumModel): Promise<void>;
public abstract addPlaylistToQueueAsync(playlistToAdd: PlaylistModel): Promise<void>;
public abstract removeFromQueue(tracksToRemove: TrackModel[]): void;
public abstract playQueuedTrack(trackToPlay: TrackModel): void;
public abstract playPrevious(): void;
public abstract playNext(): void;
public abstract skipByFractionOfTotalSeconds(fractionOfTotalSeconds: number): void;
public abstract stopIfPlaying(track: TrackModel): void;
public abstract pause(): void;
public abstract resume(): void;
public abstract toggleMute(): void;
}
Loading

0 comments on commit 2c3d21b

Please sign in to comment.