mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Merge branch 'master' of https://github.com/krtirtho/spotube
This commit is contained in:
commit
65596e3527
13
README.md
13
README.md
@ -14,7 +14,7 @@ Following are the features that currently spotube offers:
|
||||
- Playback control is on user's machine instead of server based
|
||||
- Small size & less data hungry
|
||||
- No spotify or youtube ads since it uses all public & free APIs (But it's recommended to support the creators by watching/liking/subscribing to the artists youtube channel or add as favourite track in spotify. Mostly buying spotify premium is the best way to support their valuable creations)
|
||||
- Lyric Seek (WIP)
|
||||
- Lyric Seek
|
||||
|
||||
## Requirements
|
||||
|
||||
@ -149,11 +149,12 @@ $ npm start
|
||||
There will be some glitches, lags & stuck motions because of the library Spotube is currently using under the hood. It has some issues with layouts thus sometime some contents aren't shown or overflows out of the window. But resizing the window would fix this issue. Soon there will be some updates fixing this sort of layout related problems
|
||||
|
||||
## TODO:
|
||||
|
||||
- Compile, Debug & Build for **MacOS**
|
||||
- Add seek Lyric for currently playing track
|
||||
- Support for playing/streaming podcasts/shows
|
||||
- Easy installation procedure/mechanism for simplicity
|
||||
- [ ] Compile, Debug & Build for **MacOS**
|
||||
- [x] Compile, Debug & Build for **Windows**
|
||||
- [x] Add seek Lyric for currently playing track
|
||||
- [ ] Support for playing/streaming podcasts/shows
|
||||
- [ ] Easy installation procedure/mechanism for simplicity in Windows
|
||||
- [ ] Artist, User & Album pages
|
||||
|
||||
## Things that don't work
|
||||
- Shows & Podcasts aren't supported as it'd require premium anyway
|
||||
|
@ -10,9 +10,11 @@
|
||||
"dev": "cross-env TSC_WATCHFILE=UseFsEvents webpack --mode=development",
|
||||
"start": "qode ./dist/index.js",
|
||||
"start:watch": "nodemon -e node -w ./*.babelrc -x \"npm start\"",
|
||||
"start-dev": "concurrently -n \"webpack,spotube\" -p \"{name}-{pid}\" -c \"bgBlue,bgGreen\" -i --default-input-target spotube \"npm run dev\" \"npm run start:watch\"",
|
||||
"start-dev": "concurrently -n \"webpack,spotube\" -p \"{name}-{pid}\" -c \"bgBlue.black.bold,bgGreen.black.bold\" -i --default-input-target spotube \"npm run dev\" \"npm run start:watch\"",
|
||||
"debug-dev": "concurrently -n \"webpack,spotube\" -p \"{name}-{pid}\" -c \"bgBlue.black.bold,bgGreen.black.bold\" -i --default-input-target spotube \"npm run dev\" \"npm run debug:watch\"",
|
||||
"start:trace": "qode ./dist/index.js --trace",
|
||||
"debug": "qode --inspect ./dist/index.js",
|
||||
"debug:watch": "nodemon -e node -w ./*.babelrc -x \"npm run debug\"",
|
||||
"pack": "nodegui-packer -p ./dist",
|
||||
"pack-deb": "node scripts/build-deb.js",
|
||||
"pack-win32": "powershell.exe -ExecutionPolicy Unrestricted -Command \". '.\\scripts\\build-win32.ps1'\""
|
||||
|
99
src/app.tsx
99
src/app.tsx
@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Window, hot, View, useEventHandler } from "@nodegui/react-nodegui";
|
||||
import { QIcon, QKeyEvent, QMainWindow, QMainWindowSignals, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
|
||||
import { Window, hot, View } from "@nodegui/react-nodegui";
|
||||
import { QIcon, QMainWindow, WidgetEventTypes, WindowState, QShortcut, QKeySequence } from "@nodegui/nodegui";
|
||||
import { MemoryRouter } from "react-router";
|
||||
import Routes from "./routes";
|
||||
import { LocalStorage } from "node-localstorage";
|
||||
@ -14,17 +14,10 @@ import spotifyApi from "./initializations/spotifyApi";
|
||||
import showError from "./helpers/showError";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { confDir } from "./conf";
|
||||
import { confDir, LocalStorageKeys } from "./conf";
|
||||
import spotubeIcon from "../assets/icon.svg";
|
||||
import preferencesContext, { PreferencesContextProperties } from "./context/preferencesContext";
|
||||
|
||||
export enum LocalStorageKeys {
|
||||
credentials = "credentials",
|
||||
refresh_token = "refresh_token",
|
||||
preferences = "user-preferences",
|
||||
volume = "volume"
|
||||
}
|
||||
|
||||
export interface Credentials {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
@ -54,36 +47,6 @@ const initialCredentials: Credentials = { clientId: "", clientSecret: "" };
|
||||
function RootApp() {
|
||||
const windowRef = useRef<QMainWindow>();
|
||||
const [currentTrack, setCurrentTrack] = useState<CurrentTrack>();
|
||||
|
||||
const windowEvents = useEventHandler<QMainWindowSignals>(
|
||||
{
|
||||
async KeyRelease(nativeEv) {
|
||||
try {
|
||||
if (nativeEv) {
|
||||
const event = new QKeyEvent(nativeEv);
|
||||
const eventKey = event.key();
|
||||
if (audioPlayer.isRunning() && currentTrack)
|
||||
switch (eventKey) {
|
||||
case 32: //space
|
||||
(await audioPlayer.isPaused()) ? await audioPlayer.play() : await audioPlayer.pause();
|
||||
break;
|
||||
case 16777236: //arrow-right
|
||||
(await audioPlayer.isSeekable()) && (await audioPlayer.seek(+5));
|
||||
break;
|
||||
case 16777234: //arrow-left
|
||||
(await audioPlayer.isSeekable()) && (await audioPlayer.seek(-5));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in window events: ", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
[currentTrack]
|
||||
);
|
||||
// cache
|
||||
const cachedPreferences = localStorage.getItem(LocalStorageKeys.preferences);
|
||||
const cachedCredentials = localStorage.getItem(LocalStorageKeys.credentials);
|
||||
@ -160,13 +123,67 @@ function RootApp() {
|
||||
};
|
||||
|
||||
windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose);
|
||||
|
||||
return () => {
|
||||
windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose);
|
||||
};
|
||||
});
|
||||
let spaceShortcut: QShortcut | null;
|
||||
let rightShortcut: QShortcut | null;
|
||||
let leftShortcut: QShortcut | null;
|
||||
// short cut effect
|
||||
useEffect(() => {
|
||||
if (windowRef.current) {
|
||||
spaceShortcut = new QShortcut(windowRef.current);
|
||||
rightShortcut = new QShortcut(windowRef.current);
|
||||
leftShortcut = new QShortcut(windowRef.current);
|
||||
|
||||
spaceShortcut.setKey(new QKeySequence("SPACE"));
|
||||
rightShortcut.setKey(new QKeySequence("RIGHT"));
|
||||
leftShortcut.setKey(new QKeySequence("LEFT"));
|
||||
|
||||
async function spaceAction() {
|
||||
try {
|
||||
currentTrack && audioPlayer.isRunning() && (await audioPlayer.isPaused()) ? await audioPlayer.play() : await audioPlayer.pause();
|
||||
console.log("You pressed SPACE");
|
||||
} catch (error) {
|
||||
showError(error, "[Failed to play/pause audioPlayer]: ");
|
||||
}
|
||||
}
|
||||
async function rightAction() {
|
||||
try {
|
||||
currentTrack && audioPlayer.isRunning() && (await audioPlayer.isSeekable()) && (await audioPlayer.seek(+5));
|
||||
console.log("You pressed RIGHT");
|
||||
} catch (error) {
|
||||
showError(error, "[Failed to seek audioPlayer]: ");
|
||||
}
|
||||
}
|
||||
async function leftAction() {
|
||||
try {
|
||||
currentTrack && audioPlayer.isRunning() && (await audioPlayer.isSeekable()) && (await audioPlayer.seek(-5));
|
||||
console.log("You pressed LEFT");
|
||||
} catch (error) {
|
||||
showError(error, "[Failed to seek audioPlayer]: ");
|
||||
}
|
||||
}
|
||||
|
||||
spaceShortcut.addEventListener("activated", spaceAction);
|
||||
rightShortcut.addEventListener("activated", rightAction);
|
||||
leftShortcut.addEventListener("activated", leftAction);
|
||||
|
||||
return () => {
|
||||
spaceShortcut?.removeEventListener("activated", spaceAction);
|
||||
rightShortcut?.removeEventListener("activated", rightAction);
|
||||
leftShortcut?.removeEventListener("activated", leftAction);
|
||||
spaceShortcut = null;
|
||||
rightShortcut = null;
|
||||
leftShortcut = null;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Window ref={windowRef} on={windowEvents} windowState={WindowState.WindowMaximized} windowIcon={winIcon} windowTitle="Spotube" minSize={minSize}>
|
||||
<Window ref={windowRef} windowState={WindowState.WindowMaximized} windowIcon={winIcon} windowTitle="Spotube" minSize={minSize}>
|
||||
<MemoryRouter>
|
||||
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, setAccess_token, ...credentials, setCredentials }}>
|
||||
<preferencesContext.Provider value={{ ...preferences, setPreferences }}>
|
||||
|
@ -1,12 +1,10 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Button, ScrollArea, View } from "@nodegui/react-nodegui";
|
||||
import { useHistory } from "react-router";
|
||||
import { CursorShape, QMouseEvent } from "@nodegui/nodegui";
|
||||
import { QueryCacheKeys } from "../conf";
|
||||
import useSpotifyQuery from "../hooks/useSpotifyQuery";
|
||||
import PlaceholderApplet from "./shared/PlaceholderApplet";
|
||||
import PlaylistCard from "./shared/PlaylistCard";
|
||||
import useSpotifyInfiniteQuery from "../hooks/useSpotifyInfiniteQuery";
|
||||
import CategoryCardView from "./shared/CategoryCardView";
|
||||
|
||||
function Home() {
|
||||
const { data: pagedCategories, isError, refetch, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } = useSpotifyInfiniteQuery<SpotifyApi.PagingObject<SpotifyApi.CategoryObject>>(
|
||||
@ -25,7 +23,7 @@ function Home() {
|
||||
.map((page) => page.items)
|
||||
.filter(Boolean)
|
||||
.flat(1);
|
||||
|
||||
categories?.unshift({ href: "", icons: [], id: "featured", name: "Featured" });
|
||||
return (
|
||||
<ScrollArea style={`flex-grow: 1; border: none;`}>
|
||||
<View style={`flex-direction: 'column'; align-items: 'center'; flex: 1;`}>
|
||||
@ -33,12 +31,7 @@ function Home() {
|
||||
{categories?.map((category, index) => {
|
||||
return <CategoryCard key={index + category.id} id={category.id} name={category.name} />;
|
||||
})}
|
||||
{hasNextPage &&
|
||||
<Button
|
||||
on={{ clicked: () => fetchNextPage() }}
|
||||
text="Load More"
|
||||
enabled={!isFetchingNextPage}
|
||||
/>}
|
||||
{hasNextPage && <Button on={{ clicked: () => fetchNextPage() }} text="Load More" enabled={!isFetchingNextPage} />}
|
||||
</View>
|
||||
</ScrollArea>
|
||||
);
|
||||
@ -51,55 +44,21 @@ interface CategoryCardProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const categoryStylesheet = `
|
||||
#container{
|
||||
flex: 1;
|
||||
flex-direction: 'column';
|
||||
justify-content: 'center';
|
||||
}
|
||||
#anchor-heading{
|
||||
background: transparent;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
align-self: flex-start;
|
||||
}
|
||||
#child-view{
|
||||
flex: 1;
|
||||
}
|
||||
#anchor-heading:hover{
|
||||
border: none;
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
const CategoryCard = ({ id, name }: CategoryCardProps) => {
|
||||
const history = useHistory();
|
||||
const { data: playlists, isError } = useSpotifyQuery<SpotifyApi.PlaylistObjectSimplified[]>(
|
||||
[QueryCacheKeys.categoryPlaylists, id],
|
||||
(spotifyApi) => spotifyApi.getPlaylistsForCategory(id, { limit: 4 }).then((playlistsRes) => playlistsRes.body.playlists.items),
|
||||
async (spotifyApi) => {
|
||||
const option = { limit: 4 };
|
||||
let res;
|
||||
if (id === "featured") {
|
||||
res = await spotifyApi.getFeaturedPlaylists(option);
|
||||
} else {
|
||||
res = await spotifyApi.getPlaylistsForCategory(id, option);
|
||||
}
|
||||
return res.body.playlists.items;
|
||||
},
|
||||
{ initialData: [] }
|
||||
);
|
||||
|
||||
function goToGenre(native: any) {
|
||||
const mouse = new QMouseEvent(native);
|
||||
if (mouse.button() === 1) {
|
||||
history.push(`/genre/playlists/${id}`, { name });
|
||||
}
|
||||
}
|
||||
if (isError) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<View id="container" styleSheet={categoryStylesheet}>
|
||||
<Button id="anchor-heading" cursor={CursorShape.PointingHandCursor} on={{ MouseButtonRelease: goToGenre }} text={name} />
|
||||
<View id="child-view">
|
||||
{playlists?.map((playlist, index) => {
|
||||
return <PlaylistCard key={index + playlist.id} playlist={playlist} />;
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
return <CategoryCardView url={`/genre/playlists/${id}`} isError={isError} name={name} playlists={playlists ?? []} />;
|
||||
};
|
||||
|
@ -4,14 +4,14 @@ import React, { ReactElement, useContext, useEffect, useRef, useState } from "re
|
||||
import playerContext, { CurrentPlaylist } from "../context/playerContext";
|
||||
import { shuffleArray } from "../helpers/shuffleArray";
|
||||
import NodeMpv from "node-mpv";
|
||||
import { getYoutubeTrack } from "../helpers/getYoutubeTrack";
|
||||
import { getYoutubeTrack, YoutubeTrack } from "../helpers/getYoutubeTrack";
|
||||
import PlayerProgressBar from "./PlayerProgressBar";
|
||||
import { random as shuffleIcon, play, pause, backward, forward, stop, heartRegular, heart, musicNode } from "../icons";
|
||||
import IconButton from "./shared/IconButton";
|
||||
import showError from "../helpers/showError";
|
||||
import useTrackReaction from "../hooks/useTrackReaction";
|
||||
import ManualLyricDialog from "./ManualLyricDialog";
|
||||
import { LocalStorageKeys } from "../app";
|
||||
import { LocalStorageKeys } from "../conf";
|
||||
|
||||
export const audioPlayer = new NodeMpv(
|
||||
{
|
||||
@ -35,6 +35,7 @@ function Player(): ReactElement {
|
||||
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
|
||||
const [isStopped, setIsStopped] = useState<boolean>(false);
|
||||
const [openLyrics, setOpenLyrics] = useState<boolean>(false);
|
||||
const [currentYtTrack, setCurrentYtTrack] = useState<YoutubeTrack>();
|
||||
const playlistTracksIds = currentPlaylist?.tracks.map((t) => t.track.id);
|
||||
const volumeHandler = useEventHandler<QAbstractSliderSignals>(
|
||||
{
|
||||
@ -49,6 +50,8 @@ function Player(): ReactElement {
|
||||
);
|
||||
const playerRunning = audioPlayer.isRunning();
|
||||
const titleRef = useRef<QLabel>();
|
||||
const cachedPlaylist = localStorage.getItem(LocalStorageKeys.cachedPlaylist);
|
||||
const cachedTrack = localStorage.getItem(LocalStorageKeys.cachedTrack);
|
||||
|
||||
// initial Effect
|
||||
useEffect(() => {
|
||||
@ -61,21 +64,31 @@ function Player(): ReactElement {
|
||||
} catch (error) {
|
||||
showError(error, "[Failed starting audio player]: ");
|
||||
}
|
||||
})();
|
||||
})().then(() => {
|
||||
if (cachedPlaylist && !currentPlaylist) {
|
||||
setCurrentPlaylist(JSON.parse(cachedPlaylist));
|
||||
}
|
||||
if (cachedTrack && !currentTrack) {
|
||||
setCurrentTrack(JSON.parse(cachedTrack));
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (playerRunning) {
|
||||
audioPlayer.quit().catch((e: any) => console.log(e));
|
||||
audioPlayer.quit().catch((e: unknown) => console.log(e));
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// track change effect
|
||||
useEffect(() => {
|
||||
// caching current track
|
||||
localStorage.setItem(LocalStorageKeys.cachedTrack, JSON.stringify(currentTrack ?? ""));
|
||||
(async () => {
|
||||
try {
|
||||
if (currentTrack && playerRunning) {
|
||||
const youtubeTrack = await getYoutubeTrack(currentTrack);
|
||||
setCurrentYtTrack(youtubeTrack);
|
||||
await audioPlayer.load(youtubeTrack.youtube_uri, "replace");
|
||||
await audioPlayer.play();
|
||||
setIsPaused(false);
|
||||
@ -94,6 +107,8 @@ function Player(): ReactElement {
|
||||
// changing shuffle to default
|
||||
useEffect(() => {
|
||||
setShuffle(false);
|
||||
// caching playlist
|
||||
localStorage.setItem(LocalStorageKeys.cachedPlaylist, JSON.stringify(currentPlaylist ?? ""));
|
||||
}, [currentPlaylist]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -191,10 +206,10 @@ function Player(): ReactElement {
|
||||
<GridView enabled={!!currentTrack} style="flex: 1; max-height: 120px;">
|
||||
<GridRow>
|
||||
<GridColumn width={2}>
|
||||
<Text ref={titleRef} wordWrap>
|
||||
<Text ref={titleRef} wordWrap openExternalLinks>
|
||||
{artistsNames && currentTrack
|
||||
? `
|
||||
<p><b>${currentTrack.name}</b> - ${artistsNames[0]} ${artistsNames.length > 1 ? "feat. " + artistsNames.slice(1).join(", ") : ""}</p>
|
||||
<p><b><a href="${currentYtTrack?.youtube_uri}"}>${currentTrack.name}</a></b> - ${artistsNames[0]} ${artistsNames.length > 1 ? "feat. " + artistsNames.slice(1).join(", ") : ""}</p>
|
||||
`
|
||||
: `<b>Oh, dear don't waste time</b>`}
|
||||
</Text>
|
||||
|
@ -13,7 +13,16 @@ function PlaylistGenreView() {
|
||||
const location = useLocation<{ name: string }>();
|
||||
const { data: pagedPlaylists, isError, isLoading, refetch, hasNextPage, isFetchingNextPage, fetchNextPage } = useSpotifyInfiniteQuery<SpotifyApi.PagingObject<SpotifyApi.PlaylistObjectSimplified>>(
|
||||
[QueryCacheKeys.genrePlaylists, id],
|
||||
(spotifyApi, { pageParam }) => spotifyApi.getPlaylistsForCategory(id, { limit: 20, offset: pageParam }).then((playlistsRes) => playlistsRes.body.playlists),
|
||||
async (spotifyApi, { pageParam }) => {
|
||||
const option = { limit: 20, offset: pageParam };
|
||||
let res;
|
||||
if (id === "featured") {
|
||||
res = await spotifyApi.getFeaturedPlaylists(option);
|
||||
} else {
|
||||
res = await spotifyApi.getPlaylistsForCategory(id, option);
|
||||
}
|
||||
return res.body.playlists;
|
||||
},
|
||||
{
|
||||
getNextPageParam(lastPage) {
|
||||
if (lastPage.next) {
|
||||
|
61
src/components/shared/CategoryCardView.tsx
Normal file
61
src/components/shared/CategoryCardView.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { QMouseEvent, CursorShape } from "@nodegui/nodegui";
|
||||
import { View, Button } from "@nodegui/react-nodegui";
|
||||
import React, { FC } from "react";
|
||||
import { useHistory } from "react-router";
|
||||
import PlaylistCard from "./PlaylistCard";
|
||||
|
||||
interface CategoryCardProps {
|
||||
url: string;
|
||||
name: string;
|
||||
isError: boolean;
|
||||
playlists: SpotifyApi.PlaylistObjectSimplified[];
|
||||
}
|
||||
|
||||
const categoryStylesheet = `
|
||||
#container{
|
||||
flex: 1;
|
||||
flex-direction: 'column';
|
||||
justify-content: 'center';
|
||||
}
|
||||
#anchor-heading{
|
||||
background: transparent;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
align-self: flex-start;
|
||||
}
|
||||
#child-view{
|
||||
flex: 1;
|
||||
}
|
||||
#anchor-heading:hover{
|
||||
border: none;
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
const CategoryCard: FC<CategoryCardProps> = ({ name, isError, playlists, url }) => {
|
||||
const history = useHistory();
|
||||
function goToGenre(native: any) {
|
||||
const mouse = new QMouseEvent(native);
|
||||
if (mouse.button() === 1) {
|
||||
history.push(url, { name });
|
||||
}
|
||||
}
|
||||
if (isError) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<View id="container" styleSheet={categoryStylesheet}>
|
||||
<Button id="anchor-heading" cursor={CursorShape.PointingHandCursor} on={{ MouseButtonRelease: goToGenre }} text={name} />
|
||||
<View id="child-view">
|
||||
{playlists.map((playlist, index) => {
|
||||
return <PlaylistCard key={index + playlist.id} playlist={playlist} />;
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryCard;
|
34
src/conf.ts
34
src/conf.ts
@ -1,22 +1,32 @@
|
||||
import dotenv from "dotenv"
|
||||
import dotenv from "dotenv";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
|
||||
const env = dotenv.config({path: join(process.cwd(), ".env")}).parsed as any
|
||||
const env = dotenv.config({ path: join(process.cwd(), ".env") }).parsed as any;
|
||||
export const clientId = "";
|
||||
export const trace = process.argv.find(arg => arg === "--trace") ?? false;
|
||||
export const redirectURI = "http://localhost:4304/auth/spotify/callback"
|
||||
export const confDir = join(homedir(), ".config", "spotube")
|
||||
export const cacheDir = join(homedir(), ".cache", "spotube")
|
||||
export const trace = process.argv.find((arg) => arg === "--trace") ?? false;
|
||||
export const redirectURI = "http://localhost:4304/auth/spotify/callback";
|
||||
export const confDir = join(homedir(), ".config", "spotube");
|
||||
export const cacheDir = join(homedir(), ".cache", "spotube");
|
||||
|
||||
export enum QueryCacheKeys{
|
||||
categories="categories",
|
||||
export enum QueryCacheKeys {
|
||||
categories = "categories",
|
||||
categoryPlaylists = "categoryPlaylists",
|
||||
genrePlaylists="genrePlaylists",
|
||||
playlistTracks="playlistTracks",
|
||||
featuredPlaylists = "featuredPlaylists",
|
||||
genrePlaylists = "genrePlaylists",
|
||||
playlistTracks = "playlistTracks",
|
||||
userPlaylists = "user-palylists",
|
||||
userSavedTracks = "user-saved-tracks",
|
||||
search = "search",
|
||||
searchPlaylist = "searchPlaylist",
|
||||
searchSongs = "searchSongs"
|
||||
}
|
||||
searchSongs = "searchSongs",
|
||||
}
|
||||
|
||||
export enum LocalStorageKeys {
|
||||
credentials = "credentials",
|
||||
refresh_token = "refresh_token",
|
||||
preferences = "user-preferences",
|
||||
volume = "volume",
|
||||
cachedPlaylist = "cached-playlist",
|
||||
cachedTrack = "cached-track"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import path from "path";
|
||||
import isUrl from "is-url";
|
||||
import * as fs from "fs"
|
||||
import fs from "fs";
|
||||
import axios from "axios";
|
||||
import { Stream } from "stream";
|
||||
import { streamToBuffer } from "./streamToBuffer";
|
||||
@ -8,7 +8,6 @@ import Jimp from "jimp";
|
||||
import du from "du";
|
||||
import { cacheDir } from "../conf";
|
||||
|
||||
|
||||
interface ImageDimensions {
|
||||
height: number;
|
||||
width: number;
|
||||
@ -22,20 +21,20 @@ export async function getCachedImageBuffer(name: string, dims?: ImageDimensions)
|
||||
const cacheImgFolder = path.join(cacheDir, "images");
|
||||
// for clearing up the cache if it reaches out of the size
|
||||
const cacheName = `${isUrl(name) ? name.split("/").slice(-1)[0] : name}.cnim`;
|
||||
const cachePath = path.join(cacheImgFolder, cacheName);
|
||||
const cacheImgPath = path.join(cacheImgFolder, cacheName);
|
||||
// checking if the cached image already exists or not
|
||||
if (fs.existsSync(cachePath)) {
|
||||
if (fs.existsSync(cacheImgPath)) {
|
||||
// automatically removing cache after a certain 50 MB oversize
|
||||
if ((await du(cacheImgFolder)) > MB_5) {
|
||||
fs.rmSync(cacheImgFolder, { recursive: true, force: true });
|
||||
fs.rmdirSync(cacheImgFolder, { recursive: true });
|
||||
}
|
||||
const cachedImg = await fsm.readFile(cachePath);
|
||||
const cachedImg = await fsm.readFile(cacheImgPath);
|
||||
const cachedImgMeta = (await Jimp.read(cachedImg)).bitmap;
|
||||
|
||||
// if the dimensions are changed then the previously cached
|
||||
// images are removed and replaced with a new one
|
||||
if (dims && (cachedImgMeta.height !== dims.height || cachedImgMeta.width !== dims?.width)) {
|
||||
fs.rmSync(cachePath);
|
||||
fs.unlinkSync(cacheImgPath);
|
||||
return await imageResizeAndWrite(cachedImg, { cacheFolder: cacheImgFolder, cacheName, dims });
|
||||
}
|
||||
return cachedImg;
|
||||
@ -61,7 +60,7 @@ export async function getCachedImageBuffer(name: string, dims?: ImageDimensions)
|
||||
async function imageResizeAndWrite(img: Buffer, { cacheFolder, cacheName, dims }: { dims: ImageDimensions; cacheFolder: string; cacheName: string }): Promise<Buffer> {
|
||||
// caching the images by resizing if the max/fixed (Width/Height)
|
||||
// is available in the args
|
||||
const resizedImg = (await Jimp.read(img)).resize(dims.width, dims.height)
|
||||
const resizedImg = (await Jimp.read(img)).resize(dims.width, dims.height);
|
||||
const resizedImgBuffer = await resizedImg.getBufferAsync(resizedImg._originalMime);
|
||||
await fsm.writeFile(path.join(cacheFolder, cacheName), resizedImgBuffer);
|
||||
return resizedImgBuffer;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import chalk from "chalk";
|
||||
import { useContext, useEffect } from "react";
|
||||
import { LocalStorageKeys } from "../app";
|
||||
import { LocalStorageKeys } from "../conf";
|
||||
import authContext from "../context/authContext";
|
||||
import showError from "../helpers/showError";
|
||||
import spotifyApi from "../initializations/spotifyApi";
|
||||
|
Loading…
Reference in New Issue
Block a user