Skip to content

Commit

Permalink
Feature: Gapless Playback with Gapless-5 (#41)
Browse files Browse the repository at this point in the history
1. Moved as much of the player handling into the store as possible
2. Fixed a minor bug with the placeholder album art being the wrong size
3. Updated "previous" behavior to match standard music players with regards to song restarting
4. Updates our time estimation code for importing a new library
5. Move quiet mode to the menu to declutter the static player a bit
6. Shuffle history contains the songs a user manually selects while shuffle is engaged
7. Shuffle history contains the song that was playing when user engaged shuffle
8. Fixes the flash of full progress bar when importing songs or swapping libs
9. Makes playcounts more intelligently tracked, requires 10s of the song to "play"
10. Add volume controls to the hihat menu with accelerators for convenience aka key commands
  • Loading branch information
johnnyshankman authored Nov 22, 2024
1 parent f68b085 commit 73ce834
Show file tree
Hide file tree
Showing 25 changed files with 1,050 additions and 592 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module.exports = {
],
// Add these new rules
'react/jsx-sort-props': ['error'],
// allow console.error and console.warn
'no-console': ['error', { allow: ['error', 'warn'] }],
},
parserOptions: {
ecmaVersion: 2022,
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ This project was built with the following technologies:
* [![zustand][zustand.js]][zustand-url]
* [![Tailwind][Tailwind.js]][Tailwind-url]
* [![Music Metadata][MusicMetadata.js]][MusicMetadata-url]
* [![Gapless 5][Gapless5.js]][Gapless5-url]

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Expand Down Expand Up @@ -278,7 +279,7 @@ This project was built with the following technologies:
- [x] Show stats about your library somewhere, like GB and # of songs
- [x] iTunes-like browser with list of artist and album filters
- [x] Quiet mode creating bg music to a video/audiobook/podcast in another app
- [ ] True gapless playback
- [x] True gapless playback
- [ ] Edit song metadata
- [ ] Hide/show columns in the Library List
- [ ] Queue a next-up song
Expand Down Expand Up @@ -399,6 +400,8 @@ Project Link: [https://github.com/johnnyshankman/hihat](https://github.com/johnn
[Typescript-url]: https://typescriptlang.org
[zustand.js]: https://img.shields.io/badge/Zustand-20232A?style=for-the-badge&logo=javascript&logoColor=007ACC
[zustand-url]: [https://typescriptlang.org](https://github.com/pmndrs/zustand)https://github.com/pmndrs/zustand
[Gapless5.js]: https://img.shields.io/badge/Gapless5-20232A?style=for-the-badge&logo=javascript&logoColor=007ACC
[Gapless5-url]: https://github.com/regosen/Gapless-5



6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
}
},
"dependencies": {
"@regosen/gapless-5": "git+https://github.com/johnnyshankman/Gapless-5.git",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
"electron-updater": "^6.1.4",
Expand Down
Binary file added regosen-gapless-5-1.5.6.tgz
Binary file not shown.
4 changes: 2 additions & 2 deletions release/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hihat",
"version": "1.0.0",
"version": "1.1.0",
"description": "A minimalist offline music player for OSX audiophiles :: Based on iTunes circa 2002 :: by White Lights",
"license": "MIT",
"author": {
Expand Down
13 changes: 11 additions & 2 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,6 @@ ipcMain.on('set-last-played-song', async (event, arg: string) => {
Object.keys(userConfig.library).forEach((key) => {
if (key === songFilePath) {
userConfig.library[key].additionalInfo.lastPlayed = Date.now();
userConfig.library[key].additionalInfo.playCount += 1;
}
});

Expand All @@ -471,6 +470,16 @@ ipcMain.on('set-last-played-song', async (event, arg: string) => {
});
});

/**
* @dev increments the play count of the requested song
* @note expected the store will manually add 1 to the internal play count until reboot
*/
ipcMain.on('increment-play-count', async (event, arg: { song: string }) => {
const userConfig = getUserConfig();
userConfig.library[arg.song].additionalInfo.playCount += 1;
writeFileSyncToUserConfig(userConfig);
});

/**
* @dev for requesting the modification of a tag of a media file
* and then modifying it in the app as well as cache'ing it
Expand Down Expand Up @@ -947,7 +956,7 @@ const createWindow = async () => {
});

/**
* @importnat Set the global asset path to /assets
* @important Set the global asset path to /assets
*/
const RESOURCES_PATH = app.isPackaged
? path.join(process.resourcesPath, 'assets')
Expand Down
79 changes: 56 additions & 23 deletions src/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default class MenuBuilder {
}

buildDarwinTemplate(): MenuItemConstructorOptions[] {
const subMenuAbout: DarwinMenuItemConstructorOptions = {
const subMenuHihat: DarwinMenuItemConstructorOptions = {
label: 'hihat',
submenu: [
{
Expand All @@ -121,38 +121,30 @@ export default class MenuBuilder {
this.mainWindow.webContents.send('menu-rescan-library');
},
},
{ type: 'separator' },
{
label: 'select library folder',
click: () => {
this.mainWindow.webContents.send('menu-select-library');
},
},
{
label: 'hide duplicate songs',
label: 'max volume',
accelerator: 'Command+Up',
click: () => {
this.mainWindow.webContents.send('menu-hide-dupes');
this.mainWindow.webContents.send('menu-max-volume');
},
},
{
label: 'delete duplicate songs',
label: 'mute volume',
accelerator: 'Command+Down',
click: () => {
this.mainWindow.webContents.send('menu-delete-dupes');
this.mainWindow.webContents.send('menu-mute-volume');
},
},
{
label: 'backup / sync library',
label: 'quiet',
accelerator: 'Option+Command+Down',
click: () => {
this.mainWindow.webContents.send('menu-backup-library');
this.mainWindow.webContents.send('menu-quiet-mode');
},
},
// {
// label: 'reset all hihat data',
// click: () => {
// this.mainWindow.webContents.send('menu-reset-library');
// },
// },
{ type: 'separator' },

{ type: 'separator' },
{
label: 'see library stats',
click: () => {
Expand All @@ -166,9 +158,7 @@ export default class MenuBuilder {
});
},
},

{ type: 'separator' },

{
label: 'hide hihat',
accelerator: 'Command+H',
Expand Down Expand Up @@ -238,6 +228,42 @@ export default class MenuBuilder {
this.mainWindow.webContents.send('menu-toggle-browser');
},
},
{
label: 'reset all hihat data',
click: () => {
this.mainWindow.webContents.send('menu-reset-library');
},
},
],
};
const subMenuAdvanced: DarwinMenuItemConstructorOptions = {
label: 'Advanced',
submenu: [
{
label: 'change library folder',
click: () => {
this.mainWindow.webContents.send('menu-select-library');
},
},
{
label: 'backup / sync library',
click: () => {
this.mainWindow.webContents.send('menu-backup-library');
},
},
{ type: 'separator' },
{
label: 'hide duplicate songs',
click: () => {
this.mainWindow.webContents.send('menu-hide-dupes');
},
},
{
label: 'delete duplicate songs',
click: () => {
this.mainWindow.webContents.send('menu-delete-dupes');
},
},
],
};
const subMenuViewProd: MenuItemConstructorOptions = {
Expand Down Expand Up @@ -299,7 +325,14 @@ export default class MenuBuilder {
? subMenuViewDev
: subMenuViewProd;

return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
return [
subMenuHihat,
subMenuEdit,
subMenuView,
subMenuAdvanced,
subMenuWindow,
subMenuHelp,
];
}

buildDefaultTemplate() {
Expand Down
10 changes: 10 additions & 0 deletions src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ export type Channels =
| 'menu-reset-library'
| 'menu-hide-dupes'
| 'menu-delete-dupes'
| 'menu-max-volume'
| 'menu-quiet-mode'
| 'menu-mute-volume'
| 'song-imported'
| 'hide-song'
| 'delete-song'
| 'delete-album'
| 'menu-toggle-browser'
| 'increment-play-count'
| 'update-store';

export type ArgsBase = Record<Channels, unknown>;
Expand Down Expand Up @@ -64,6 +68,9 @@ export interface SendMessageArgs extends ArgsBase {
'show-in-finder': {
path: string;
};
'increment-play-count': {
song: string;
};
}

export interface ResponseArgs extends ArgsBase {
Expand All @@ -84,6 +91,9 @@ export interface ResponseArgs extends ArgsBase {
scrollToIndex?: number;
};
'backup-library-success': undefined;
'menu-quiet-mode': undefined;
'menu-max-volume': undefined;
'menu-mute-volume': undefined;
}

const electronHandler = {
Expand Down
46 changes: 38 additions & 8 deletions src/renderer/components/AlbumArt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import Draggable from 'react-draggable';
import { TinyText } from './SimpleStyledMaterialUIComponents';
import AlbumArtRightClickMenu from './AlbumArtRightClickMenu';
import usePlayerStore from '../store/player';
import useMainStore from '../store/main';
import { useWindowDimensions } from '../hooks/useWindowDimensions';

interface AlbumArtProps {
Expand All @@ -14,20 +14,23 @@ export default function AlbumArt({
setShowAlbumArtMenu,
showAlbumArtMenu,
}: AlbumArtProps) {
/**
* @dev window provider hook
*/
const { width, height } = useWindowDimensions();

/**
* @dev global store hooks
* @dev store hooks
*/
const currentSong = usePlayerStore((store) => store.currentSong);
const currentSongMetadata = usePlayerStore(
const currentSong = useMainStore((store) => store.currentSong);
const currentSongMetadata = useMainStore(
(store) => store.currentSongMetadata,
);
const currentSongDataURL = usePlayerStore(
const currentSongDataURL = useMainStore(
(store) => store.currentSongArtworkDataURL,
);
const filteredLibrary = usePlayerStore((store) => store.filteredLibrary);
const setOverrideScrollToIndex = usePlayerStore(
const filteredLibrary = useMainStore((store) => store.filteredLibrary);
const setOverrideScrollToIndex = useMainStore(
(store) => store.setOverrideScrollToIndex,
);

Expand Down Expand Up @@ -60,7 +63,7 @@ export default function AlbumArt({
if (!currentSongDataURL) {
return (
<div
className="relative aspect-square w-[40%] bg-gradient-to-r from-neutral-800 via-neutral-700 to-neutral-600 border-2 border-neutral-700 shadow-2xl rounded-lg transition-all duration-200"
className="relative aspect-square w-full bg-gradient-to-r from-neutral-800 via-neutral-700 to-neutral-600 border-2 border-neutral-700 shadow-2xl rounded-lg"
style={{
maxWidth: `${albumArtMaxWidth}px`,
}}
Expand All @@ -84,6 +87,33 @@ export default function AlbumArt({
</svg>
<TinyText className="absolute bottom-3 left-3">hihat</TinyText>
</div>
<Draggable
axis="y"
onDrag={(e, data) => {
if (!width || !height) return;
const newMaxWidth = albumArtMaxWidth + data.deltaY;
const maxWidthBasedOnWidth = Math.min(320, width * 0.4);
const maxWidthBasedOnHeight = Math.min(320, height * 0.6);
const clampedMaxWidth = Math.max(
120,
Math.min(
newMaxWidth,
maxWidthBasedOnWidth,
maxWidthBasedOnHeight,
),
);
setAlbumArtMaxWidth(clampedMaxWidth);

/**
* @important dispatch an event to let all components know the width has changed
* @see LibraryList.tsx
*/
window.dispatchEvent(new Event('album-art-width-changed'));
}}
position={{ x: 0, y: 0 }}
>
<div className="w-full absolute bottom-0 h-[20px] bg-transparent cursor-ns-resize hover:cursor-ns-resize" />
</Draggable>
</div>
);
}
Expand Down
Loading

0 comments on commit 73ce834

Please sign in to comment.