Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ import { CoverPlayerPlaybackQueueComponent } from './ui/components/mini-players/
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { CoverPlayerVolumeControlComponent } from './ui/components/mini-players/cover-player/cover-player-volume-control/cover-player-volume-control.component';
import { VolumeIconComponent } from './ui/components/volume-icon/volume-icon.component';
import { MacOSDockService } from './services/dock/macos.dock.service';

export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
Expand Down Expand Up @@ -486,7 +487,7 @@ export function appInitializerFactory(translate: TranslateService, injector: Inj
{
provide: APP_INITIALIZER,
useFactory: appInitializerFactory,
deps: [TranslateService, Injector],
deps: [TranslateService, Injector, MacOSDockService],
multi: true,
},
ElectronService,
Expand Down
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 @@ -59,6 +59,7 @@ export abstract class SettingsBase {
public abstract showPlaylistsPage: boolean;
public abstract showFoldersPage: boolean;
public abstract showRating: boolean;
public abstract showAlbumCoverInDock: boolean;
public abstract saveRatingToAudioFiles: boolean;
public abstract tracksPageVisibleColumns: string;
public abstract tracksPageColumnsOrder: string;
Expand Down
13 changes: 13 additions & 0 deletions src/app/common/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,15 @@ export class Settings implements SettingsBase {
this.settings.set('showRating', v);
}

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

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

// tracksPageVisibleColumns
public get tracksPageVisibleColumns(): string {
return <string>this.settings.get('tracksPageVisibleColumns');
Expand Down Expand Up @@ -1019,6 +1028,10 @@ export class Settings implements SettingsBase {
this.settings.set('showRating', true);
}

if (!this.settings.has('showAlbumCoverInDock')) {
this.settings.set('showAlbumCoverInDock', false);
}

if (!this.settings.has('saveRatingToAudioFiles')) {
this.settings.set('saveRatingToAudioFiles', false);
}
Expand Down
1 change: 1 addition & 0 deletions src/app/services/appearance/appearance.service.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export abstract class AppearanceServiceBase {
public abstract fontSizes: number[];
public abstract themes: Theme[];
public abstract followSystemTheme: boolean;
public abstract showAlbumCoverInDock: boolean;
public abstract useLightBackgroundTheme: boolean;
public abstract followSystemColor: boolean;
public abstract followAlbumCoverColor: boolean;
Expand Down
39 changes: 39 additions & 0 deletions src/app/services/appearance/appearance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { PlaybackService } from '../playback/playback.service';
import { PlaybackStarted } from '../playback/playback-started';
import { AlbumAccentColorService } from '../album-accent-color/album-accent-color.service';
import { PromiseUtils } from '../../common/utils/promise-utils';
import { Dock, nativeImage } from 'electron';
import * as remote from '@electron/remote';
import { MetadataService } from '../metadata/metadata.service';

@Injectable()
export class AppearanceService implements AppearanceServiceBase {
Expand Down Expand Up @@ -52,6 +55,7 @@ export class AppearanceService implements AppearanceServiceBase {
private applicationPaths: ApplicationPaths,
private playbackService: PlaybackService,
private albumAccentColorService: AlbumAccentColorService,
private metadataService: MetadataService,
) {
this.initialize();
}
Expand Down Expand Up @@ -92,6 +96,15 @@ export class AppearanceService implements AppearanceServiceBase {
PromiseUtils.noAwait(this.safeApplyThemeAsync());
}

public get showAlbumCoverInDock(): boolean {
return this.settings.showAlbumCoverInDock;
}

public set showAlbumCoverInDock(v: boolean) {
this.settings.showAlbumCoverInDock = v;
PromiseUtils.noAwait(this.setDockIconAsync());
}

public get useLightBackgroundTheme(): boolean {
return this.settings.useLightBackgroundTheme;
}
Expand Down Expand Up @@ -496,4 +509,30 @@ export class AppearanceService implements AppearanceServiceBase {

return themes;
}

private async setDockIconAsync(): Promise<boolean> {
try {
const track = this.playbackService.currentTrack;
if (this.settings.showAlbumCoverInDock && this.application.getGlobal('isMacOS') && track != undefined) {
const albumArtwork = await this.metadataService.getDockAlbumArtwork(track.albumKey);
remote.app.dock.setIcon(albumArtwork);

} else {
const staticPath = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\');
const defaultIconPath: string = require('path').join(staticPath, 'icons/64x64.png');
const image = nativeImage.createFromPath(defaultIconPath);
remote.app.dock.setIcon(image);
}

} catch (e: unknown) {
this.logger.warn(
'Could not set dock icon.',
'AppearanceService',
'setDockIconAsync',
);

return false;
}
return true;
}
}
5 changes: 5 additions & 0 deletions src/app/services/dock/macos.dock.service.base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Menu } from "electron";

export abstract class MacOSDockServiceBase {
public abstract reloadDockMenu(): void;
}
Empty file.
74 changes: 74 additions & 0 deletions src/app/services/dock/macos.dock.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Injectable } from '@angular/core';
import { MacOSDockServiceBase } from './macos.dock.service.base';
import { Menu } from 'electron';
import * as remote from '@electron/remote';
import { Subscription } from 'rxjs';
import { TranslatorServiceBase } from '../translator/translator.service.base';
import { PlaybackService } from '../playback/playback.service';
import { Logger } from '../../common/logger';

@Injectable({ providedIn: 'root' })
export class MacOSDockService implements MacOSDockServiceBase {
private dockMenu: Menu;
private subscription: Subscription = new Subscription();

constructor (
private translatorService: TranslatorServiceBase,
private playbackService: PlaybackService,
private logger: Logger,
) {
this.logger.info('Init service', MacOSDockService.name, 'constructor');
this.initializeSubscriptions();
}

private initializeSubscriptions(): void {
this.subscription.add(
this.translatorService.languageChanged$.subscribe(() => {
this.reloadDockMenu();
}),
);
this.subscription.add(
this.playbackService.playbackStarted$.subscribe(() => {
this.reloadDockMenu();
}),
);
this.subscription.add(
this.playbackService.playbackPaused$.subscribe(() => {
this.reloadDockMenu();
}),
);
this.subscription.add(
this.playbackService.playbackResumed$.subscribe(() => {
this.reloadDockMenu();
}),
);
this.subscription.add(
this.playbackService.playbackStopped$.subscribe(() => {
this.reloadDockMenu();
}),
);
this.subscription.add(
this.playbackService.playbackSkipped$.subscribe(() => {
this.reloadDockMenu();
}),
);
}

public getDockMenu(): Menu {
return this.dockMenu;
}

public reloadDockMenu(): void {
this.logger.info(`Reloading dock menu: ${this.playbackService.isPlaying}`, MacOSDockService.name, 'reloadDockMenu');
const menu = new remote.Menu();
if (this.playbackService.isPlaying) {
menu.append(new remote.MenuItem({ label: this.translatorService.get('pause'), click: () => this.playbackService.togglePlayback() }));
} else {
menu.append(new remote.MenuItem({ label: this.translatorService.get('play'), click: () => this.playbackService.togglePlayback() }));
}
menu.append(new remote.MenuItem({ label: this.translatorService.get('previous'), click: () => this.playbackService.playPrevious() }));
menu.append(new remote.MenuItem({ label: this.translatorService.get('next'), click: () => this.playbackService.playNext() }));
remote.app.dock.setMenu(menu);
this.dockMenu = menu;
}
}
34 changes: 34 additions & 0 deletions src/app/services/metadata/metadata.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { TrackRepositoryBase } from '../../data/repositories/track-repository.ba
import { FileAccessBase } from '../../common/io/file-access.base';
import { FileMetadataFactoryBase } from '../../common/metadata/file-metadata.factory.base';
import { SettingsBase } from '../../common/settings/settings.base';
import {Jimp, BlendMode} from 'jimp';
import { nativeImage } from 'electron';

@Injectable({ providedIn: 'root' })
export class MetadataService {
Expand Down Expand Up @@ -100,4 +102,36 @@ export class MetadataService {
public getAlbumArtworkPath(albumKey: string): string {
return this.cachedAlbumArtworkGetter.getCachedAlbumArtworkPath(albumKey);
}

public async getDockAlbumArtwork(albumKey) : Promise<Electron.NativeImage> {
const albumArtworkPath: string = this.getAlbumArtworkPath(albumKey);
const imageBuffer = nativeImage.createFromPath(albumArtworkPath).toJPEG(80);

const background = new Jimp({
width:1024,
height:1024,
color:0x00000000
});

// Load the input image from buffer
const image = await Jimp.read(imageBuffer);

// Scale the image by 13/16 of 1024
image.resize({
w: 832,
h: 832,
});

// Calculate position to center the scaled image
const x = (1024 - image.width) / 2;
const y = (1024 - image.height) / 2;

// Composite the processed image onto the background
background.composite(image, x, y, {
mode: BlendMode.SRC_OVER
});

const result = await background.getBuffer("image/png");
return nativeImage.createFromBuffer(result);
}
}
16 changes: 15 additions & 1 deletion src/app/services/playback/playback.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import { QueueRestoreInfo } from './queue-restore-info';
import { AudioPlayerFactory } from './audio-player/audio-player.factory';
import { IAudioPlayer } from './audio-player/i-audio-player';
import { MediaSessionService } from '../media-session/media-session.service';
import { Track } from '../../data/entities/track';
import * as remote from '@electron/remote';
import { MetadataService } from '../metadata/metadata.service';
import { ApplicationBase } from '../../common/io/application.base';

@Injectable({ providedIn: 'root' })
export class PlaybackService {
Expand All @@ -48,11 +50,13 @@ export class PlaybackService {
private _preloadTimeoutId: NodeJS.Timeout | number | undefined;

public constructor(
private application: ApplicationBase,
private audioPlayerFactory: AudioPlayerFactory,
private trackService: TrackServiceBase,
private playlistService: PlaylistServiceBase,
private notificationService: NotificationServiceBase,
private mediaSessionService: MediaSessionService,
private metadataService: MetadataService,
private queuePersister: QueuePersister,
private trackSorter: TrackSorter,
private queue: Queue,
Expand Down Expand Up @@ -273,6 +277,7 @@ export class PlaybackService {
private postPause() {
this._canPause = false;
this._canResume = true;
this._isPlaying = false;
this.pauseUpdatingProgress();
this.playbackPaused.next();

Expand Down Expand Up @@ -398,6 +403,10 @@ export class PlaybackService {
this._canResume = false;

this.mediaSessionService.setMetadataAsync(trackToPlay);

if (this.settings.showAlbumCoverInDock && this.application.getGlobal('isMacOS')) {
this.showDockAlbumArtwork(trackToPlay);
}

this.startUpdatingProgress();
this.playbackStarted.next(new PlaybackStarted(trackToPlay, isPlayingPreviousTrack));
Expand All @@ -407,6 +416,11 @@ export class PlaybackService {
this.preloadNextTrackAfterDelay();
}

private async showDockAlbumArtwork(track: TrackModel): Promise<void> {
const albumArtwork = await this.metadataService.getDockAlbumArtwork(track.albumKey);
remote.app.dock.setIcon(albumArtwork);
}

private preloadNextTrackAfterDelay(): void {
const nextTrack: TrackModel | undefined = this.queue.getNextTrack(this.currentTrack, this.loopMode === LoopMode.All);

Expand Down
1 change: 1 addition & 0 deletions src/app/testing/settings-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class SettingsMock implements SettingsBase {
public showRating: boolean;
public showTracksPage: boolean;
public showWelcome: boolean;
public showAlbumCoverInDock: boolean;
public skipRemovedFilesDuringRefresh: boolean;
public theme: string;
public tracksPageColumnsOrder: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<div class="title">{{ 'title-bar' | translate }}</div>
<app-toggle-switch [(isChecked)]="settings.useSystemTitleBar"> {{ 'use-system-title-bar' | translate }}</app-toggle-switch>
<mat-divider class="my-4"></mat-divider>
<div class="title">{{ 'dock-bar' | translate }}</div>
<app-toggle-switch [(isChecked)]="appearanceService.showAlbumCoverInDock"> {{ 'show-album-cover-in-dock' | translate }}</app-toggle-switch>
<mat-divider class="my-4"></mat-divider>
<div class="title">{{ 'language' | translate }}</div>
<div>{{ 'choose-language' | translate }}</div>
<mat-form-field class="mt-2">
Expand Down
2 changes: 2 additions & 0 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"follow-system-color": "Follow the system color",
"title-bar": "Title bar",
"use-system-title-bar": "Use system title bar (requires a restart)",
"dock-bar": "Dock",
"show-album-cover-in-dock": "Show album cover as the Dock icon (macOS only)",
"choose-language": "Choose a language",
"text-size": "Text size",
"choose-text-size": "Choose a text size",
Expand Down