mirror of
https://github.com/GiriNeko/YesPlayMusic.git
synced 2025-12-17 05:38:04 +00:00
feat: updates
This commit is contained in:
parent
196a974a64
commit
8f4c3d8e5b
24 changed files with 572 additions and 93 deletions
84
packages/electron/main/appleMusic.ts
Normal file
84
packages/electron/main/appleMusic.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { logger } from '@sentry/utils'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
// 'https://mvod.itunes.apple.com/itunes-assets/HLSMusic116/v4/de/52/95/de52957b-fcf1-ae96-b114-0445cb8c41d4/P359420813_default.m3u8'
|
||||||
|
|
||||||
|
const searchAlbum = async (
|
||||||
|
keyword: string
|
||||||
|
): Promise<
|
||||||
|
| {
|
||||||
|
id: string
|
||||||
|
href: string
|
||||||
|
attributes: {
|
||||||
|
artistName: string
|
||||||
|
url: string
|
||||||
|
name: string
|
||||||
|
editorialNotes?: {
|
||||||
|
standard: string
|
||||||
|
short: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
> => {
|
||||||
|
const r = await axios.get(
|
||||||
|
`https://amp-api.music.apple.com/v1/catalog/cn/search`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
term: keyword,
|
||||||
|
l: 'zh-cn',
|
||||||
|
platform: 'web',
|
||||||
|
types: 'albums',
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
authorization: 'Bearer xxxxxx', // required token
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return r.data?.results?.albums?.data?.[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCoverVideo = async ({
|
||||||
|
name,
|
||||||
|
artists,
|
||||||
|
}: {
|
||||||
|
name: string
|
||||||
|
artists: string[]
|
||||||
|
}): Promise<string | undefined> => {
|
||||||
|
const keyword = `${artists.join(' ')} ${name}`
|
||||||
|
logger.debug(`[appleMusic] getCoverVideo: ${keyword}`)
|
||||||
|
const album = await searchAlbum(keyword).catch(e => {
|
||||||
|
console.log(e)
|
||||||
|
logger.debug('[appleMusic] Search album error', e)
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = album?.attributes.url
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
logger.info('[appleMusic] no url')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let { data: html } = await axios.get(url)
|
||||||
|
if (!html) return
|
||||||
|
|
||||||
|
const regex =
|
||||||
|
/<script type="fastboot\/shoebox" id="shoebox-media-api-cache-amp-music">(.*?)<\/script>/
|
||||||
|
html = html
|
||||||
|
.match(regex)[0]
|
||||||
|
.replace(
|
||||||
|
'<script type="fastboot/shoebox" id="shoebox-media-api-cache-amp-music">',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
.replace('</script>', '')
|
||||||
|
html = JSON.parse(html)
|
||||||
|
const data = JSON.parse(html[Object.keys(html)[1]])
|
||||||
|
const m3u8 =
|
||||||
|
data?.d?.[0]?.attributes?.editorialVideo?.motionSquareVideo1x1?.video
|
||||||
|
|
||||||
|
logger.debug(`[appleMusic] ${m3u8}`)
|
||||||
|
|
||||||
|
return m3u8
|
||||||
|
}
|
||||||
|
|
@ -31,8 +31,9 @@ class Cache {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case APIs.Track: {
|
case APIs.Track: {
|
||||||
if (!data.songs) return
|
const res = data as FetchTracksResponse
|
||||||
const tracks = (data as FetchTracksResponse).songs.map(t => ({
|
if (!res.songs) return
|
||||||
|
const tracks = res.songs.map(t => ({
|
||||||
id: t.id,
|
id: t.id,
|
||||||
json: JSON.stringify(t),
|
json: JSON.stringify(t),
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
|
|
@ -106,6 +107,16 @@ class Cache {
|
||||||
db.upsert(Tables.CoverColor, {
|
db.upsert(Tables.CoverColor, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
color: data.color,
|
color: data.color,
|
||||||
|
queriedAt: Date.now(),
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case APIs.VideoCover: {
|
||||||
|
if (!data.id) return
|
||||||
|
db.upsert(Tables.VideoCover, {
|
||||||
|
id: data.id,
|
||||||
|
url: data.url || 'no',
|
||||||
|
queriedAt: Date.now(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +207,10 @@ class Cache {
|
||||||
if (isNaN(Number(params?.id))) return
|
if (isNaN(Number(params?.id))) return
|
||||||
return db.find(Tables.CoverColor, params.id)?.color
|
return db.find(Tables.CoverColor, params.id)?.color
|
||||||
}
|
}
|
||||||
|
case APIs.VideoCover: {
|
||||||
|
if (isNaN(Number(params?.id))) return
|
||||||
|
return db.find(Tables.VideoCover, params.id)?.url
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,7 +293,7 @@ class Cache {
|
||||||
br,
|
br,
|
||||||
type: type as TablesStructures[Tables.Audio]['type'],
|
type: type as TablesStructures[Tables.Audio]['type'],
|
||||||
source,
|
source,
|
||||||
updatedAt: Date.now(),
|
queriedAt: Date.now(),
|
||||||
})
|
})
|
||||||
|
|
||||||
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export const enum Tables {
|
||||||
AccountData = 'AccountData',
|
AccountData = 'AccountData',
|
||||||
CoverColor = 'CoverColor',
|
CoverColor = 'CoverColor',
|
||||||
AppData = 'AppData',
|
AppData = 'AppData',
|
||||||
|
VideoCover = 'VideoCover',
|
||||||
}
|
}
|
||||||
interface CommonTableStructure {
|
interface CommonTableStructure {
|
||||||
id: number
|
id: number
|
||||||
|
|
@ -50,16 +51,22 @@ export interface TablesStructures {
|
||||||
| 'qq'
|
| 'qq'
|
||||||
| 'bilibili'
|
| 'bilibili'
|
||||||
| 'joox'
|
| 'joox'
|
||||||
updatedAt: number
|
queriedAt: number
|
||||||
}
|
}
|
||||||
[Tables.CoverColor]: {
|
[Tables.CoverColor]: {
|
||||||
id: number
|
id: number
|
||||||
color: string
|
color: string
|
||||||
|
queriedAt: number
|
||||||
}
|
}
|
||||||
[Tables.AppData]: {
|
[Tables.AppData]: {
|
||||||
id: 'appVersion' | 'skippedVersion'
|
id: 'appVersion' | 'skippedVersion'
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
[Tables.VideoCover]: {
|
||||||
|
id: number
|
||||||
|
url: string
|
||||||
|
queriedAt: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TableNames = keyof TablesStructures
|
type TableNames = keyof TablesStructures
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { Thumbar } from './windowsTaskbar'
|
||||||
import fastFolderSize from 'fast-folder-size'
|
import fastFolderSize from 'fast-folder-size'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
|
import { getCoverVideo } from './appleMusic'
|
||||||
|
|
||||||
const on = <T extends keyof IpcChannelsParams>(
|
const on = <T extends keyof IpcChannelsParams>(
|
||||||
channel: T,
|
channel: T,
|
||||||
|
|
@ -20,6 +21,16 @@ const on = <T extends keyof IpcChannelsParams>(
|
||||||
ipcMain.on(channel, listener)
|
ipcMain.on(channel, listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handle = <T extends keyof IpcChannelsParams>(
|
||||||
|
channel: T,
|
||||||
|
listener: (
|
||||||
|
event: Electron.IpcMainInvokeEvent,
|
||||||
|
params: IpcChannelsParams[T]
|
||||||
|
) => void
|
||||||
|
) => {
|
||||||
|
return ipcMain.handle(channel, listener)
|
||||||
|
}
|
||||||
|
|
||||||
export function initIpcMain(
|
export function initIpcMain(
|
||||||
win: BrowserWindow | null,
|
win: BrowserWindow | null,
|
||||||
tray: YPMTray | null,
|
tray: YPMTray | null,
|
||||||
|
|
@ -143,6 +154,22 @@ function initOtherIpcMain() {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存动态专辑封面
|
||||||
|
*/
|
||||||
|
on(IpcChannels.SetVideoCover, (event, args) => {
|
||||||
|
const { id, url } = args
|
||||||
|
cache.set(APIs.VideoCover, { id, url })
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取动态专辑封面
|
||||||
|
*/
|
||||||
|
on(IpcChannels.GetVideoCover, (event, args) => {
|
||||||
|
const { id } = args
|
||||||
|
event.returnValue = cache.get(APIs.VideoCover, { id })
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出tables到json文件,方便查看table大小(dev环境)
|
* 导出tables到json文件,方便查看table大小(dev环境)
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ contextBridge.exposeInMainWorld('log', log)
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||||
sendSync: ipcRenderer.sendSync,
|
sendSync: ipcRenderer.sendSync,
|
||||||
|
invoke: ipcRenderer.invoke,
|
||||||
send: ipcRenderer.send,
|
send: ipcRenderer.send,
|
||||||
on: (
|
on: (
|
||||||
channel: IpcChannels,
|
channel: IpcChannels,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ CREATE TABLE IF NOT EXISTS "AccountData" ("id" text NOT NULL,"json" text NOT NUL
|
||||||
CREATE TABLE IF NOT EXISTS "Album" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Album" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "ArtistAlbum" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "ArtistAlbum" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Artist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Artist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Audio" ("id" integer NOT NULL,"br" int NOT NULL,"type" text NOT NULL,"source" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
|
||||||
CREATE TABLE IF NOT EXISTS "Lyric" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" integer NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Lyric" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" integer NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Playlist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Playlist" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "Track" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "Track" ("id" integer NOT NULL,"json" text NOT NULL,"updatedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
CREATE TABLE IF NOT EXISTS "CoverColor" ("id" integer NOT NULL,"color" text NOT NULL, PRIMARY KEY (id));
|
|
||||||
CREATE TABLE IF NOT EXISTS "AppData" ("id" text NOT NULL,"value" text, PRIMARY KEY (id));
|
CREATE TABLE IF NOT EXISTS "AppData" ("id" text NOT NULL,"value" text, PRIMARY KEY (id));
|
||||||
|
CREATE TABLE IF NOT EXISTS "CoverColor" ("id" integer NOT NULL,"color" text NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
|
CREATE TABLE IF NOT EXISTS "Audio" ("id" integer NOT NULL,"br" int NOT NULL,"type" text NOT NULL,"source" text NOT NULL,"updatedAt" int NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
|
CREATE TABLE IF NOT EXISTS "VideoCover" ("id" integer NOT NULL,"url" text NOT NULL,"updatedAt" int NOT NULL, "queriedAt" int NOT NULL, PRIMARY KEY (id));
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"dev": "node scripts/build.main.mjs --watch",
|
"dev": "node scripts/build.main.mjs --watch",
|
||||||
"build": "node scripts/build.main.mjs",
|
"build": "node scripts/build.main.mjs",
|
||||||
"pack": "electron-builder build -c .electron-builder.config.js",
|
"pack": "electron-builder build -c .electron-builder.config.js",
|
||||||
"test:types": "tsc --noEmit --project src/main/tsconfig.json",
|
"test:types": "tsc --noEmit --project ./tsconfig.json",
|
||||||
"lint": "eslint --ext .ts,.js ./",
|
"lint": "eslint --ext .ts,.js ./",
|
||||||
"format": "prettier --write './**/*.{ts,js,tsx,jsx}'"
|
"format": "prettier --write './**/*.{ts,js,tsx,jsx}'"
|
||||||
},
|
},
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"fast-folder-size": "^1.7.0",
|
"fast-folder-size": "^1.7.0",
|
||||||
|
"m3u8-parser": "^4.7.1",
|
||||||
"pretty-bytes": "^6.0.0"
|
"pretty-bytes": "^6.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ export const enum APIs {
|
||||||
Album = 'album',
|
Album = 'album',
|
||||||
Artist = 'artists',
|
Artist = 'artists',
|
||||||
ArtistAlbum = 'artist/album',
|
ArtistAlbum = 'artist/album',
|
||||||
CoverColor = 'cover_color',
|
|
||||||
Likelist = 'likelist',
|
Likelist = 'likelist',
|
||||||
Lyric = 'lyric',
|
Lyric = 'lyric',
|
||||||
Personalized = 'personalized',
|
Personalized = 'personalized',
|
||||||
|
|
@ -33,13 +32,16 @@ export const enum APIs {
|
||||||
UserAlbums = 'album/sublist',
|
UserAlbums = 'album/sublist',
|
||||||
UserArtists = 'artist/sublist',
|
UserArtists = 'artist/sublist',
|
||||||
UserPlaylist = 'user/playlist',
|
UserPlaylist = 'user/playlist',
|
||||||
|
|
||||||
|
// not netease api
|
||||||
|
CoverColor = 'cover_color',
|
||||||
|
VideoCover = 'video_cover',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIsParams {
|
export interface APIsParams {
|
||||||
[APIs.Album]: { id: number }
|
[APIs.Album]: { id: number }
|
||||||
[APIs.Artist]: { id: number }
|
[APIs.Artist]: { id: number }
|
||||||
[APIs.ArtistAlbum]: { id: number }
|
[APIs.ArtistAlbum]: { id: number }
|
||||||
[APIs.CoverColor]: { id: number }
|
|
||||||
[APIs.Likelist]: void
|
[APIs.Likelist]: void
|
||||||
[APIs.Lyric]: { id: number }
|
[APIs.Lyric]: { id: number }
|
||||||
[APIs.Personalized]: void
|
[APIs.Personalized]: void
|
||||||
|
|
@ -51,13 +53,14 @@ export interface APIsParams {
|
||||||
[APIs.UserAlbums]: void
|
[APIs.UserAlbums]: void
|
||||||
[APIs.UserArtists]: void
|
[APIs.UserArtists]: void
|
||||||
[APIs.UserPlaylist]: void
|
[APIs.UserPlaylist]: void
|
||||||
|
[APIs.CoverColor]: { id: number }
|
||||||
|
[APIs.VideoCover]: { id: number }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIsResponse {
|
export interface APIsResponse {
|
||||||
[APIs.Album]: FetchAlbumResponse
|
[APIs.Album]: FetchAlbumResponse
|
||||||
[APIs.Artist]: FetchArtistResponse
|
[APIs.Artist]: FetchArtistResponse
|
||||||
[APIs.ArtistAlbum]: FetchArtistAlbumsResponse
|
[APIs.ArtistAlbum]: FetchArtistAlbumsResponse
|
||||||
[APIs.CoverColor]: string | undefined
|
|
||||||
[APIs.Likelist]: FetchUserLikedTracksIDsResponse
|
[APIs.Likelist]: FetchUserLikedTracksIDsResponse
|
||||||
[APIs.Lyric]: FetchLyricResponse
|
[APIs.Lyric]: FetchLyricResponse
|
||||||
[APIs.Personalized]: FetchRecommendedPlaylistsResponse
|
[APIs.Personalized]: FetchRecommendedPlaylistsResponse
|
||||||
|
|
@ -69,4 +72,6 @@ export interface APIsResponse {
|
||||||
[APIs.UserAlbums]: FetchUserAlbumsResponse
|
[APIs.UserAlbums]: FetchUserAlbumsResponse
|
||||||
[APIs.UserArtists]: FetchUserArtistsResponse
|
[APIs.UserArtists]: FetchUserArtistsResponse
|
||||||
[APIs.UserPlaylist]: FetchUserPlaylistsResponse
|
[APIs.UserPlaylist]: FetchUserPlaylistsResponse
|
||||||
|
[APIs.CoverColor]: string | undefined
|
||||||
|
[APIs.VideoCover]: string | undefined
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export const enum IpcChannels {
|
||||||
SyncSettings = 'SyncSettings',
|
SyncSettings = 'SyncSettings',
|
||||||
GetAudioCacheSize = 'GetAudioCacheSize',
|
GetAudioCacheSize = 'GetAudioCacheSize',
|
||||||
ResetWindowSize = 'ResetWindowSize',
|
ResetWindowSize = 'ResetWindowSize',
|
||||||
|
GetVideoCover = 'GetVideoCover',
|
||||||
|
SetVideoCover = 'SetVideoCover',
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcMain.on params
|
// ipcMain.on params
|
||||||
|
|
@ -58,6 +60,8 @@ export interface IpcChannelsParams {
|
||||||
[IpcChannels.SyncSettings]: Store['settings']
|
[IpcChannels.SyncSettings]: Store['settings']
|
||||||
[IpcChannels.GetAudioCacheSize]: void
|
[IpcChannels.GetAudioCacheSize]: void
|
||||||
[IpcChannels.ResetWindowSize]: void
|
[IpcChannels.ResetWindowSize]: void
|
||||||
|
[IpcChannels.GetVideoCover]: { id: number }
|
||||||
|
[IpcChannels.SetVideoCover]: { id: number; url: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipcRenderer.on params
|
// ipcRenderer.on params
|
||||||
|
|
@ -79,4 +83,6 @@ export interface IpcChannelsReturns {
|
||||||
[IpcChannels.Like]: void
|
[IpcChannels.Like]: void
|
||||||
[IpcChannels.Repeat]: RepeatMode
|
[IpcChannels.Repeat]: RepeatMode
|
||||||
[IpcChannels.GetAudioCacheSize]: void
|
[IpcChannels.GetAudioCacheSize]: void
|
||||||
|
[IpcChannels.GetVideoCover]: string | undefined
|
||||||
|
[IpcChannels.SetVideoCover]: void
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const Image = ({
|
||||||
className?: string
|
className?: string
|
||||||
alt: string
|
alt: string
|
||||||
lazyLoad?: boolean
|
lazyLoad?: boolean
|
||||||
placeholder?: 'artist' | 'album' | 'playlist' | 'podcast' | 'blank' | null
|
placeholder?: 'artist' | 'album' | 'playlist' | 'podcast' | 'blank' | false
|
||||||
onClick?: (e: React.MouseEvent<HTMLImageElement>) => void
|
onClick?: (e: React.MouseEvent<HTMLImageElement>) => void
|
||||||
onMouseOver?: (e: React.MouseEvent<HTMLImageElement>) => void
|
onMouseOver?: (e: React.MouseEvent<HTMLImageElement>) => void
|
||||||
animation?: boolean
|
animation?: boolean
|
||||||
|
|
@ -48,6 +48,7 @@ const Image = ({
|
||||||
? {
|
? {
|
||||||
animate,
|
animate,
|
||||||
initial: { opacity: 0 },
|
initial: { opacity: 0 },
|
||||||
|
exit: { opacity: 0 },
|
||||||
transition,
|
transition,
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|
@ -60,8 +61,15 @@ const Image = ({
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('relative overflow-hidden', className)}>
|
<div
|
||||||
|
className={cx(
|
||||||
|
'overflow-hidden',
|
||||||
|
className,
|
||||||
|
className?.includes('absolute') === false && 'relative'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{/* Image */}
|
{/* Image */}
|
||||||
|
<AnimatePresence>
|
||||||
<motion.img
|
<motion.img
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className='absolute inset-0 h-full w-full'
|
className='absolute inset-0 h-full w-full'
|
||||||
|
|
@ -76,6 +84,7 @@ const Image = ({
|
||||||
onMouseOver={onMouseOver}
|
onMouseOver={onMouseOver}
|
||||||
{...motionProps}
|
{...motionProps}
|
||||||
/>
|
/>
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* Placeholder / Error fallback */}
|
{/* Placeholder / Error fallback */}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import Router from './Router'
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
return (
|
return (
|
||||||
<main
|
<main
|
||||||
|
id='main'
|
||||||
className={cx(
|
className={cx(
|
||||||
'no-scrollbar overflow-y-auto pb-16 pr-6 pl-10',
|
'no-scrollbar overflow-y-auto pb-16 pr-6 pl-10',
|
||||||
css`
|
css`
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import Image from './Image'
|
import Image from './Image'
|
||||||
import Wave from './Wave'
|
import Wave from './Wave'
|
||||||
import Icon from '@/web/components/Icon'
|
import Icon from '@/web/components/Icon'
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
import { useWindowSize } from 'react-use'
|
||||||
|
import { playerWidth, topbarHeight } from '@/web/utils/const'
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -46,13 +50,13 @@ const Track = ({
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className='flex items-center justify-between'
|
className='flex items-center justify-between'
|
||||||
initial={{ opacity: 0 }}
|
// initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
// animate={{ opacity: 1 }}
|
||||||
exit={{ x: '100%', opacity: 0 }}
|
// exit={{ x: '100%', opacity: 0 }}
|
||||||
transition={{
|
// transition={{
|
||||||
duration: 0.24,
|
// duration: 0.24,
|
||||||
}}
|
// }}
|
||||||
layout
|
// layout
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
if (e.detail === 2 && track?.id) player.playTrack(track.id)
|
if (e.detail === 2 && track?.id) player.playTrack(track.id)
|
||||||
}}
|
}}
|
||||||
|
|
@ -62,6 +66,8 @@ const Track = ({
|
||||||
alt='Cover'
|
alt='Cover'
|
||||||
className='mr-4 aspect-square h-14 w-14 flex-shrink-0 rounded-12'
|
className='mr-4 aspect-square h-14 w-14 flex-shrink-0 rounded-12'
|
||||||
src={resizeImage(track?.al?.picUrl || '', 'sm')}
|
src={resizeImage(track?.al?.picUrl || '', 'sm')}
|
||||||
|
animation={false}
|
||||||
|
placeholder={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Track info */}
|
{/* Track info */}
|
||||||
|
|
@ -93,41 +99,82 @@ const Track = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PlayingNext = ({ className }: { className?: string }) => {
|
const TrackList = ({ className }: { className?: string }) => {
|
||||||
const { trackList, trackIndex, state } = useSnapshot(player)
|
const { trackList, trackIndex, state } = useSnapshot(player)
|
||||||
const { data: tracks } = useTracks({ ids: trackList })
|
const { data: tracksRaw } = useTracks({ ids: trackList })
|
||||||
|
const tracks = tracksRaw?.songs || []
|
||||||
|
const parentRef = useRef<HTMLDivElement>(null)
|
||||||
|
const { height } = useWindowSize()
|
||||||
|
|
||||||
|
const listHeight = height - topbarHeight - playerWidth - 24 - 20 // 24是封面与底部间距,20是list与封面间距
|
||||||
|
|
||||||
|
const rowVirtualizer = useVirtualizer({
|
||||||
|
count: tracks.length,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 76,
|
||||||
|
overscan: 5,
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
ref={parentRef}
|
||||||
|
style={{
|
||||||
|
height: `${listHeight}px`,
|
||||||
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
'no-scrollbar relative z-10 overflow-scroll',
|
'no-scrollbar relative z-10 w-full overflow-auto',
|
||||||
className,
|
className,
|
||||||
css`
|
css`
|
||||||
padding-top: 42px;
|
padding-top: 42px;
|
||||||
mask-image: linear-gradient(to bottom, transparent 0, black 42px);
|
mask-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent 0,
|
||||||
|
black 42px
|
||||||
|
); // 顶部渐变遮罩
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<motion.div className='grid gap-4'>
|
<div
|
||||||
<AnimatePresence>
|
className='relative w-full'
|
||||||
{tracks?.songs?.map((track, index) => (
|
style={{
|
||||||
|
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{rowVirtualizer.getVirtualItems().map((row: any) => (
|
||||||
|
<div
|
||||||
|
key={row.index}
|
||||||
|
className='absolute top-0 left-0 w-full'
|
||||||
|
style={{
|
||||||
|
height: `${row.size}px`,
|
||||||
|
transform: `translateY(${row.start}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Track
|
<Track
|
||||||
key={track.id}
|
track={tracks?.[row.index]}
|
||||||
track={track}
|
index={row.index}
|
||||||
index={index}
|
|
||||||
playingTrackIndex={trackIndex}
|
playingTrackIndex={trackIndex}
|
||||||
state={state}
|
state={state}
|
||||||
/>
|
/>
|
||||||
))}
|
|
||||||
|
|
||||||
{(tracks?.songs?.length || 0) >= 4 && (
|
|
||||||
<div className='pointer-events-none sticky bottom-0 h-8 w-full bg-gradient-to-t from-black'></div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部渐变遮罩 */}
|
||||||
|
<div
|
||||||
|
className='pointer-events-none absolute right-0 left-0 z-20 h-14 bg-gradient-to-t from-black to-transparent'
|
||||||
|
style={{ top: `${listHeight - 56}px` }}
|
||||||
|
></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayingNext = ({ className }: { className?: string }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<TrackList className={className} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,113 @@
|
||||||
import { formatDate, formatDuration, resizeImage } from '@/web/utils/common'
|
import {
|
||||||
|
formatDate,
|
||||||
|
formatDuration,
|
||||||
|
isIOS,
|
||||||
|
isSafari,
|
||||||
|
resizeImage,
|
||||||
|
} from '@/web/utils/common'
|
||||||
import { css, cx } from '@emotion/css'
|
import { css, cx } from '@emotion/css'
|
||||||
import Icon from '@/web/components/Icon'
|
import Icon from '@/web/components/Icon'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useMemo } from 'react'
|
|
||||||
import Image from './Image'
|
import Image from './Image'
|
||||||
import useIsMobile from '@/web/hooks/useIsMobile'
|
import useIsMobile from '@/web/hooks/useIsMobile'
|
||||||
|
import { memo, useEffect, useMemo, useRef } from 'react'
|
||||||
|
import Hls from 'hls.js'
|
||||||
|
import Plyr, { APITypes, PlyrProps, PlyrInstance } from 'plyr-react'
|
||||||
|
import useVideoCover from '@/web/hooks/useVideoCover'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { ease } from '@/web/utils/const'
|
||||||
|
import { injectGlobal } from '@emotion/css'
|
||||||
|
|
||||||
const TrackListHeader = ({
|
injectGlobal`
|
||||||
album,
|
.plyr__video-wrapper,
|
||||||
playlist,
|
.plyr--video {
|
||||||
onPlay,
|
background-color: transparent !important;
|
||||||
}: {
|
}
|
||||||
album?: Album
|
`
|
||||||
playlist?: Playlist
|
|
||||||
onPlay: () => void
|
const VideoCover = ({ source }: { source?: string }) => {
|
||||||
}) => {
|
const ref = useRef<APITypes>(null)
|
||||||
const albumDuration = useMemo(() => {
|
useEffect(() => {
|
||||||
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
|
const loadVideo = async () => {
|
||||||
return formatDuration(duration, 'en', 'hh[hr] mm[min]')
|
if (!source || !Hls.isSupported()) return
|
||||||
}, [album?.songs])
|
const video = document.getElementById('plyr') as HTMLVideoElement
|
||||||
|
const hls = new Hls()
|
||||||
|
hls.loadSource(source)
|
||||||
|
hls.attachMedia(video)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
ref.current!.plyr.media = video
|
||||||
|
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
||||||
|
;(ref.current!.plyr as PlyrInstance).play()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loadVideo()
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='z-10 aspect-square overflow-hidden rounded-24'>
|
||||||
|
<Plyr
|
||||||
|
id='plyr'
|
||||||
|
options={{
|
||||||
|
volume: 0,
|
||||||
|
controls: [],
|
||||||
|
autoplay: true,
|
||||||
|
clickToPlay: false,
|
||||||
|
loop: {
|
||||||
|
active: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
source={{} as PlyrProps['source']}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Cover = memo(
|
||||||
|
({ album, playlist }: { album?: Album; playlist?: Playlist }) => {
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
const { data: videoCover } = useVideoCover({
|
||||||
|
id: album?.id,
|
||||||
|
name: album?.name,
|
||||||
|
artist: album?.artist.name,
|
||||||
|
})
|
||||||
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={cx(
|
<div className='relative z-10 aspect-square w-full overflow-auto rounded-24 '>
|
||||||
'mx-2.5 rounded-48 p-8 dark:bg-white/10',
|
|
||||||
'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
|
|
||||||
!isMobile &&
|
|
||||||
css`
|
|
||||||
grid-template-columns: 318px auto;
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* Cover */}
|
|
||||||
<Image
|
<Image
|
||||||
className='z-10 aspect-square w-full rounded-24'
|
className='absolute inset-0 h-full w-full'
|
||||||
src={resizeImage(cover, 'lg')}
|
src={resizeImage(cover, 'lg')}
|
||||||
alt='Cover'
|
alt='Cover'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{videoCover && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: isIOS ? 1 : 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6, ease }}
|
||||||
|
className='absolute inset-0 h-full w-full'
|
||||||
|
>
|
||||||
|
{isSafari ? (
|
||||||
|
<video
|
||||||
|
src={videoCover}
|
||||||
|
className='h-full w-full'
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
></video>
|
||||||
|
) : (
|
||||||
|
<VideoCover source={videoCover} />
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Blur bg */}
|
{/* Blur bg */}
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<img
|
<img
|
||||||
|
|
@ -57,6 +124,39 @@ const TrackListHeader = ({
|
||||||
src={resizeImage(cover, 'sm')}
|
src={resizeImage(cover, 'sm')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Cover.displayName = 'Cover'
|
||||||
|
|
||||||
|
const TrackListHeader = ({
|
||||||
|
album,
|
||||||
|
playlist,
|
||||||
|
onPlay,
|
||||||
|
}: {
|
||||||
|
album?: Album
|
||||||
|
playlist?: Playlist
|
||||||
|
onPlay: () => void
|
||||||
|
}) => {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
const albumDuration = useMemo(() => {
|
||||||
|
const duration = album?.songs?.reduce((acc, cur) => acc + cur.dt, 0) || 0
|
||||||
|
return formatDuration(duration, 'en', 'hh[hr] mm[min]')
|
||||||
|
}, [album?.songs])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'z-10 mx-2.5 rounded-48 p-8 dark:bg-white/10',
|
||||||
|
'lg:mx-0 lg:grid lg:grid-rows-1 lg:gap-10 lg:rounded-none lg:p-0 lg:dark:bg-transparent',
|
||||||
|
!isMobile &&
|
||||||
|
css`
|
||||||
|
grid-template-columns: 318px auto;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Cover {...{ album, playlist }} />
|
||||||
|
|
||||||
<div className='flex flex-col justify-between'>
|
<div className='flex flex-col justify-between'>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
4
packages/web/global.d.ts
vendored
4
packages/web/global.d.ts
vendored
|
|
@ -10,6 +10,10 @@ declare global {
|
||||||
channel: T,
|
channel: T,
|
||||||
params?: IpcChannelsParams[T]
|
params?: IpcChannelsParams[T]
|
||||||
) => IpcChannelsReturns[T]
|
) => IpcChannelsReturns[T]
|
||||||
|
invoke: <T extends keyof IpcChannelsParams>(
|
||||||
|
channel: T,
|
||||||
|
params?: IpcChannelsParams[T]
|
||||||
|
) => Promise<IpcChannelsReturns[T]>
|
||||||
send: <T extends keyof IpcChannelsParams>(
|
send: <T extends keyof IpcChannelsParams>(
|
||||||
channel: T,
|
channel: T,
|
||||||
params?: IpcChannelsParams[T]
|
params?: IpcChannelsParams[T]
|
||||||
|
|
|
||||||
43
packages/web/hooks/useVideoCover.ts
Normal file
43
packages/web/hooks/useVideoCover.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { IpcChannels } from '@/shared/IpcChannels'
|
||||||
|
import axios from 'axios'
|
||||||
|
import { useQuery } from 'react-query'
|
||||||
|
|
||||||
|
export default function useVideoCover(props: {
|
||||||
|
id?: number
|
||||||
|
name?: string
|
||||||
|
artist?: string
|
||||||
|
}) {
|
||||||
|
const { id, name, artist } = props
|
||||||
|
return useQuery(
|
||||||
|
['useVideoCover', props],
|
||||||
|
async () => {
|
||||||
|
if (!id || !name || !artist) return
|
||||||
|
|
||||||
|
const fromCache = window.ipcRenderer?.sendSync(
|
||||||
|
IpcChannels.GetVideoCover,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (fromCache) {
|
||||||
|
return fromCache === 'no' ? undefined : fromCache
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromRemote = await axios.get('/yesplaymusic/video-cover', {
|
||||||
|
params: props,
|
||||||
|
})
|
||||||
|
window.ipcRenderer?.send(IpcChannels.SetVideoCover, {
|
||||||
|
id,
|
||||||
|
url: fromRemote.data.url || '',
|
||||||
|
})
|
||||||
|
if (fromRemote?.data?.url) {
|
||||||
|
return fromRemote.data.url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!id && !!name && !!artist,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchInterval: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/src/public/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/src/public/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' www.googletagmanager.com;" />
|
<meta http-equiv="Content-Security-Policy"
|
||||||
|
content="script-src 'self' 'unsafe-inline' www.googletagmanager.com blob:;" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<title>YesPlayMusic</title>
|
<title>YesPlayMusic</title>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import ReactGA from 'react-ga4'
|
||||||
import { ipcRenderer } from './ipcRenderer'
|
import { ipcRenderer } from './ipcRenderer'
|
||||||
import { QueryClientProvider } from 'react-query'
|
import { QueryClientProvider } from 'react-query'
|
||||||
import reactQueryClient from '@/web/utils/reactQueryClient'
|
import reactQueryClient from '@/web/utils/reactQueryClient'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
|
||||||
ReactGA.initialize('G-KMJJCFZDKF')
|
ReactGA.initialize('G-KMJJCFZDKF')
|
||||||
|
|
||||||
|
|
@ -34,12 +35,23 @@ ipcRenderer()
|
||||||
const container = document.getElementById('root') as HTMLElement
|
const container = document.getElementById('root') as HTMLElement
|
||||||
const root = ReactDOMClient.createRoot(container)
|
const root = ReactDOMClient.createRoot(container)
|
||||||
|
|
||||||
root.render(
|
// root.render(
|
||||||
|
// <StrictMode>
|
||||||
|
// <BrowserRouter>
|
||||||
|
// <QueryClientProvider client={reactQueryClient}>
|
||||||
|
// <App />
|
||||||
|
// </QueryClientProvider>
|
||||||
|
// </BrowserRouter>
|
||||||
|
// </StrictMode>
|
||||||
|
// )
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<QueryClientProvider client={reactQueryClient}>
|
<QueryClientProvider client={reactQueryClient}>
|
||||||
<App />
|
<App />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>
|
</StrictMode>,
|
||||||
|
document.getElementById('root')
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,19 @@
|
||||||
"@emotion/css": "^11.9.0",
|
"@emotion/css": "^11.9.0",
|
||||||
"@sentry/react": "^6.19.7",
|
"@sentry/react": "^6.19.7",
|
||||||
"@sentry/tracing": "^6.19.7",
|
"@sentry/tracing": "^6.19.7",
|
||||||
|
"@tanstack/react-virtual": "3.0.0-beta.2",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"color.js": "^1.2.0",
|
"color.js": "^1.2.0",
|
||||||
"colord": "^2.9.2",
|
"colord": "^2.9.2",
|
||||||
"dayjs": "^1.11.1",
|
"dayjs": "^1.11.1",
|
||||||
"framer-motion": "^6.3.4",
|
"framer-motion": "^6.3.4",
|
||||||
|
"hls.js": "^1.1.5",
|
||||||
"howler": "^2.2.3",
|
"howler": "^2.2.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
|
"plyr": "^3.7.2",
|
||||||
|
"plyr-react": "^5.0.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import useArtistAlbums from '@/web/api/hooks/useArtistAlbums'
|
||||||
import { css, cx } from '@emotion/css'
|
import { css, cx } from '@emotion/css'
|
||||||
import CoverRow from '@/web/components/New/CoverRow'
|
import CoverRow from '@/web/components/New/CoverRow'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
import 'plyr-react/plyr.css'
|
||||||
|
|
||||||
const MoreByArtist = ({ album }: { album?: Album }) => {
|
const MoreByArtist = ({ album }: { album?: Album }) => {
|
||||||
const { data: albums } = useArtistAlbums({
|
const { data: albums } = useArtistAlbums({
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,10 @@ const Albums = () => {
|
||||||
const Playlists = () => {
|
const Playlists = () => {
|
||||||
const { data: playlists } = useUserPlaylists()
|
const { data: playlists } = useUserPlaylists()
|
||||||
return (
|
return (
|
||||||
<CoverRow playlists={playlists?.playlist} className='mt-6 px-2.5 lg:px-0' />
|
<CoverRow
|
||||||
|
playlists={playlists?.playlist?.slice(1)}
|
||||||
|
className='mt-6 px-2.5 lg:px-0'
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,9 @@ export async function calcCoverColor(coverUrl: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
export const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
|
||||||
export const isSafari = /Safari/.test(navigator.userAgent)
|
export const isSafari = /^((?!chrome|android).)*safari/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
)
|
||||||
export const isPWA =
|
export const isPWA =
|
||||||
(navigator as any).standalone ||
|
(navigator as any).standalone ||
|
||||||
window.matchMedia('(display-mode: standalone)').matches
|
window.matchMedia('(display-mode: standalone)').matches
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
// 动画曲线
|
||||||
export const ease: [number, number, number, number] = [0.4, 0, 0.2, 1]
|
export const ease: [number, number, number, number] = [0.4, 0, 0.2, 1]
|
||||||
|
|
||||||
|
// 屏幕断点
|
||||||
export const breakpoint = {
|
export const breakpoint = {
|
||||||
sm: '@media (min-width: 640px)',
|
sm: '@media (min-width: 640px)',
|
||||||
md: '@media (min-width: 768px)',
|
md: '@media (min-width: 768px)',
|
||||||
|
|
@ -6,3 +9,6 @@ export const breakpoint = {
|
||||||
xl: '@media (min-width: 1280px)',
|
xl: '@media (min-width: 1280px)',
|
||||||
'2xl': '@media (min-width: 1536px)',
|
'2xl': '@media (min-width: 1536px)',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const topbarHeight = 132 // 桌面端顶栏高度 (px)
|
||||||
|
export const playerWidth = 318 // 桌面端播放器宽度 (px)
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,11 @@ export default defineConfig({
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path => (IS_ELECTRON ? path : path.replace(/^\/netease/, '')),
|
rewrite: path => (IS_ELECTRON ? path : path.replace(/^\/netease/, '')),
|
||||||
},
|
},
|
||||||
|
'/yesplaymusic/video-cover': {
|
||||||
|
target: `http://168.138.40.199:51324`,
|
||||||
|
// target: `http://127.0.0.1:51324`,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
'/yesplaymusic/': {
|
'/yesplaymusic/': {
|
||||||
target: `http://127.0.0.1:${
|
target: `http://127.0.0.1:${
|
||||||
process.env.ELECTRON_DEV_NETEASE_API_PORT || 3000
|
process.env.ELECTRON_DEV_NETEASE_API_PORT || 3000
|
||||||
|
|
|
||||||
107
pnpm-lock.yaml
generated
107
pnpm-lock.yaml
generated
|
|
@ -48,6 +48,7 @@ importers:
|
||||||
express: ^4.18.1
|
express: ^4.18.1
|
||||||
express-fileupload: ^1.4.0
|
express-fileupload: ^1.4.0
|
||||||
fast-folder-size: ^1.7.0
|
fast-folder-size: ^1.7.0
|
||||||
|
m3u8-parser: ^4.7.1
|
||||||
minimist: ^1.2.6
|
minimist: ^1.2.6
|
||||||
music-metadata: ^7.12.3
|
music-metadata: ^7.12.3
|
||||||
NeteaseCloudMusicApi: ^4.6.2
|
NeteaseCloudMusicApi: ^4.6.2
|
||||||
|
|
@ -70,6 +71,7 @@ importers:
|
||||||
electron-store: 8.0.1
|
electron-store: 8.0.1
|
||||||
express: 4.18.1
|
express: 4.18.1
|
||||||
fast-folder-size: 1.7.0
|
fast-folder-size: 1.7.0
|
||||||
|
m3u8-parser: 4.7.1
|
||||||
NeteaseCloudMusicApi: 4.6.2
|
NeteaseCloudMusicApi: 4.6.2
|
||||||
pretty-bytes: 6.0.0
|
pretty-bytes: 6.0.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
|
@ -115,6 +117,7 @@ importers:
|
||||||
'@storybook/builder-vite': ^0.1.35
|
'@storybook/builder-vite': ^0.1.35
|
||||||
'@storybook/react': ^6.5.5
|
'@storybook/react': ^6.5.5
|
||||||
'@storybook/testing-library': ^0.0.11
|
'@storybook/testing-library': ^0.0.11
|
||||||
|
'@tanstack/react-virtual': 3.0.0-beta.2
|
||||||
'@testing-library/react': ^13.3.0
|
'@testing-library/react': ^13.3.0
|
||||||
'@types/howler': ^2.2.7
|
'@types/howler': ^2.2.7
|
||||||
'@types/js-cookie': ^3.0.2
|
'@types/js-cookie': ^3.0.2
|
||||||
|
|
@ -138,12 +141,15 @@ importers:
|
||||||
eslint-plugin-react: ^7.30.0
|
eslint-plugin-react: ^7.30.0
|
||||||
eslint-plugin-react-hooks: ^4.5.0
|
eslint-plugin-react-hooks: ^4.5.0
|
||||||
framer-motion: ^6.3.4
|
framer-motion: ^6.3.4
|
||||||
|
hls.js: ^1.1.5
|
||||||
howler: ^2.2.3
|
howler: ^2.2.3
|
||||||
js-cookie: ^3.0.1
|
js-cookie: ^3.0.1
|
||||||
jsdom: ^19.0.0
|
jsdom: ^19.0.0
|
||||||
lodash-es: ^4.17.21
|
lodash-es: ^4.17.21
|
||||||
md5: ^2.3.0
|
md5: ^2.3.0
|
||||||
open-cli: ^7.0.1
|
open-cli: ^7.0.1
|
||||||
|
plyr: ^3.7.2
|
||||||
|
plyr-react: ^5.0.2
|
||||||
postcss: ^8.4.14
|
postcss: ^8.4.14
|
||||||
prettier: '*'
|
prettier: '*'
|
||||||
prettier-plugin-tailwindcss: ^0.1.11
|
prettier-plugin-tailwindcss: ^0.1.11
|
||||||
|
|
@ -169,15 +175,19 @@ importers:
|
||||||
'@emotion/css': 11.9.0
|
'@emotion/css': 11.9.0
|
||||||
'@sentry/react': 6.19.7_react@18.1.0
|
'@sentry/react': 6.19.7_react@18.1.0
|
||||||
'@sentry/tracing': 6.19.7
|
'@sentry/tracing': 6.19.7
|
||||||
|
'@tanstack/react-virtual': 3.0.0-beta.2
|
||||||
axios: 0.27.2
|
axios: 0.27.2
|
||||||
color.js: 1.2.0
|
color.js: 1.2.0
|
||||||
colord: 2.9.2
|
colord: 2.9.2
|
||||||
dayjs: 1.11.2
|
dayjs: 1.11.2
|
||||||
framer-motion: 6.3.10_ef5jwxihqo6n7gxfmzogljlgcm
|
framer-motion: 6.3.10_ef5jwxihqo6n7gxfmzogljlgcm
|
||||||
|
hls.js: 1.1.5
|
||||||
howler: 2.2.3
|
howler: 2.2.3
|
||||||
js-cookie: 3.0.1
|
js-cookie: 3.0.1
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
md5: 2.3.0
|
md5: 2.3.0
|
||||||
|
plyr: 3.7.2
|
||||||
|
plyr-react: 5.0.2_react@18.1.0
|
||||||
qrcode: 1.5.0
|
qrcode: 1.5.0
|
||||||
react: 18.1.0
|
react: 18.1.0
|
||||||
react-dom: 18.1.0_react@18.1.0
|
react-dom: 18.1.0_react@18.1.0
|
||||||
|
|
@ -4527,6 +4537,10 @@ packages:
|
||||||
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@reach/observe-rect/1.2.0:
|
||||||
|
resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rollup/plugin-babel/5.3.1_4kojsos35jimftt7mhjohcqk6y:
|
/@rollup/plugin-babel/5.3.1_4kojsos35jimftt7mhjohcqk6y:
|
||||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
@ -6386,6 +6400,13 @@ packages:
|
||||||
defer-to-connect: 2.0.1
|
defer-to-connect: 2.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@tanstack/react-virtual/3.0.0-beta.2:
|
||||||
|
resolution: {integrity: sha512-pwA9URTHYXX/2PgIISoMcf1P77hxf5oI3L/IDQ19Q1xuAc76o2R2CwHv6vvl5fDhwVj5klOfBxJvuT61Lhy9/w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dependencies:
|
||||||
|
'@reach/observe-rect': 1.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@testing-library/dom/8.13.0:
|
/@testing-library/dom/8.13.0:
|
||||||
resolution: {integrity: sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==}
|
resolution: {integrity: sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
@ -7119,6 +7140,15 @@ packages:
|
||||||
'@unblockneteasemusic/rust-napi-win32-x64-msvc': 0.3.0
|
'@unblockneteasemusic/rust-napi-win32-x64-msvc': 0.3.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@videojs/vhs-utils/3.0.5:
|
||||||
|
resolution: {integrity: sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==}
|
||||||
|
engines: {node: '>=8', npm: '>=5'}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.18.3
|
||||||
|
global: 4.4.0
|
||||||
|
url-toolkit: 2.2.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@vitejs/plugin-react/1.3.2:
|
/@vitejs/plugin-react/1.3.2:
|
||||||
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
|
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
@ -9424,7 +9454,6 @@ packages:
|
||||||
/core-js/3.22.7:
|
/core-js/3.22.7:
|
||||||
resolution: {integrity: sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg==}
|
resolution: {integrity: sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
|
||||||
|
|
||||||
/core-util-is/1.0.2:
|
/core-util-is/1.0.2:
|
||||||
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
||||||
|
|
@ -9698,6 +9727,10 @@ packages:
|
||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/custom-event-polyfill/1.0.7:
|
||||||
|
resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cyclist/1.0.1:
|
/cyclist/1.0.1:
|
||||||
resolution: {integrity: sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==}
|
resolution: {integrity: sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -10109,7 +10142,6 @@ packages:
|
||||||
|
|
||||||
/dom-walk/0.1.2:
|
/dom-walk/0.1.2:
|
||||||
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/domain-browser/1.2.0:
|
/domain-browser/1.2.0:
|
||||||
resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
|
resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
|
||||||
|
|
@ -12259,7 +12291,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
min-document: 2.19.0
|
min-document: 2.19.0
|
||||||
process: 0.11.10
|
process: 0.11.10
|
||||||
dev: true
|
|
||||||
|
|
||||||
/globals/11.12.0:
|
/globals/11.12.0:
|
||||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||||
|
|
@ -12578,6 +12609,10 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.18.3
|
'@babel/runtime': 7.18.3
|
||||||
|
|
||||||
|
/hls.js/1.1.5:
|
||||||
|
resolution: {integrity: sha512-mQX5TSNtJEzGo5HPpvcQgCu+BWoKDQM6YYtg/KbgWkmVAcqOCvSTi0SuqG2ZJLXxIzdnFcKU2z7Mrw/YQWhPOA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/hmac-drbg/1.0.1:
|
/hmac-drbg/1.0.1:
|
||||||
resolution: {integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=}
|
resolution: {integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -13861,6 +13896,10 @@ packages:
|
||||||
json5: 2.2.1
|
json5: 2.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/loadjs/4.2.0:
|
||||||
|
resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/local-pkg/0.4.1:
|
/local-pkg/0.4.1:
|
||||||
resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
|
resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
@ -14001,6 +14040,14 @@ packages:
|
||||||
readable-stream: 3.6.0
|
readable-stream: 3.6.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/m3u8-parser/4.7.1:
|
||||||
|
resolution: {integrity: sha512-pbrQwiMiq+MmI9bl7UjtPT3AK603PV9bogNlr83uC+X9IoxqL5E4k7kU7fMQ0dpRgxgeSMygqUa0IMLQNXLBNA==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.18.3
|
||||||
|
'@videojs/vhs-utils': 3.0.5
|
||||||
|
global: 4.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/magic-string/0.25.9:
|
/magic-string/0.25.9:
|
||||||
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -14357,10 +14404,9 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
/min-document/2.19.0:
|
/min-document/2.19.0:
|
||||||
resolution: {integrity: sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=}
|
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-walk: 0.1.2
|
dom-walk: 0.1.2
|
||||||
dev: true
|
|
||||||
|
|
||||||
/min-indent/1.0.1:
|
/min-indent/1.0.1:
|
||||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||||
|
|
@ -15472,6 +15518,30 @@ packages:
|
||||||
xmlbuilder: 9.0.7
|
xmlbuilder: 9.0.7
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/plyr-react/5.0.2_react@18.1.0:
|
||||||
|
resolution: {integrity: sha512-CksykyesFtmPoslasOVIplYZkduJ2OQ/q3QNUdktjy8Ds4Rhxw9u57jKBjSLdpyhENp/Yu+lDC7lOHc1o9iPUQ==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
plyr: 3.7.2
|
||||||
|
react: 18.1.0
|
||||||
|
react-aptor: 2.0.0-alpha.1_react@18.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/plyr/3.7.2:
|
||||||
|
resolution: {integrity: sha512-I0ZC/OI4oJ0iWG9s2rrnO0YFO6aLyrPiQBq9kum0FqITYljwTPBbYL3TZZu8UJQJUq7tUWN18Q7ACwNCkGKABQ==}
|
||||||
|
dependencies:
|
||||||
|
core-js: 3.22.7
|
||||||
|
custom-event-polyfill: 1.0.7
|
||||||
|
loadjs: 4.2.0
|
||||||
|
rangetouch: 2.0.1
|
||||||
|
url-polyfill: 1.1.12
|
||||||
|
dev: false
|
||||||
|
|
||||||
/pngjs/5.0.0:
|
/pngjs/5.0.0:
|
||||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
@ -15793,9 +15863,8 @@ packages:
|
||||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||||
|
|
||||||
/process/0.11.10:
|
/process/0.11.10:
|
||||||
resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=}
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/progress/2.0.3:
|
/progress/2.0.3:
|
||||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||||
|
|
@ -16020,6 +16089,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
/rangetouch/2.0.1:
|
||||||
|
resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/raw-body/2.5.1:
|
/raw-body/2.5.1:
|
||||||
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
@ -16049,6 +16122,18 @@ packages:
|
||||||
minimist: 1.2.6
|
minimist: 1.2.6
|
||||||
strip-json-comments: 2.0.1
|
strip-json-comments: 2.0.1
|
||||||
|
|
||||||
|
/react-aptor/2.0.0-alpha.1_react@18.1.0:
|
||||||
|
resolution: {integrity: sha512-FbvxQKsZMUZcLr2WdrQEmxH0kifsN4N+v6YdL1g3At03zouJCEcPXv+o+bhP3Ci3ya4QPvNHK/bpbrCzuKWOMw==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-docgen-typescript/2.2.2_typescript@4.7.3:
|
/react-docgen-typescript/2.2.2_typescript@4.7.3:
|
||||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -18653,6 +18738,14 @@ packages:
|
||||||
prepend-http: 2.0.0
|
prepend-http: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/url-polyfill/1.1.12:
|
||||||
|
resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/url-toolkit/2.2.5:
|
||||||
|
resolution: {integrity: sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/url/0.11.0:
|
/url/0.11.0:
|
||||||
resolution: {integrity: sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=}
|
resolution: {integrity: sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue