Skip to content

Commit

Permalink
Starts adding option to enable/disable gapless playback
Browse files Browse the repository at this point in the history
  • Loading branch information
digimezzo committed Oct 13, 2024
1 parent b276725 commit 5a546ab
Show file tree
Hide file tree
Showing 29 changed files with 245 additions and 28 deletions.
1 change: 1 addition & 0 deletions src/app/common/settings/settings.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ export abstract class SettingsBase {
public abstract playerType: string;
public abstract fullPlayerPositionSizeMaximized: string;
public abstract coverPlayerPosition: string;
public abstract enableGaplessPlayback: 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 @@ -790,6 +790,15 @@ export class Settings implements SettingsBase {
this.settings.set('coverPlayerPosition', v);
}

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

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

// Initialize
private initialize(): void {
if (!this.settings.has('language')) {
Expand Down Expand Up @@ -1119,5 +1128,9 @@ export class Settings implements SettingsBase {
if (!this.settings.has('coverPlayerPosition')) {
this.settings.set('coverPlayerPosition', '50;50');
}

if (!this.settings.has('enableGaplessPlayback')) {
this.settings.set('enableGaplessPlayback', true);
}
}
}
21 changes: 21 additions & 0 deletions src/app/services/playback/audio-player/audio-player-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { MathExtensions } from '../../../common/math-extensions';
import { Logger } from '../../../common/logger';
import { AudioPlayer } from './audio-player';
import { SettingsBase } from '../../../common/settings/settings.base';
import { GaplessAudioPlayer } from './gapless-audio-player';
import { AudioPlayerBase } from './audio-player.base';
@Injectable({ providedIn: 'root' })
export class AudioPlayerFactory {
public constructor(
private mathExtensions: MathExtensions,
private settings: SettingsBase,
private logger: Logger,
) {}
public create(): AudioPlayerBase {
if (this.settings.enableGaplessPlayback) {
return new GaplessAudioPlayer(this.mathExtensions, this.logger);
}
return new AudioPlayer(this.mathExtensions, this.logger);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IMock, Mock } from 'typemoq';
import { Logger } from '../../common/logger';
import { MathExtensions } from '../../common/math-extensions';
import { Logger } from '../../../common/logger';
import { MathExtensions } from '../../../common/math-extensions';
import { AudioPlayer } from './audio-player';
import { AudioPlayerBase } from './audio-player.base';

Expand Down
104 changes: 104 additions & 0 deletions src/app/services/playback/audio-player/audio-player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Logger } from '../../../common/logger';
import { MathExtensions } from '../../../common/math-extensions';
import { PromiseUtils } from '../../../common/utils/promise-utils';
import { StringUtils } from '../../../common/utils/string-utils';
import { AudioPlayerBase } from './audio-player.base';

@Injectable()
export class AudioPlayer implements AudioPlayerBase {
private _audio: HTMLAudioElement;

public constructor(
private mathExtensions: MathExtensions,
private logger: Logger,
) {
this._audio = new Audio();

try {
// This fails during unit tests because setSinkId() does not exist on HTMLAudioElement
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
this.audio.setSinkId('default');
} catch (e: unknown) {
// Suppress this error, but log it, in case it happens in production.
this.logger.error(e, 'Could not perform setSinkId()', 'AudioPlayer', 'constructor');
}

this.audio.defaultPlaybackRate = 1;
this.audio.playbackRate = 1;
this.audio.volume = 1;
this.audio.muted = false;

this.audio.onended = () => this.playbackFinished.next();
}

private playbackFinished: Subject<void> = new Subject();
public playbackFinished$: Observable<void> = this.playbackFinished.asObservable();

public get audio(): HTMLAudioElement {
return this._audio;
}

public get progressSeconds(): number {
if (isNaN(this.audio.currentTime)) {
return 0;
}

return this.audio.currentTime;
}

public get totalSeconds(): number {
if (isNaN(this.audio.duration)) {
return 0;
}

return this.audio.duration;
}

public play(audioFilePath: string): void {
const playableAudioFilePath: string = this.replaceUnplayableCharacters(audioFilePath);
this.audio.src = 'file:///' + playableAudioFilePath;
PromiseUtils.noAwait(this.audio.play());
}

public stop(): void {
this.audio.currentTime = 0;
this.audio.pause();
}

public pause(): void {
this.audio.pause();
}

public resume(): void {
PromiseUtils.noAwait(this.audio.play());
}

public setVolume(linearVolume: number): void {
// log(0) is undefined. So we provide a minimum of 0.01.
const logarithmicVolume: number = linearVolume > 0 ? this.mathExtensions.linearToLogarithmic(linearVolume, 0.01, 1) : 0;
this.audio.volume = logarithmicVolume;
}

public mute(): void {
this.audio.muted = true;
}

public unMute(): void {
this.audio.muted = false;
}

public skipToSeconds(seconds: number): void {
this.audio.currentTime = seconds;
}

private replaceUnplayableCharacters(audioFilePath: string): string {
// HTMLAudioElement doesn't play paths which contain # and ?, so we escape them.
let playableAudioFilePath: string = StringUtils.replaceAll(audioFilePath, '#', '%23');
playableAudioFilePath = StringUtils.replaceAll(playableAudioFilePath, '?', '%3F');
return playableAudioFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Logger } from '../../common/logger';
import { MathExtensions } from '../../common/math-extensions';
import { PromiseUtils } from '../../common/utils/promise-utils';
import { StringUtils } from '../../common/utils/string-utils';
import { Logger } from '../../../common/logger';
import { MathExtensions } from '../../../common/math-extensions';
import { PromiseUtils } from '../../../common/utils/promise-utils';
import { StringUtils } from '../../../common/utils/string-utils';
import { AudioPlayerBase } from './audio-player.base';

@Injectable()
export class AudioPlayer implements AudioPlayerBase {
export class GaplessAudioPlayer implements AudioPlayerBase {
private _audio: HTMLAudioElement;
private _audioContext: AudioContext;
private _buffer: AudioBuffer | undefined = undefined;
Expand Down
33 changes: 32 additions & 1 deletion src/app/services/playback/playback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { NotificationServiceBase } from '../notification/notification.service.ba
import { TrackSorter } from '../../common/sorting/track-sorter';
import { QueuePersister } from './queue-persister';
import { QueueRestoreInfo } from './queue-restore-info';
import { AudioPlayerFactory } from './audio-player/audio-player-factory';

@Injectable()
export class PlaybackService implements PlaybackServiceBase {
Expand All @@ -48,7 +49,7 @@ export class PlaybackService implements PlaybackServiceBase {
private playlistService: PlaylistServiceBase,
private notificationService: NotificationServiceBase,
private queuePersister: QueuePersister,
private _audioPlayer: AudioPlayerBase,
private audioPlayerFactory: AudioPlayerFactory,
private trackSorter: TrackSorter,
private queue: Queue,
private progressUpdater: ProgressUpdater,
Expand Down Expand Up @@ -537,4 +538,34 @@ export class PlaybackService implements PlaybackServiceBase {
this.progressUpdater.startUpdatingProgress();
}
}

private checkIfGaplessPlaybackChanged(): void {
if (this._enableGaplessPlayback !== this.settings.enableGaplessPlayback) {
this.logger.info(
`Gapless playback changed from ${this._enableGaplessPlayback} to ${this.settings.enableGaplessPlayback}`,
'PlaybackService',
'initializeAudioPlayer',
);
this._enableGaplessPlayback = this.settings.enableGaplessPlayback;
this.initializeAudioPlayer();
}
}

private initializeAudioPlayer(): void {
this.destroyAudioPlayerSubscriptions$.next();
this.destroyAudioPlayerSubscriptions$.complete();
this.destroyAudioPlayerSubscriptions$ = new Subject<void>();

this._audioPlayer = this.audioPlayerFactory.create();
this.logger.info(
`Created new audio player. Gapless playback: ${this._audioPlayer.supportsGaplessPlayback}`,
'PlaybackService',
'initializeAudioPlayer',
);
this._audioPlayer.setVolume(this._volume);

this._audioPlayer.playbackFinished$.pipe(takeUntil(this.destroyAudioPlayerSubscriptions$)).subscribe(() => {
this.playbackFinishedHandler();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@
>
</div>
<mat-divider class="my-4"></mat-divider>
<div class="title">{{ 'gapless-playback' | translate }}</div>
<div>
<app-toggle-switch [(isChecked)]="this.settings.enableGaplessPlayback">
{{ 'enable-gapless-playback' | translate }}</app-toggle-switch
>
</div>
<mat-divider class="my-4"></mat-divider>
<div class="title">{{ 'split-artists' | translate }}</div>
<div>
<div>{{ 'symbols-to-split-multiple-artists' | translate }}</div>
Expand Down
4 changes: 3 additions & 1 deletion src/assets/i18n/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Сигурни ли сте, че искате да премахнете изпълнителя '{{artist}}'?",
"switch-player": "Смени плеъра",
"volume": "Сила на звука",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Etes-vous sûr de vouloir supprimer l'artiste '{{artist}}'?",
"switch-player": "Changer de lecteur",
"volume": "Volume",
"thanks-to-visualisationexpo": "À VisualisationExpo, pour la superbe icône macOS!"
"thanks-to-visualisationexpo": "À VisualisationExpo, pour la superbe icône macOS!",
"gapless-playback": "Lecture sans interruption",
"enable-gapless-playback": "Activer la lecture sans interruption"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/hr.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/ku.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Weet u zeker dat u de artiest '{{artist}}' wilt verwijderen?",
"switch-player": "Speler wisselen",
"volume": "Volume",
"thanks-to-visualisationexpo": "Aan VisualizationExpo, voor het fantastische macOS icoon!"
"thanks-to-visualisationexpo": "Aan VisualizationExpo, voor het fantastische macOS icoon!",
"gapless-playback": "Naadloos afspelen",
"enable-gapless-playback": "Schakel naadloos afspelen in"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Are you sure you want to remove the artist '{{artist}}'?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
4 changes: 3 additions & 1 deletion src/assets/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,7 @@
"confirm-remove-artist": "Уверены, что хотите удалить исполнителя «{{artist}}»?",
"switch-player": "Switch player",
"volume": "Volume",
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!"
"thanks-to-visualisationexpo": "To VisualisationExpo, for the superb macOS icon!",
"gapless-playback": "Gapless playback",
"enable-gapless-playback": "Enable gapless playback"
}
Loading

0 comments on commit 5a546ab

Please sign in to comment.