mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3675146f1f | |||
| 946f4ff306 | |||
| 277669c413 | |||
| 49b6478b72 | |||
| ca39409cc3 | |||
| cca6fa21db | |||
| 5e1059870c | |||
| 6bac172bbe | |||
| 118a9f73d1 | |||
| c464be8cea | |||
| 3bbe696f4c | |||
| f7cacd2b73 | |||
| 62794623a3 |
Generated
+31
-31
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "feishin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
@@ -59,8 +59,8 @@
|
||||
"react-i18next": "^11.16.7",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-player": "^2.11.0",
|
||||
"react-router": "^6.5.0",
|
||||
"react-router-dom": "^6.5.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-simple-img": "^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.17",
|
||||
"react-window": "^1.8.9",
|
||||
@@ -4336,11 +4336,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0.tgz",
|
||||
"integrity": "sha512-rGl+jH/7x1KBCQScz9p54p0dtPLNeKGb3e0wD2H5/oZj41bwQUnXdzbj2TbUAFhvD7cp9EyEQA4dEgpUFa1O7Q==",
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz",
|
||||
"integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
@@ -17055,29 +17055,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.5.0.tgz",
|
||||
"integrity": "sha512-fqqUSU0NC0tSX0sZbyuxzuAzvGqbjiZItBQnyicWlOUmzhAU8YuLgRbaCL2hf3sJdtRy4LP/WBrWtARkMvdGPQ==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
|
||||
"integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.1.0"
|
||||
"@remix-run/router": "1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0.tgz",
|
||||
"integrity": "sha512-/XzRc5fq80gW1ctiIGilyKFZC/j4kfe75uivMsTChFbkvrK4ZrF3P3cGIc1f/SSkQ4JiJozPrf+AwUHHWVehVg==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz",
|
||||
"integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.1.0",
|
||||
"react-router": "6.5.0"
|
||||
"@remix-run/router": "1.9.0",
|
||||
"react-router": "6.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
@@ -24366,9 +24366,9 @@
|
||||
}
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.1.0.tgz",
|
||||
"integrity": "sha512-rGl+jH/7x1KBCQScz9p54p0dtPLNeKGb3e0wD2H5/oZj41bwQUnXdzbj2TbUAFhvD7cp9EyEQA4dEgpUFa1O7Q=="
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.9.0.tgz",
|
||||
"integrity": "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="
|
||||
},
|
||||
"@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
@@ -33977,20 +33977,20 @@
|
||||
}
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.5.0.tgz",
|
||||
"integrity": "sha512-fqqUSU0NC0tSX0sZbyuxzuAzvGqbjiZItBQnyicWlOUmzhAU8YuLgRbaCL2hf3sJdtRy4LP/WBrWtARkMvdGPQ==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
|
||||
"integrity": "sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.1.0"
|
||||
"@remix-run/router": "1.9.0"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.5.0.tgz",
|
||||
"integrity": "sha512-/XzRc5fq80gW1ctiIGilyKFZC/j4kfe75uivMsTChFbkvrK4ZrF3P3cGIc1f/SSkQ4JiJozPrf+AwUHHWVehVg==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.16.0.tgz",
|
||||
"integrity": "sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.1.0",
|
||||
"react-router": "6.5.0"
|
||||
"@remix-run/router": "1.9.0",
|
||||
"react-router": "6.16.0"
|
||||
}
|
||||
},
|
||||
"react-shallow-renderer": {
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
"name": "feishin",
|
||||
"productName": "Feishin",
|
||||
"description": "Feishin music server",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"scripts": {
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
|
||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||
@@ -305,8 +305,8 @@
|
||||
"react-i18next": "^11.16.7",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-player": "^2.11.0",
|
||||
"react-router": "^6.5.0",
|
||||
"react-router-dom": "^6.5.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-simple-img": "^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.17",
|
||||
"react-window": "^1.8.9",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "feishin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"description": "",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -112,18 +112,16 @@ ipcMain.on('player-set-queue', async (_event, data: PlayerData, pause?: boolean)
|
||||
|
||||
try {
|
||||
if (data.queue.current) {
|
||||
getMpvInstance()
|
||||
await getMpvInstance()
|
||||
?.load(data.queue.current.streamUrl, 'replace')
|
||||
.then(() => {
|
||||
// eslint-disable-next-line promise/always-return
|
||||
if (data.queue.next) {
|
||||
getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('MPV failed to load song', err);
|
||||
getMpvInstance()?.play();
|
||||
});
|
||||
|
||||
if (data.queue.next) {
|
||||
await getMpvInstance()?.load(data.queue.next.streamUrl, 'append');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
@@ -259,6 +259,11 @@ const createWindow = async () => {
|
||||
mainWindow?.close();
|
||||
});
|
||||
|
||||
ipcMain.on('window-quit', () => {
|
||||
mainWindow?.close();
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.on('app-restart', () => {
|
||||
// Fix for .AppImage
|
||||
if (process.env.APPIMAGE) {
|
||||
|
||||
@@ -3,16 +3,23 @@ import { ipcRenderer } from 'electron';
|
||||
const exit = () => {
|
||||
ipcRenderer.send('window-close');
|
||||
};
|
||||
|
||||
const maximize = () => {
|
||||
ipcRenderer.send('window-maximize');
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
ipcRenderer.send('window-minimize');
|
||||
};
|
||||
|
||||
const unmaximize = () => {
|
||||
ipcRenderer.send('window-unmaximize');
|
||||
};
|
||||
|
||||
const quit = () => {
|
||||
ipcRenderer.send('window-quit');
|
||||
};
|
||||
|
||||
const devtools = () => {
|
||||
ipcRenderer.send('window-dev-tools');
|
||||
};
|
||||
@@ -22,5 +29,6 @@ export const browser = {
|
||||
exit,
|
||||
maximize,
|
||||
minimize,
|
||||
quit,
|
||||
unmaximize,
|
||||
};
|
||||
|
||||
@@ -934,7 +934,7 @@ const getLyrics = async (args: LyricsArgs): Promise<LyricsResponse> => {
|
||||
}
|
||||
|
||||
if (res.body.Lyrics.length > 0 && res.body.Lyrics[0].Start === undefined) {
|
||||
return res.body.Lyrics[0].Text;
|
||||
return res.body.Lyrics.map((lyric) => lyric.Text).join('\n');
|
||||
}
|
||||
|
||||
return res.body.Lyrics.map((lyric) => [lyric.Start! / 1e4, lyric.Text]);
|
||||
|
||||
@@ -87,6 +87,7 @@ export const GENRE_TABLE_COLUMNS = [
|
||||
];
|
||||
|
||||
interface TableConfigDropdownProps {
|
||||
// tableRef?: MutableRefObject<AgGridReactType<any> | null>;
|
||||
type: TableType;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import { AlbumListSort, LibraryItem, QueueSong, SortOrder } from '/@/renderer/ap
|
||||
import { Button, Popover } from '/@/renderer/components';
|
||||
import { MemoizedSwiperGridCarousel } from '/@/renderer/components/grid-carousel';
|
||||
import {
|
||||
getColumnDefs,
|
||||
TableConfigDropdown,
|
||||
VirtualTable,
|
||||
getColumnDefs,
|
||||
} from '/@/renderer/components/virtual-table';
|
||||
import { FullWidthDiscCell } from '/@/renderer/components/virtual-table/cells/full-width-disc-cell';
|
||||
import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles';
|
||||
@@ -34,7 +34,11 @@ import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components
|
||||
import { useContainerQuery } from '/@/renderer/hooks';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { usePlayButtonBehavior, useTableSettings } from '/@/renderer/store/settings.store';
|
||||
import {
|
||||
usePlayButtonBehavior,
|
||||
useSettingsStoreActions,
|
||||
useTableSettings,
|
||||
} from '/@/renderer/store/settings.store';
|
||||
import { Play } from '/@/renderer/types';
|
||||
|
||||
const isFullWidthRow = (node: RowNode) => {
|
||||
@@ -65,16 +69,20 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
const cq = useContainerQuery();
|
||||
const handlePlayQueueAdd = usePlayQueueAdd();
|
||||
const tableConfig = useTableSettings('albumDetail');
|
||||
const { setTable } = useSettingsStoreActions();
|
||||
|
||||
const columnDefs = useMemo(() => getColumnDefs(tableConfig.columns), [tableConfig.columns]);
|
||||
|
||||
const getRowHeight = useCallback((params: RowHeightParams) => {
|
||||
if (isFullWidthRow(params.node)) {
|
||||
return 45;
|
||||
}
|
||||
const getRowHeight = useCallback(
|
||||
(params: RowHeightParams) => {
|
||||
if (isFullWidthRow(params.node)) {
|
||||
return 45;
|
||||
}
|
||||
|
||||
return 60;
|
||||
}, []);
|
||||
return tableConfig.rowHeight;
|
||||
},
|
||||
[tableConfig.rowHeight],
|
||||
);
|
||||
|
||||
const songsRowData = useMemo(() => {
|
||||
if (!detailQuery.data?.songs) {
|
||||
@@ -216,7 +224,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
});
|
||||
};
|
||||
|
||||
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
||||
const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, SONG_CONTEXT_MENU_ITEMS);
|
||||
|
||||
const handleRowDoubleClick = (e: RowDoubleClickedEvent<QueueSong>) => {
|
||||
if (!e.data || e.node.isFullWidthCell()) return;
|
||||
@@ -266,6 +274,32 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
ALBUM_CONTEXT_MENU_ITEMS,
|
||||
);
|
||||
|
||||
const onColumnMoved = useCallback(() => {
|
||||
const { columnApi } = tableRef?.current || {};
|
||||
const columnsOrder = columnApi?.getAllGridColumns();
|
||||
|
||||
if (!columnsOrder) return;
|
||||
|
||||
const columnsInSettings = tableConfig.columns;
|
||||
const updatedColumns = [];
|
||||
for (const column of columnsOrder) {
|
||||
const columnInSettings = columnsInSettings.find(
|
||||
(c) => c.column === column.getColDef().colId,
|
||||
);
|
||||
|
||||
if (columnInSettings) {
|
||||
updatedColumns.push({
|
||||
...columnInSettings,
|
||||
...(!tableConfig.autoFit && {
|
||||
width: column.getActualWidth(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setTable('albumDetail', { ...tableConfig, columns: updatedColumns });
|
||||
}, [setTable, tableConfig, tableRef]);
|
||||
|
||||
const { rowClassRules } = useCurrentSongRowStyles({ tableRef });
|
||||
|
||||
return (
|
||||
@@ -352,6 +386,7 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
)}
|
||||
<Box style={{ minHeight: '300px' }}>
|
||||
<VirtualTable
|
||||
key={`table-${tableConfig.rowHeight}`}
|
||||
ref={tableRef}
|
||||
autoHeight
|
||||
stickyHeader
|
||||
@@ -360,6 +395,9 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
suppressRowDrag
|
||||
autoFitColumns={tableConfig.autoFit}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
onCellContextMenu,
|
||||
}}
|
||||
enableCellChangeFlash={false}
|
||||
fullWidthCellRenderer={FullWidthDiscCell}
|
||||
getRowHeight={getRowHeight}
|
||||
@@ -374,7 +412,8 @@ export const AlbumDetailContent = ({ tableRef, background }: AlbumDetailContentP
|
||||
rowClassRules={rowClassRules}
|
||||
rowData={songsRowData}
|
||||
rowSelection="multiple"
|
||||
onCellContextMenu={handleContextMenu}
|
||||
onCellContextMenu={onCellContextMenu}
|
||||
onColumnMoved={onColumnMoved}
|
||||
onRowDoubleClicked={handleRowDoubleClick}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -18,6 +18,14 @@ const UnsynchronizedLyricsContainer = styled.div<{ $gap: number }>`
|
||||
overflow: scroll;
|
||||
transform: translateY(-2rem);
|
||||
|
||||
-webkit-mask-image: linear-gradient(
|
||||
180deg,
|
||||
transparent 5%,
|
||||
rgb(0 0 0 / 100%) 20%,
|
||||
rgb(0 0 0 / 100%) 85%,
|
||||
transparent 95%
|
||||
);
|
||||
|
||||
mask-image: linear-gradient(
|
||||
180deg,
|
||||
transparent 5%,
|
||||
|
||||
@@ -206,7 +206,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||
}
|
||||
}, [currentSong, previousSong, tableConfig.followCurrentSong]);
|
||||
|
||||
const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, QUEUE_CONTEXT_MENU_ITEMS);
|
||||
const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, QUEUE_CONTEXT_MENU_ITEMS);
|
||||
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
@@ -218,6 +218,9 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||
rowDragMultiRow
|
||||
autoFitColumns={tableConfig.autoFit}
|
||||
columnDefs={columnDefs}
|
||||
context={{
|
||||
onCellContextMenu,
|
||||
}}
|
||||
deselectOnClickOutside={type === 'fullScreen'}
|
||||
getRowId={(data) => data.data.uniqueId}
|
||||
rowBuffer={50}
|
||||
@@ -225,7 +228,7 @@ export const PlayQueue = forwardRef(({ type }: QueueProps, ref: Ref<any>) => {
|
||||
rowData={queue}
|
||||
rowHeight={tableConfig.rowHeight || 40}
|
||||
suppressCellFocus={type === 'fullScreen'}
|
||||
onCellContextMenu={handleContextMenu}
|
||||
onCellContextMenu={onCellContextMenu}
|
||||
onCellDoubleClicked={handleDoubleClick}
|
||||
onColumnMoved={handleColumnChange}
|
||||
onColumnResized={debouncedColumnChange}
|
||||
|
||||
@@ -34,20 +34,21 @@ Progress Events (Jellyfin only):
|
||||
*/
|
||||
|
||||
const checkScrobbleConditions = (args: {
|
||||
scrobbleAtDuration: number;
|
||||
scrobbleAtDurationMs: number;
|
||||
scrobbleAtPercentage: number;
|
||||
songCompletedDuration: number;
|
||||
songDuration: number;
|
||||
songCompletedDurationMs: number;
|
||||
songDurationMs: number;
|
||||
}) => {
|
||||
const { scrobbleAtDuration, scrobbleAtPercentage, songCompletedDuration, songDuration } = args;
|
||||
const percentageOfSongCompleted = songDuration
|
||||
? (songCompletedDuration / songDuration) * 100
|
||||
const { scrobbleAtDurationMs, scrobbleAtPercentage, songCompletedDurationMs, songDurationMs } =
|
||||
args;
|
||||
const percentageOfSongCompleted = songDurationMs
|
||||
? (songCompletedDurationMs / songDurationMs) * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
percentageOfSongCompleted >= scrobbleAtPercentage ||
|
||||
songCompletedDuration >= scrobbleAtDuration
|
||||
);
|
||||
const shouldScrobbleBasedOnPercetange = percentageOfSongCompleted >= scrobbleAtPercentage;
|
||||
const shouldScrobbleBasedOnDuration = songCompletedDurationMs >= scrobbleAtDurationMs;
|
||||
|
||||
return shouldScrobbleBasedOnPercetange || shouldScrobbleBasedOnDuration;
|
||||
};
|
||||
|
||||
export const useScrobble = () => {
|
||||
@@ -97,15 +98,15 @@ export const useScrobble = () => {
|
||||
|
||||
// const currentSong = current[0] as QueueSong | undefined;
|
||||
const previousSong = previous[0] as QueueSong;
|
||||
const previousSongTime = previous[1] as number;
|
||||
const previousSongTimeSec = previous[1] as number;
|
||||
|
||||
// Send completion scrobble when song changes and a previous song exists
|
||||
if (previousSong?.id) {
|
||||
const shouldSubmitScrobble = checkScrobbleConditions({
|
||||
scrobbleAtDuration: scrobbleSettings?.scrobbleAtDuration,
|
||||
scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000,
|
||||
scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage,
|
||||
songCompletedDuration: previousSongTime,
|
||||
songDuration: previousSong.duration,
|
||||
songCompletedDurationMs: previousSongTimeSec * 1000,
|
||||
songDurationMs: previousSong.duration,
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -114,7 +115,7 @@ export const useScrobble = () => {
|
||||
) {
|
||||
const position =
|
||||
previousSong?.serverType === ServerType.JELLYFIN
|
||||
? previousSongTime * 1e7
|
||||
? previousSongTimeSec * 1e7
|
||||
: undefined;
|
||||
|
||||
sendScrobble.mutate({
|
||||
@@ -168,7 +169,10 @@ export const useScrobble = () => {
|
||||
);
|
||||
|
||||
const handleScrobbleFromStatusChange = useCallback(
|
||||
(status: PlayerStatus | undefined) => {
|
||||
(
|
||||
current: (PlayerStatus | number | undefined)[],
|
||||
previous: (PlayerStatus | number | undefined)[],
|
||||
) => {
|
||||
if (!isScrobbleEnabled) return;
|
||||
|
||||
const currentSong = usePlayerStore.getState().current.song;
|
||||
@@ -180,8 +184,11 @@ export const useScrobble = () => {
|
||||
? usePlayerStore.getState().current.time * 1e7
|
||||
: undefined;
|
||||
|
||||
const currentStatus = current[0] as PlayerStatus;
|
||||
const currentTimeSec = current[1] as number;
|
||||
|
||||
// Whenever the player is restarted, send a 'start' scrobble
|
||||
if (status === PlayerStatus.PLAYING) {
|
||||
if (currentStatus === PlayerStatus.PLAYING) {
|
||||
sendScrobble.mutate({
|
||||
query: {
|
||||
event: 'unpause',
|
||||
@@ -194,7 +201,7 @@ export const useScrobble = () => {
|
||||
|
||||
if (currentSong?.serverType === ServerType.JELLYFIN) {
|
||||
progressIntervalId.current = setInterval(() => {
|
||||
const currentTime = usePlayerStore.getState().current.time;
|
||||
const currentTime = currentTimeSec;
|
||||
handleScrobbleFromSeek(currentTime);
|
||||
}, 10000);
|
||||
}
|
||||
@@ -215,12 +222,17 @@ export const useScrobble = () => {
|
||||
clearInterval(progressIntervalId.current as ReturnType<typeof setInterval>);
|
||||
}
|
||||
} else {
|
||||
const isLastTrackInQueue = usePlayerStore.getState().actions.checkIsLastTrack();
|
||||
const previousTimeSec = previous[1] as number;
|
||||
|
||||
// If not already scrobbled, send a 'submission' scrobble if conditions are met
|
||||
const shouldSubmitScrobble = checkScrobbleConditions({
|
||||
scrobbleAtDuration: scrobbleSettings?.scrobbleAtDuration,
|
||||
scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000,
|
||||
scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage,
|
||||
songCompletedDuration: usePlayerStore.getState().current.time,
|
||||
songDuration: currentSong.duration,
|
||||
// If scrobbling the last song in the queue, use the previous time instead of the current time since otherwise time value will be 0
|
||||
songCompletedDurationMs:
|
||||
(isLastTrackInQueue ? previousTimeSec : currentTimeSec) * 1000,
|
||||
songDurationMs: currentSong.duration,
|
||||
});
|
||||
|
||||
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
||||
@@ -261,10 +273,10 @@ export const useScrobble = () => {
|
||||
currentSong?.serverType === ServerType.JELLYFIN ? currentTime * 1e7 : undefined;
|
||||
|
||||
const shouldSubmitScrobble = checkScrobbleConditions({
|
||||
scrobbleAtDuration: scrobbleSettings?.scrobbleAtDuration,
|
||||
scrobbleAtDurationMs: (scrobbleSettings?.scrobbleAtDuration ?? 0) * 1000,
|
||||
scrobbleAtPercentage: scrobbleSettings?.scrobbleAtPercentage,
|
||||
songCompletedDuration: currentTime,
|
||||
songDuration: currentSong.duration,
|
||||
songCompletedDurationMs: currentTime,
|
||||
songDurationMs: currentSong.duration,
|
||||
});
|
||||
|
||||
if (!isCurrentSongScrobbled && shouldSubmitScrobble) {
|
||||
@@ -313,8 +325,11 @@ export const useScrobble = () => {
|
||||
);
|
||||
|
||||
const unsubStatusChange = usePlayerStore.subscribe(
|
||||
(state) => state.current.status,
|
||||
(state) => [state.current.status, state.current.time],
|
||||
handleScrobbleFromStatusChange,
|
||||
{
|
||||
equalityFn: (a, b) => (a[0] as PlayerStatus) === (b[0] as PlayerStatus),
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -7,23 +7,23 @@ interface AnimatedPageProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const variants = {
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 },
|
||||
initial: { opacity: 0 },
|
||||
};
|
||||
// const variants = {
|
||||
// animate: { opacity: 1 },
|
||||
// exit: { opacity: 0 },
|
||||
// initial: { opacity: 0 },
|
||||
// };
|
||||
|
||||
export const AnimatedPage = forwardRef(
|
||||
({ children }: AnimatedPageProps, ref: Ref<HTMLDivElement>) => {
|
||||
return (
|
||||
<motion.main
|
||||
ref={ref}
|
||||
animate="animate"
|
||||
// animate="animate"
|
||||
className={styles.animatedPage}
|
||||
exit="exit"
|
||||
initial="initial"
|
||||
transition={{ duration: 0.3, ease: 'easeIn' }}
|
||||
variants={variants}
|
||||
// exit="exit"
|
||||
// initial="initial"
|
||||
// transition={{ duration: 0.3, ease: 'easeIn' }}
|
||||
// variants={variants}
|
||||
>
|
||||
{children}
|
||||
</motion.main>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const SongListHeader = ({ gridRef, title, itemCount, tableRef }: SongList
|
||||
const { display, filter } = useListStoreByKey({ key: pageKey });
|
||||
const cq = useContainerQuery();
|
||||
|
||||
const { handleRefreshTable } = useListFilterRefresh({
|
||||
const { handleRefreshTable, handleRefreshGrid } = useListFilterRefresh({
|
||||
itemType: LibraryItem.SONG,
|
||||
server,
|
||||
});
|
||||
@@ -51,7 +51,7 @@ export const SongListHeader = ({ gridRef, title, itemCount, tableRef }: SongList
|
||||
handleRefreshTable(tableRef, filterWithCustom);
|
||||
setTablePagination({ data: { currentPage: 0 }, key: pageKey });
|
||||
} else {
|
||||
// handleRefreshGrid(gridRef, filterWithCustom);
|
||||
handleRefreshGrid(gridRef, filterWithCustom);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export const AppMenu = () => {
|
||||
};
|
||||
|
||||
const handleQuit = () => {
|
||||
browser?.exit();
|
||||
browser?.quit();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import {
|
||||
Route,
|
||||
createRoutesFromElements,
|
||||
RouterProvider,
|
||||
createBrowserRouter,
|
||||
createHashRouter,
|
||||
} from 'react-router-dom';
|
||||
import { AppRoute } from './routes';
|
||||
@@ -68,10 +66,8 @@ const RouteErrorBoundary = lazy(
|
||||
() => import('/@/renderer/features/action-required/components/route-error-boundary'),
|
||||
);
|
||||
|
||||
const dynamicRouter = isElectron() ? createHashRouter : createBrowserRouter;
|
||||
|
||||
export const AppRouter = () => {
|
||||
const router = dynamicRouter(
|
||||
const router = createHashRouter(
|
||||
createRoutesFromElements(
|
||||
<>
|
||||
<Route element={<TitlebarOutlet />}>
|
||||
@@ -198,7 +194,10 @@ export const AppRouter = () => {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<></>}>
|
||||
<RouterProvider router={router} />
|
||||
<RouterProvider
|
||||
future={{ v7_startTransition: true }}
|
||||
router={router}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -196,6 +196,7 @@ export interface SettingsSlice extends SettingsState {
|
||||
reset: () => void;
|
||||
setSettings: (data: Partial<SettingsState>) => void;
|
||||
setSidebarItems: (items: SidebarItemType[]) => void;
|
||||
setTable: (type: TableType, data: DataTableProps) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -493,6 +494,11 @@ export const useSettingsStore = create<SettingsSlice>()(
|
||||
state.general.sidebarItems = items;
|
||||
});
|
||||
},
|
||||
setTable: (type: TableType, data: DataTableProps) => {
|
||||
set((state) => {
|
||||
state.tables[type] = data;
|
||||
});
|
||||
},
|
||||
},
|
||||
...initialState,
|
||||
})),
|
||||
|
||||
Reference in New Issue
Block a user