mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
fixed webpack rmSync is not a function & added cached playback
This commit is contained in:
parent
0dd41812ee
commit
35da4d5fd4
@ -14,17 +14,10 @@ import spotifyApi from "./initializations/spotifyApi";
|
|||||||
import showError from "./helpers/showError";
|
import showError from "./helpers/showError";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { confDir } from "./conf";
|
import { confDir, LocalStorageKeys } from "./conf";
|
||||||
import spotubeIcon from "../assets/icon.svg";
|
import spotubeIcon from "../assets/icon.svg";
|
||||||
import preferencesContext, { PreferencesContextProperties } from "./context/preferencesContext";
|
import preferencesContext, { PreferencesContextProperties } from "./context/preferencesContext";
|
||||||
|
|
||||||
export enum LocalStorageKeys {
|
|
||||||
credentials = "credentials",
|
|
||||||
refresh_token = "refresh_token",
|
|
||||||
preferences = "user-preferences",
|
|
||||||
volume = "volume",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Credentials {
|
export interface Credentials {
|
||||||
clientId: string;
|
clientId: string;
|
||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
|
@ -4,14 +4,14 @@ import React, { ReactElement, useContext, useEffect, useRef, useState } from "re
|
|||||||
import playerContext, { CurrentPlaylist } from "../context/playerContext";
|
import playerContext, { CurrentPlaylist } from "../context/playerContext";
|
||||||
import { shuffleArray } from "../helpers/shuffleArray";
|
import { shuffleArray } from "../helpers/shuffleArray";
|
||||||
import NodeMpv from "node-mpv";
|
import NodeMpv from "node-mpv";
|
||||||
import { getYoutubeTrack } from "../helpers/getYoutubeTrack";
|
import { getYoutubeTrack, YoutubeTrack } from "../helpers/getYoutubeTrack";
|
||||||
import PlayerProgressBar from "./PlayerProgressBar";
|
import PlayerProgressBar from "./PlayerProgressBar";
|
||||||
import { random as shuffleIcon, play, pause, backward, forward, stop, heartRegular, heart, musicNode } from "../icons";
|
import { random as shuffleIcon, play, pause, backward, forward, stop, heartRegular, heart, musicNode } from "../icons";
|
||||||
import IconButton from "./shared/IconButton";
|
import IconButton from "./shared/IconButton";
|
||||||
import showError from "../helpers/showError";
|
import showError from "../helpers/showError";
|
||||||
import useTrackReaction from "../hooks/useTrackReaction";
|
import useTrackReaction from "../hooks/useTrackReaction";
|
||||||
import ManualLyricDialog from "./ManualLyricDialog";
|
import ManualLyricDialog from "./ManualLyricDialog";
|
||||||
import { LocalStorageKeys } from "../app";
|
import { LocalStorageKeys } from "../conf";
|
||||||
|
|
||||||
export const audioPlayer = new NodeMpv(
|
export const audioPlayer = new NodeMpv(
|
||||||
{
|
{
|
||||||
@ -35,6 +35,7 @@ function Player(): ReactElement {
|
|||||||
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
|
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
|
||||||
const [isStopped, setIsStopped] = useState<boolean>(false);
|
const [isStopped, setIsStopped] = useState<boolean>(false);
|
||||||
const [openLyrics, setOpenLyrics] = useState<boolean>(false);
|
const [openLyrics, setOpenLyrics] = useState<boolean>(false);
|
||||||
|
const [currentYtTrack, setCurrentYtTrack] = useState<YoutubeTrack>();
|
||||||
const playlistTracksIds = currentPlaylist?.tracks.map((t) => t.track.id);
|
const playlistTracksIds = currentPlaylist?.tracks.map((t) => t.track.id);
|
||||||
const volumeHandler = useEventHandler<QAbstractSliderSignals>(
|
const volumeHandler = useEventHandler<QAbstractSliderSignals>(
|
||||||
{
|
{
|
||||||
@ -49,6 +50,8 @@ function Player(): ReactElement {
|
|||||||
);
|
);
|
||||||
const playerRunning = audioPlayer.isRunning();
|
const playerRunning = audioPlayer.isRunning();
|
||||||
const titleRef = useRef<QLabel>();
|
const titleRef = useRef<QLabel>();
|
||||||
|
const cachedPlaylist = localStorage.getItem(LocalStorageKeys.cachedPlaylist);
|
||||||
|
const cachedTrack = localStorage.getItem(LocalStorageKeys.cachedTrack);
|
||||||
|
|
||||||
// initial Effect
|
// initial Effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -61,21 +64,31 @@ function Player(): ReactElement {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error, "[Failed starting audio player]: ");
|
showError(error, "[Failed starting audio player]: ");
|
||||||
}
|
}
|
||||||
})();
|
})().then(() => {
|
||||||
|
if (cachedPlaylist && !currentPlaylist) {
|
||||||
|
setCurrentPlaylist(JSON.parse(cachedPlaylist));
|
||||||
|
}
|
||||||
|
if (cachedTrack && !currentTrack) {
|
||||||
|
setCurrentTrack(JSON.parse(cachedTrack));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (playerRunning) {
|
if (playerRunning) {
|
||||||
audioPlayer.quit().catch((e: any) => console.log(e));
|
audioPlayer.quit().catch((e: unknown) => console.log(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// track change effect
|
// track change effect
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// caching current track
|
||||||
|
localStorage.setItem(LocalStorageKeys.cachedTrack, JSON.stringify(currentTrack ?? ""));
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (currentTrack && playerRunning) {
|
if (currentTrack && playerRunning) {
|
||||||
const youtubeTrack = await getYoutubeTrack(currentTrack);
|
const youtubeTrack = await getYoutubeTrack(currentTrack);
|
||||||
|
setCurrentYtTrack(youtubeTrack);
|
||||||
await audioPlayer.load(youtubeTrack.youtube_uri, "replace");
|
await audioPlayer.load(youtubeTrack.youtube_uri, "replace");
|
||||||
await audioPlayer.play();
|
await audioPlayer.play();
|
||||||
setIsPaused(false);
|
setIsPaused(false);
|
||||||
@ -94,6 +107,8 @@ function Player(): ReactElement {
|
|||||||
// changing shuffle to default
|
// changing shuffle to default
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShuffle(false);
|
setShuffle(false);
|
||||||
|
// caching playlist
|
||||||
|
localStorage.setItem(LocalStorageKeys.cachedPlaylist, JSON.stringify(currentPlaylist ?? ""));
|
||||||
}, [currentPlaylist]);
|
}, [currentPlaylist]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -191,10 +206,10 @@ function Player(): ReactElement {
|
|||||||
<GridView enabled={!!currentTrack} style="flex: 1; max-height: 120px;">
|
<GridView enabled={!!currentTrack} style="flex: 1; max-height: 120px;">
|
||||||
<GridRow>
|
<GridRow>
|
||||||
<GridColumn width={2}>
|
<GridColumn width={2}>
|
||||||
<Text ref={titleRef} wordWrap>
|
<Text ref={titleRef} wordWrap openExternalLinks>
|
||||||
{artistsNames && currentTrack
|
{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>`}
|
: `<b>Oh, dear don't waste time</b>`}
|
||||||
</Text>
|
</Text>
|
||||||
|
33
src/conf.ts
33
src/conf.ts
@ -1,23 +1,32 @@
|
|||||||
import dotenv from "dotenv"
|
import dotenv from "dotenv";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
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 clientId = "";
|
||||||
export const trace = process.argv.find(arg => arg === "--trace") ?? false;
|
export const trace = process.argv.find((arg) => arg === "--trace") ?? false;
|
||||||
export const redirectURI = "http://localhost:4304/auth/spotify/callback"
|
export const redirectURI = "http://localhost:4304/auth/spotify/callback";
|
||||||
export const confDir = join(homedir(), ".config", "spotube")
|
export const confDir = join(homedir(), ".config", "spotube");
|
||||||
export const cacheDir = join(homedir(), ".cache", "spotube")
|
export const cacheDir = join(homedir(), ".cache", "spotube");
|
||||||
|
|
||||||
export enum QueryCacheKeys{
|
export enum QueryCacheKeys {
|
||||||
categories="categories",
|
categories = "categories",
|
||||||
categoryPlaylists = "categoryPlaylists",
|
categoryPlaylists = "categoryPlaylists",
|
||||||
featuredPlaylists = "featuredPlaylists",
|
featuredPlaylists = "featuredPlaylists",
|
||||||
genrePlaylists="genrePlaylists",
|
genrePlaylists = "genrePlaylists",
|
||||||
playlistTracks="playlistTracks",
|
playlistTracks = "playlistTracks",
|
||||||
userPlaylists = "user-palylists",
|
userPlaylists = "user-palylists",
|
||||||
userSavedTracks = "user-saved-tracks",
|
userSavedTracks = "user-saved-tracks",
|
||||||
search = "search",
|
search = "search",
|
||||||
searchPlaylist = "searchPlaylist",
|
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 path from "path";
|
||||||
import isUrl from "is-url";
|
import isUrl from "is-url";
|
||||||
import * as fs from "fs"
|
import fs from "fs";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Stream } from "stream";
|
import { Stream } from "stream";
|
||||||
import { streamToBuffer } from "./streamToBuffer";
|
import { streamToBuffer } from "./streamToBuffer";
|
||||||
@ -8,7 +8,6 @@ import Jimp from "jimp";
|
|||||||
import du from "du";
|
import du from "du";
|
||||||
import { cacheDir } from "../conf";
|
import { cacheDir } from "../conf";
|
||||||
|
|
||||||
|
|
||||||
interface ImageDimensions {
|
interface ImageDimensions {
|
||||||
height: number;
|
height: number;
|
||||||
width: number;
|
width: number;
|
||||||
@ -22,20 +21,20 @@ export async function getCachedImageBuffer(name: string, dims?: ImageDimensions)
|
|||||||
const cacheImgFolder = path.join(cacheDir, "images");
|
const cacheImgFolder = path.join(cacheDir, "images");
|
||||||
// for clearing up the cache if it reaches out of the size
|
// for clearing up the cache if it reaches out of the size
|
||||||
const cacheName = `${isUrl(name) ? name.split("/").slice(-1)[0] : name}.cnim`;
|
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
|
// 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
|
// automatically removing cache after a certain 50 MB oversize
|
||||||
if ((await du(cacheImgFolder)) > MB_5) {
|
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;
|
const cachedImgMeta = (await Jimp.read(cachedImg)).bitmap;
|
||||||
|
|
||||||
// if the dimensions are changed then the previously cached
|
// if the dimensions are changed then the previously cached
|
||||||
// images are removed and replaced with a new one
|
// images are removed and replaced with a new one
|
||||||
if (dims && (cachedImgMeta.height !== dims.height || cachedImgMeta.width !== dims?.width)) {
|
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 await imageResizeAndWrite(cachedImg, { cacheFolder: cacheImgFolder, cacheName, dims });
|
||||||
}
|
}
|
||||||
return cachedImg;
|
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> {
|
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)
|
// caching the images by resizing if the max/fixed (Width/Height)
|
||||||
// is available in the args
|
// 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);
|
const resizedImgBuffer = await resizedImg.getBufferAsync(resizedImg._originalMime);
|
||||||
await fsm.writeFile(path.join(cacheFolder, cacheName), resizedImgBuffer);
|
await fsm.writeFile(path.join(cacheFolder, cacheName), resizedImgBuffer);
|
||||||
return resizedImgBuffer;
|
return resizedImgBuffer;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import { LocalStorageKeys } from "../app";
|
import { LocalStorageKeys } from "../conf";
|
||||||
import authContext from "../context/authContext";
|
import authContext from "../context/authContext";
|
||||||
import showError from "../helpers/showError";
|
import showError from "../helpers/showError";
|
||||||
import spotifyApi from "../initializations/spotifyApi";
|
import spotifyApi from "../initializations/spotifyApi";
|
||||||
|
Loading…
Reference in New Issue
Block a user