mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-22 10:26:33 +02:00
Compare commits
12 Commits
v1.12.1
...
7243ed7f15
| Author | SHA1 | Date | |
|---|---|---|---|
| 7243ed7f15 | |||
| 7e9a78898f | |||
| 6aab8d4121 | |||
| 70594a696b | |||
| 08b4c620f2 | |||
| 7a20cf3853 | |||
| dd186c570f | |||
| d5e9d491b6 | |||
| 28dc822e4f | |||
| def1b1e710 | |||
| 2ff9e4b0a2 | |||
| 49bfc907cd |
@@ -12,7 +12,8 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5
|
- uses: dessant/lock-threads@v6
|
||||||
|
|
||||||
with:
|
with:
|
||||||
process-only: 'issues, prs'
|
process-only: 'issues, prs'
|
||||||
issue-inactive-days: 120
|
issue-inactive-days: 120
|
||||||
|
|||||||
@@ -296,7 +296,7 @@
|
|||||||
"releaseChannel": "Kanál vydání",
|
"releaseChannel": "Kanál vydání",
|
||||||
"releaseChannel_description": "Vyberte si mezi stabilními, beta nebo alpha (nočními) vydáními pro automatické aktualizace",
|
"releaseChannel_description": "Vyberte si mezi stabilními, beta nebo alpha (nočními) vydáními pro automatické aktualizace",
|
||||||
"mediaSession": "Povolit relaci médií",
|
"mediaSession": "Povolit relaci médií",
|
||||||
"mediaSession_description": "Povolí integraci do služby Media Session, což zobrazí ovládání a metadata médií v překrytí systémové hlasitosti a na zamykací obrazovce",
|
"mediaSession_description": "Povolí integraci do služby Media Session, což zobrazí ovládání a metadata médií v překrytí systémové hlasitosti a na zamykací obrazovce. Vyžaduje webový přehrávač zvuku.",
|
||||||
"exportImportSettings_control_description": "Exportovat a importovat nastavení pomocí souboru JSON",
|
"exportImportSettings_control_description": "Exportovat a importovat nastavení pomocí souboru JSON",
|
||||||
"exportImportSettings_control_exportText": "Exportovat nastavení",
|
"exportImportSettings_control_exportText": "Exportovat nastavení",
|
||||||
"exportImportSettings_control_importText": "Importovat nastavení",
|
"exportImportSettings_control_importText": "Importovat nastavení",
|
||||||
|
|||||||
@@ -1093,7 +1093,7 @@
|
|||||||
"sidePlayQueueLayout_description": "Sets the layout of the attached side play queue",
|
"sidePlayQueueLayout_description": "Sets the layout of the attached side play queue",
|
||||||
"sidePlayQueueLayout_optionHorizontal": "Horizontal",
|
"sidePlayQueueLayout_optionHorizontal": "Horizontal",
|
||||||
"sidePlayQueueLayout_optionVertical": "Vertical",
|
"sidePlayQueueLayout_optionVertical": "Vertical",
|
||||||
"mediaSession_description": "Enables media session integration, displaying media controls and metadata in the system volume overlay and lock screen",
|
"mediaSession_description": "Enables media session integration, displaying media controls and metadata in the system volume overlay and lock screen. Requires the Web Audio Player.",
|
||||||
"mediaSession": "Enable media session",
|
"mediaSession": "Enable media session",
|
||||||
"sidePlayQueueStyle": "Side play queue style",
|
"sidePlayQueueStyle": "Side play queue style",
|
||||||
"skipDuration_description": "Sets the duration to skip when using the skip buttons on the player bar",
|
"skipDuration_description": "Sets the duration to skip when using the skip buttons on the player bar",
|
||||||
|
|||||||
@@ -296,7 +296,7 @@
|
|||||||
"releaseChannel_description": "Elige entre lanzamientos estables, beta, o alpha (nightly) para las actualizaciones automáticas",
|
"releaseChannel_description": "Elige entre lanzamientos estables, beta, o alpha (nightly) para las actualizaciones automáticas",
|
||||||
"artistBackground_description": "Añade una imagen de fondo para las páginas de artistas que contienen el arte de los artistas",
|
"artistBackground_description": "Añade una imagen de fondo para las páginas de artistas que contienen el arte de los artistas",
|
||||||
"mediaSession": "Activar sesión de medios",
|
"mediaSession": "Activar sesión de medios",
|
||||||
"mediaSession_description": "Activa la integración de la sesión de medios, mostrando los controles de medios y los metadatos en la superposición del volumen del sistema y en la pantalla de bloqueo",
|
"mediaSession_description": "Activa la integración de la sesión de medios, mostrando los controles de medios y los metadatos en la superposición del volumen del sistema y en la pantalla de bloqueo. Requiere el Reproductor Web de Audio.",
|
||||||
"exportImportSettings_control_description": "Exporta e importa la configuración a través de JSON",
|
"exportImportSettings_control_description": "Exporta e importa la configuración a través de JSON",
|
||||||
"exportImportSettings_control_exportText": "Exportar configuración",
|
"exportImportSettings_control_exportText": "Exportar configuración",
|
||||||
"exportImportSettings_control_importText": "Importar configuración",
|
"exportImportSettings_control_importText": "Importar configuración",
|
||||||
|
|||||||
@@ -979,7 +979,7 @@
|
|||||||
"preservePitch": "Utrzymuj ton",
|
"preservePitch": "Utrzymuj ton",
|
||||||
"preventSleepOnPlayback_description": "Powstrzymuje ekran przed uśpieniem, gdy muzyka jest odtwarzana",
|
"preventSleepOnPlayback_description": "Powstrzymuje ekran przed uśpieniem, gdy muzyka jest odtwarzana",
|
||||||
"preventSleepOnPlayback": "Powstrzymuj uśpienie podczas odtwarzania",
|
"preventSleepOnPlayback": "Powstrzymuj uśpienie podczas odtwarzania",
|
||||||
"mediaSession_description": "Włącza integrację z Media Session, wyświetlając sterowanie mediami i metadane w systemowym oknie zmiany głośności i na ekranie blokady",
|
"mediaSession_description": "Włącza integrację z Media Session, wyświetlając sterowanie mediami i metadane w systemowym oknie zmiany głośności i na ekranie blokad. Wymaga odtwarzacza web audio.",
|
||||||
"mediaSession": "Włącz media session",
|
"mediaSession": "Włącz media session",
|
||||||
"transcode": "Włącz transkodowanie",
|
"transcode": "Włącz transkodowanie",
|
||||||
"queryBuilder": "Kreator zaptań",
|
"queryBuilder": "Kreator zaptań",
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"action": {
|
||||||
|
"addToFavorites": "Idagdag sa $t(entity.favorite, {\"count\": 2})",
|
||||||
|
"addToPlaylist": "Idagdag sa $t(entity.playlist, {\"count\": 1})",
|
||||||
|
"addOrRemoveFromSelection": "Idagdag o alisin sa pinili",
|
||||||
|
"collapseAllFolders": "Isara lahat ng mga folder",
|
||||||
|
"expandAllFolders": "Buksan lahat ng mga folder",
|
||||||
|
"createPlaylist": "Gumawa $t(entity.playlist, {\"count\": 1})",
|
||||||
|
"createRadioStation": "Gumawa $t(entity.radioStation, {\"count\": 1})",
|
||||||
|
"selectAll": "Piliin lahat",
|
||||||
|
"deselectAll": "Huwag piliin lahat",
|
||||||
|
"downloadStarted": "Nagsimulang mag-dowload ng {{count}} (mga) aytem"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
"preventSleepOnPlayback": "防止播放時進入睡眠狀態",
|
"preventSleepOnPlayback": "防止播放時進入睡眠狀態",
|
||||||
"preventSleepOnPlayback_description": "在音樂播放時防止螢幕進入睡眠狀態",
|
"preventSleepOnPlayback_description": "在音樂播放時防止螢幕進入睡眠狀態",
|
||||||
"mediaSession": "啟用 Media Session",
|
"mediaSession": "啟用 Media Session",
|
||||||
"mediaSession_description": "啟用 Media Session 整合功能,於系統音量 Overlay 和鎖定畫面中顯示媒體資料與控制面板",
|
"mediaSession_description": "啟用 Media Session 整合功能,在系統音量疊加層和鎖定畫面上顯示媒體控制項與中繼資料。此功能需要使用網頁播放器。",
|
||||||
"releaseChannel": "發佈通道",
|
"releaseChannel": "發佈通道",
|
||||||
"analyticsDisable": "選擇退出使用情況分析",
|
"analyticsDisable": "選擇退出使用情況分析",
|
||||||
"analyticsDisable_description": "經過匿名處理的使用情況資料將傳送給開發者,以協助改進應用程式",
|
"analyticsDisable_description": "經過匿名處理的使用情況資料將傳送給開發者,以協助改進應用程式",
|
||||||
|
|||||||
+1
-1
@@ -1039,7 +1039,7 @@ if (!singleInstance) {
|
|||||||
responseHeaders: {
|
responseHeaders: {
|
||||||
...details.responseHeaders,
|
...details.responseHeaders,
|
||||||
'Content-Security-Policy': [
|
'Content-Security-Policy': [
|
||||||
"script-src 'self' 'unsafe-inline' https://umami.jeffvli.org; style-src 'self' 'unsafe-inline'; media-src 'self' http: https: data: blob:; img-src 'self' http: https: data: blob:; connect-src 'self' http: https: ws: wss:; default-src 'self';",
|
"script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' https://umami.jeffvli.org; style-src 'self' 'unsafe-inline'; media-src 'self' http: https: data: blob:; img-src 'self' http: https: data: blob:; connect-src 'self' http: https: ws: wss:; default-src 'self';",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -366,7 +366,10 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
|
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
|
||||||
? query.id
|
? query.id
|
||||||
: undefined,
|
: undefined,
|
||||||
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
id:
|
||||||
|
query.type === LibraryItem.SONG || query.type === LibraryItem.PLAYLIST_SONG
|
||||||
|
? query.id
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -419,7 +422,10 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
|
query.type === LibraryItem.ALBUM_ARTIST || query.type === LibraryItem.ARTIST
|
||||||
? query.id
|
? query.id
|
||||||
: undefined,
|
: undefined,
|
||||||
id: query.type === LibraryItem.SONG ? query.id : undefined,
|
id:
|
||||||
|
query.type === LibraryItem.SONG || query.type === LibraryItem.PLAYLIST_SONG
|
||||||
|
? query.id
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2327,7 +2333,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
|||||||
case 'start':
|
case 'start':
|
||||||
state = 'starting';
|
state = 'starting';
|
||||||
break;
|
break;
|
||||||
case 'timeupdate':
|
|
||||||
case 'unpause':
|
case 'unpause':
|
||||||
state = 'playing';
|
state = 'playing';
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -4,50 +4,38 @@ import { generatePath, useNavigate } from 'react-router';
|
|||||||
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
||||||
import {
|
import { Album, LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types';
|
||||||
Album,
|
|
||||||
AlbumArtist,
|
|
||||||
Artist,
|
|
||||||
LibraryItem,
|
|
||||||
QueueSong,
|
|
||||||
Song,
|
|
||||||
} from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
interface GoToActionProps {
|
interface GoToActionProps {
|
||||||
items: Album[] | AlbumArtist[] | Artist[] | QueueSong[] | Song[];
|
items: Album[] | QueueSong[] | Song[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GoToAction = ({ items }: GoToActionProps) => {
|
export const GoToAction = ({ items }: GoToActionProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { albumArtists, albumId } = useMemo(() => {
|
const { albumId, artists } = useMemo(() => {
|
||||||
const firstItem = items[0];
|
const firstItem = items[0];
|
||||||
|
|
||||||
if (firstItem._itemType === LibraryItem.ALBUM) {
|
switch (firstItem._itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
return {
|
return {
|
||||||
albumArtists: firstItem.albumArtists || [],
|
|
||||||
albumId: firstItem.id,
|
albumId: firstItem.id,
|
||||||
|
artists: firstItem.albumArtists || [],
|
||||||
};
|
};
|
||||||
} else if (firstItem._itemType === LibraryItem.SONG) {
|
case LibraryItem.SONG:
|
||||||
return {
|
return {
|
||||||
albumArtists: firstItem.albumArtists || [],
|
|
||||||
albumId: firstItem.albumId,
|
albumId: firstItem.albumId,
|
||||||
|
artists:
|
||||||
|
(firstItem.artists?.length ? firstItem.artists : firstItem.albumArtists) ||
|
||||||
|
[],
|
||||||
};
|
};
|
||||||
} else if (
|
default:
|
||||||
firstItem._itemType === LibraryItem.ARTIST ||
|
|
||||||
firstItem._itemType === LibraryItem.ALBUM_ARTIST
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
albumArtists: [{ id: firstItem.id, name: firstItem.name }],
|
|
||||||
albumId: null,
|
albumId: null,
|
||||||
|
artists: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
albumArtists: [],
|
|
||||||
albumId: null,
|
|
||||||
};
|
|
||||||
}, [items]);
|
}, [items]);
|
||||||
|
|
||||||
const handleGoToAlbum = useCallback(() => {
|
const handleGoToAlbum = useCallback(() => {
|
||||||
@@ -55,7 +43,7 @@ export const GoToAction = ({ items }: GoToActionProps) => {
|
|||||||
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId }));
|
navigate(generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId }));
|
||||||
}, [albumId, navigate]);
|
}, [albumId, navigate]);
|
||||||
|
|
||||||
const handleGoToAlbumArtist = useCallback(
|
const handleGoToArtist = useCallback(
|
||||||
(albumArtistId: string) => {
|
(albumArtistId: string) => {
|
||||||
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId }));
|
navigate(generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { albumArtistId }));
|
||||||
},
|
},
|
||||||
@@ -81,13 +69,13 @@ export const GoToAction = ({ items }: GoToActionProps) => {
|
|||||||
{t('page.contextMenu.goToAlbum')}
|
{t('page.contextMenu.goToAlbum')}
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
)}
|
)}
|
||||||
{albumArtists.map((albumArtist) => (
|
{artists.map((artist) => (
|
||||||
<ContextMenu.Item
|
<ContextMenu.Item
|
||||||
key={albumArtist.id}
|
key={artist.id}
|
||||||
leftIcon="artist"
|
leftIcon="artist"
|
||||||
onSelect={() => handleGoToAlbumArtist(albumArtist.id)}
|
onSelect={() => handleGoToArtist(artist.id)}
|
||||||
>
|
>
|
||||||
{`${t('page.contextMenu.goTo')} ${albumArtist.name}`}
|
{`${t('page.contextMenu.goTo')} ${artist.name}`}
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
))}
|
))}
|
||||||
</ContextMenu.SubmenuContent>
|
</ContextMenu.SubmenuContent>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
|
|||||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
|
||||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
|
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
|
||||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
@@ -39,8 +38,6 @@ export const AlbumArtistContextMenu = ({ items, type }: AlbumArtistContextMenuPr
|
|||||||
<DownloadAction ids={ids} />
|
<DownloadAction ids={ids} />
|
||||||
<ShareAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
<ShareAction ids={ids} itemType={LibraryItem.ALBUM_ARTIST} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
|
||||||
<ContextMenu.Divider />
|
|
||||||
<GetInfoAction disabled={items.length === 0} items={items} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useMemo } from 'react';
|
|||||||
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
|
||||||
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
|
||||||
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
import { GetInfoAction } from '/@/renderer/features/context-menu/actions/get-info-action';
|
||||||
import { GoToAction } from '/@/renderer/features/context-menu/actions/go-to-action';
|
|
||||||
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
|
||||||
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
|
import { PlayArtistRadioAction } from '/@/renderer/features/context-menu/actions/play-artist-radio-action';
|
||||||
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
import { SetFavoriteAction } from '/@/renderer/features/context-menu/actions/set-favorite-action';
|
||||||
@@ -39,8 +38,6 @@ export const ArtistContextMenu = ({ items, type }: ArtistContextMenuProps) => {
|
|||||||
<DownloadAction ids={ids} />
|
<DownloadAction ids={ids} />
|
||||||
<ShareAction ids={ids} itemType={LibraryItem.ARTIST} />
|
<ShareAction ids={ids} itemType={LibraryItem.ARTIST} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
|
||||||
<ContextMenu.Divider />
|
|
||||||
<GetInfoAction disabled={items.length === 0} items={items} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { PlayerStatus } from '/@/shared/types/types';
|
import { PlayerStatus } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
let startupRestoreSessionHandled = false;
|
||||||
|
|
||||||
export const useQueueRestoreTimestamp = () => {
|
export const useQueueRestoreTimestamp = () => {
|
||||||
const { mediaSeekToTimestamp } = usePlayerActions();
|
const { mediaSeekToTimestamp } = usePlayerActions();
|
||||||
|
|
||||||
@@ -51,28 +53,65 @@ export const useInitialTimestampRestore = () => {
|
|||||||
|
|
||||||
const startupRestoreInitializedRef = useRef(false);
|
const startupRestoreInitializedRef = useRef(false);
|
||||||
const startupSeekArmedRef = useRef<null | number>(null);
|
const startupSeekArmedRef = useRef<null | number>(null);
|
||||||
|
const startupSeekTargetUniqueIdRef = useRef<null | string>(null);
|
||||||
const startupSeekAppliedRef = useRef(false);
|
const startupSeekAppliedRef = useRef(false);
|
||||||
|
|
||||||
const applyStartupSeek = useCallback(() => {
|
const cancelStartupSeek = useCallback(() => {
|
||||||
if (startupSeekAppliedRef.current) {
|
if (startupSeekAppliedRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const seekTimestamp = startupSeekArmedRef.current;
|
|
||||||
if (!seekTimestamp || seekTimestamp <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
startupSeekAppliedRef.current = true;
|
startupSeekAppliedRef.current = true;
|
||||||
startupSeekArmedRef.current = null;
|
startupSeekArmedRef.current = null;
|
||||||
|
startupSeekTargetUniqueIdRef.current = null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const applyStartupSeek = useCallback(() => {
|
||||||
|
const seekTimestamp = startupSeekArmedRef.current;
|
||||||
|
|
||||||
|
if (startupSeekAppliedRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seekTimestamp || seekTimestamp <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetUniqueId = startupSeekTargetUniqueIdRef.current;
|
||||||
|
const currentUniqueId = usePlayerStore.getState().getQueue().items[
|
||||||
|
usePlayerStore.getState().player.index
|
||||||
|
]?._uniqueId;
|
||||||
|
|
||||||
|
if (targetUniqueId && currentUniqueId !== targetUniqueId) {
|
||||||
|
cancelStartupSeek();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startupSeekAppliedRef.current = true;
|
||||||
|
startupSeekArmedRef.current = null;
|
||||||
|
startupSeekTargetUniqueIdRef.current = null;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
mediaSeekToTimestamp(seekTimestamp);
|
mediaSeekToTimestamp(seekTimestamp);
|
||||||
}, 100);
|
}, 100);
|
||||||
}, [mediaSeekToTimestamp]);
|
}, [cancelStartupSeek, mediaSeekToTimestamp]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (startupRestoreInitializedRef.current) {
|
const targetUniqueId = startupSeekTargetUniqueIdRef.current;
|
||||||
|
if (
|
||||||
|
!targetUniqueId ||
|
||||||
|
startupSeekAppliedRef.current ||
|
||||||
|
!currentSong ||
|
||||||
|
currentSong._uniqueId === targetUniqueId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelStartupSeek();
|
||||||
|
}, [cancelStartupSeek, currentSong]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (startupRestoreInitializedRef.current || startupRestoreSessionHandled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +120,11 @@ export const useInitialTimestampRestore = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startupRestoreInitializedRef.current = true;
|
startupRestoreInitializedRef.current = true;
|
||||||
|
startupRestoreSessionHandled = true;
|
||||||
|
|
||||||
if (timestamp > 0) {
|
if (timestamp > 0) {
|
||||||
startupSeekArmedRef.current = timestamp;
|
startupSeekArmedRef.current = timestamp;
|
||||||
|
startupSeekTargetUniqueIdRef.current = currentSong._uniqueId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerStatus === PlayerStatus.PLAYING) {
|
if (playerStatus === PlayerStatus.PLAYING) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const getPositionValue = (seconds: number, useTicks: boolean) => {
|
|||||||
return Math.round(seconds * 1e7);
|
return Math.round(seconds * 1e7);
|
||||||
}
|
}
|
||||||
|
|
||||||
return seconds;
|
return seconds * 1000;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -459,12 +459,14 @@ export const useScrobble = () => {
|
|||||||
lastProgressEventRef.current = properties.timestamp;
|
lastProgressEventRef.current = properties.timestamp;
|
||||||
lastSeekEventRef.current = now;
|
lastSeekEventRef.current = now;
|
||||||
|
|
||||||
|
const currentStatus = usePlayerStore.getState().player.status;
|
||||||
|
|
||||||
sendScrobble.mutate(
|
sendScrobble.mutate(
|
||||||
{
|
{
|
||||||
apiClientProps: { serverId: currentSong._serverId || '' },
|
apiClientProps: { serverId: currentSong._serverId || '' },
|
||||||
query: {
|
query: {
|
||||||
albumId: currentSong.albumId,
|
albumId: currentSong.albumId,
|
||||||
event: 'timeupdate',
|
event: currentStatus === PlayerStatus.PLAYING ? 'unpause' : 'pause',
|
||||||
id: currentSong.id,
|
id: currentSong.id,
|
||||||
mediaType: mediaType,
|
mediaType: mediaType,
|
||||||
playbackRate: playbackRate,
|
playbackRate: playbackRate,
|
||||||
|
|||||||
@@ -1363,7 +1363,7 @@ export type ScrobbleArgs = BaseEndpointArgs & {
|
|||||||
|
|
||||||
export type ScrobbleQuery = {
|
export type ScrobbleQuery = {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
event?: 'pause' | 'start' | 'timeupdate' | 'unpause';
|
event?: 'pause' | 'start' | 'unpause';
|
||||||
id: string;
|
id: string;
|
||||||
mediaType: 'podcast' | 'song';
|
mediaType: 'podcast' | 'song';
|
||||||
playbackRate: number;
|
playbackRate: number;
|
||||||
|
|||||||
@@ -2,6 +2,20 @@ import type { HotkeyItem } from '@mantine/hooks';
|
|||||||
|
|
||||||
const RESERVED_KEYS = new Set(['alt', 'ctrl', 'meta', 'mod', 'shift']);
|
const RESERVED_KEYS = new Set(['alt', 'ctrl', 'meta', 'mod', 'shift']);
|
||||||
|
|
||||||
|
const PUNCTUATION_KEY_TO_PHYSICAL: Record<string, string> = {
|
||||||
|
"'": 'Quote',
|
||||||
|
',': 'Comma',
|
||||||
|
'-': 'Minus',
|
||||||
|
'.': 'Period',
|
||||||
|
'/': 'Slash',
|
||||||
|
';': 'Semicolon',
|
||||||
|
'=': 'Equal',
|
||||||
|
'[': 'BracketLeft',
|
||||||
|
'\\': 'Backslash',
|
||||||
|
']': 'BracketRight',
|
||||||
|
'`': 'Backquote',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts stored hotkey strings to Mantine's physical-key format.
|
* Converts stored hotkey strings to Mantine's physical-key format.
|
||||||
* Mantine matches KeyboardEvent.code via normalizeKey, which turns Digit1 into
|
* Mantine matches KeyboardEvent.code via normalizeKey, which turns Digit1 into
|
||||||
@@ -25,6 +39,11 @@ export const toPhysicalHotkey = (hotkey: string): string =>
|
|||||||
return `Digit${part}`;
|
return `Digit${part}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const punctuationPhysical = PUNCTUATION_KEY_TO_PHYSICAL[part];
|
||||||
|
if (punctuationPhysical) {
|
||||||
|
return punctuationPhysical;
|
||||||
|
}
|
||||||
|
|
||||||
return part;
|
return part;
|
||||||
})
|
})
|
||||||
.join('+');
|
.join('+');
|
||||||
@@ -33,5 +52,5 @@ export const withPhysicalKeys = (hotkeys: HotkeyItem[]): HotkeyItem[] =>
|
|||||||
hotkeys.map(([hotkey, handler, options]) => [
|
hotkeys.map(([hotkey, handler, options]) => [
|
||||||
toPhysicalHotkey(hotkey),
|
toPhysicalHotkey(hotkey),
|
||||||
handler,
|
handler,
|
||||||
{ ...options, usePhysicalKeys: true },
|
{ ...options, preventDefault: true, usePhysicalKeys: true },
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ const CODE_TO_HOTKEY_KEY: Record<string, string> = {
|
|||||||
ArrowLeft: 'arrowleft',
|
ArrowLeft: 'arrowleft',
|
||||||
ArrowRight: 'arrowright',
|
ArrowRight: 'arrowright',
|
||||||
ArrowUp: 'arrowup',
|
ArrowUp: 'arrowup',
|
||||||
|
Backquote: '`',
|
||||||
|
Backslash: '\\',
|
||||||
Backspace: 'backspace',
|
Backspace: 'backspace',
|
||||||
|
BracketLeft: '[',
|
||||||
|
BracketRight: ']',
|
||||||
|
Comma: ',',
|
||||||
Delete: 'delete',
|
Delete: 'delete',
|
||||||
End: 'end',
|
End: 'end',
|
||||||
Enter: 'enter',
|
Enter: 'enter',
|
||||||
@@ -14,6 +19,10 @@ const CODE_TO_HOTKEY_KEY: Record<string, string> = {
|
|||||||
Minus: 'minus',
|
Minus: 'minus',
|
||||||
PageDown: 'pagedown',
|
PageDown: 'pagedown',
|
||||||
PageUp: 'pageup',
|
PageUp: 'pageup',
|
||||||
|
Period: '.',
|
||||||
|
Quote: "'",
|
||||||
|
Semicolon: ';',
|
||||||
|
Slash: '/',
|
||||||
Space: 'space',
|
Space: 'space',
|
||||||
Tab: 'tab',
|
Tab: 'tab',
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user