mirror of
https://github.com/qier222/YesPlayMusic.git
synced 2024-11-25 09:41:49 +08:00
feat: updates
This commit is contained in:
parent
196a974a64
commit
8f4c3d8e5b
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
|
||||
}
|
||||
case APIs.Track: {
|
||||
if (!data.songs) return
|
||||
const tracks = (data as FetchTracksResponse).songs.map(t => ({
|
||||
const res = data as FetchTracksResponse
|
||||
if (!res.songs) return
|
||||
const tracks = res.songs.map(t => ({
|
||||
id: t.id,
|
||||
json: JSON.stringify(t),
|
||||
updatedAt: Date.now(),
|
||||
|
@ -106,6 +107,16 @@ class Cache {
|
|||
db.upsert(Tables.CoverColor, {
|
||||
id: data.id,
|
||||
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
|
||||
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,
|
||||
type: type as TablesStructures[Tables.Audio]['type'],
|
||||
source,
|
||||
updatedAt: Date.now(),
|
||||
queriedAt: Date.now(),
|
||||
})
|
||||
|
||||
log.info(`[cache] cacheAudio ${id}-${br}.${type}`)
|
||||
|
|
|
@ -18,6 +18,7 @@ export const enum Tables {
|
|||
AccountData = 'AccountData',
|
||||
CoverColor = 'CoverColor',
|
||||
AppData = 'AppData',
|
||||
VideoCover = 'VideoCover',
|
||||
}
|
||||
interface CommonTableStructure {
|
||||
id: number
|
||||
|
@ -50,16 +51,22 @@ export interface TablesStructures {
|
|||
| 'qq'
|
||||
| 'bilibili'
|
||||
| 'joox'
|
||||
updatedAt: number
|
||||
queriedAt: number
|
||||
}
|
||||
[Tables.CoverColor]: {
|
||||
id: number
|
||||
color: string
|
||||
queriedAt: number
|
||||
}
|
||||
[Tables.AppData]: {
|
||||
id: 'appVersion' | 'skippedVersion'
|
||||
value: string
|
||||
}
|
||||
[Tables.VideoCover]: {
|
||||
id: number
|
||||
url: string
|
||||
queriedAt: number
|
||||
}
|
||||
}
|
||||
|
||||
type TableNames = keyof TablesStructures
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Thumbar } from './windowsTaskbar'
|
|||
import fastFolderSize from 'fast-folder-size'
|
||||
import path from 'path'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { getCoverVideo } from './appleMusic'
|
||||
|
||||
const on = <T extends keyof IpcChannelsParams>(
|
||||
channel: T,
|
||||
|
@ -20,6 +21,16 @@ const on = <T extends keyof IpcChannelsParams>(
|
|||
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(
|
||||
win: BrowserWindow | 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环境)
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,7 @@ contextBridge.exposeInMainWorld('log', log)
|
|||
|
||||
contextBridge.exposeInMainWorld('ipcRenderer', {
|
||||
sendSync: ipcRenderer.sendSync,
|
||||
invoke: ipcRenderer.invoke,
|
||||
send: ipcRenderer.send,
|
||||
on: (
|
||||
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 "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 "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 "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 "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 "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",
|
||||
"build": "node scripts/build.main.mjs",
|
||||
"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 ./",
|
||||
"format": "prettier --write './**/*.{ts,js,tsx,jsx}'"
|
||||
},
|
||||
|
@ -29,6 +29,7 @@
|
|||
"electron-store": "^8.0.1",
|
||||
"express": "^4.18.1",
|
||||
"fast-folder-size": "^1.7.0",
|
||||
"m3u8-parser": "^4.7.1",
|
||||
"pretty-bytes": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -21,7 +21,6 @@ export const enum APIs {
|
|||
Album = 'album',
|
||||
Artist = 'artists',
|
||||
ArtistAlbum = 'artist/album',
|
||||
CoverColor = 'cover_color',
|
||||
Likelist = 'likelist',
|
||||
Lyric = 'lyric',
|
||||
Personalized = 'personalized',
|
||||
|
@ -33,13 +32,16 @@ export const enum APIs {
|
|||
UserAlbums = 'album/sublist',
|
||||
UserArtists = 'artist/sublist',
|
||||
UserPlaylist = 'user/playlist',
|
||||
|
||||
// not netease api
|
||||
CoverColor = 'cover_color',
|
||||
VideoCover = 'video_cover',
|
||||
}
|
||||
|
||||
export interface APIsParams {
|
||||
[APIs.Album]: { id: number }
|
||||
[APIs.Artist]: { id: number }
|
||||
[APIs.ArtistAlbum]: { id: number }
|
||||
[APIs.CoverColor]: { id: number }
|
||||
[APIs.Likelist]: void
|
||||
[APIs.Lyric]: { id: number }
|
||||
[APIs.Personalized]: void
|
||||
|
@ -51,13 +53,14 @@ export interface APIsParams {
|
|||
[APIs.UserAlbums]: void
|
||||
[APIs.UserArtists]: void
|
||||
[APIs.UserPlaylist]: void
|
||||
[APIs.CoverColor]: { id: number }
|
||||
[APIs.VideoCover]: { id: number }
|
||||
}
|
||||
|
||||
export interface APIsResponse {
|
||||
[APIs.Album]: FetchAlbumResponse
|
||||
[APIs.Artist]: FetchArtistResponse
|
||||
[APIs.ArtistAlbum]: FetchArtistAlbumsResponse
|
||||
[APIs.CoverColor]: string | undefined
|
||||
[APIs.Likelist]: FetchUserLikedTracksIDsResponse
|
||||
[APIs.Lyric]: FetchLyricResponse
|
||||
[APIs.Personalized]: FetchRecommendedPlaylistsResponse
|
||||
|
@ -69,4 +72,6 @@ export interface APIsResponse {
|
|||
[APIs.UserAlbums]: FetchUserAlbumsResponse
|
||||
[APIs.UserArtists]: FetchUserArtistsResponse
|
||||
[APIs.UserPlaylist]: FetchUserPlaylistsResponse
|
||||
[APIs.CoverColor]: string | undefined
|
||||
[APIs.VideoCover]: string | undefined
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ export const enum IpcChannels {
|
|||
SyncSettings = 'SyncSettings',
|
||||
GetAudioCacheSize = 'GetAudioCacheSize',
|
||||
ResetWindowSize = 'ResetWindowSize',
|
||||
GetVideoCover = 'GetVideoCover',
|
||||
SetVideoCover = 'SetVideoCover',
|
||||
}
|
||||
|
||||
// ipcMain.on params
|
||||
|
@ -58,6 +60,8 @@ export interface IpcChannelsParams {
|
|||
[IpcChannels.SyncSettings]: Store['settings']
|
||||
[IpcChannels.GetAudioCacheSize]: void
|
||||
[IpcChannels.ResetWindowSize]: void
|
||||
[IpcChannels.GetVideoCover]: { id: number }
|
||||
[IpcChannels.SetVideoCover]: { id: number; url: string }
|
||||
}
|
||||
|
||||
// ipcRenderer.on params
|
||||
|
@ -79,4 +83,6 @@ export interface IpcChannelsReturns {
|
|||
[IpcChannels.Like]: void
|
||||
[IpcChannels.Repeat]: RepeatMode
|
||||
[IpcChannels.GetAudioCacheSize]: void
|
||||
[IpcChannels.GetVideoCover]: string | undefined
|
||||
[IpcChannels.SetVideoCover]: void
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ const Image = ({
|
|||
className?: string
|
||||
alt: string
|
||||
lazyLoad?: boolean
|
||||
placeholder?: 'artist' | 'album' | 'playlist' | 'podcast' | 'blank' | null
|
||||
placeholder?: 'artist' | 'album' | 'playlist' | 'podcast' | 'blank' | false
|
||||
onClick?: (e: React.MouseEvent<HTMLImageElement>) => void
|
||||
onMouseOver?: (e: React.MouseEvent<HTMLImageElement>) => void
|
||||
animation?: boolean
|
||||
|
@ -48,6 +48,7 @@ const Image = ({
|
|||
? {
|
||||
animate,
|
||||
initial: { opacity: 0 },
|
||||
exit: { opacity: 0 },
|
||||
transition,
|
||||
}
|
||||
: {}
|
||||
|
@ -60,22 +61,30 @@ const Image = ({
|
|||
: {}
|
||||
|
||||
return (
|
||||
<div className={cx('relative overflow-hidden', className)}>
|
||||
<div
|
||||
className={cx(
|
||||
'overflow-hidden',
|
||||
className,
|
||||
className?.includes('absolute') === false && 'relative'
|
||||
)}
|
||||
>
|
||||
{/* Image */}
|
||||
<motion.img
|
||||
alt={alt}
|
||||
className='absolute inset-0 h-full w-full'
|
||||
src={src}
|
||||
srcSet={srcSet}
|
||||
sizes={sizes}
|
||||
decoding='async'
|
||||
loading={lazyLoad ? 'lazy' : undefined}
|
||||
onError={onError}
|
||||
onLoad={onLoad}
|
||||
onClick={onClick}
|
||||
onMouseOver={onMouseOver}
|
||||
{...motionProps}
|
||||
/>
|
||||
<AnimatePresence>
|
||||
<motion.img
|
||||
alt={alt}
|
||||
className='absolute inset-0 h-full w-full'
|
||||
src={src}
|
||||
srcSet={srcSet}
|
||||
sizes={sizes}
|
||||
decoding='async'
|
||||
loading={lazyLoad ? 'lazy' : undefined}
|
||||
onError={onError}
|
||||
onLoad={onLoad}
|
||||
onClick={onClick}
|
||||
onMouseOver={onMouseOver}
|
||||
{...motionProps}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Placeholder / Error fallback */}
|
||||
<AnimatePresence>
|
||||
|
|
|
@ -4,6 +4,7 @@ import Router from './Router'
|
|||
const Main = () => {
|
||||
return (
|
||||
<main
|
||||
id='main'
|
||||
className={cx(
|
||||
'no-scrollbar overflow-y-auto pb-16 pr-6 pl-10',
|
||||
css`
|
||||
|
|
|
@ -8,6 +8,10 @@ import { AnimatePresence, motion } from 'framer-motion'
|
|||
import Image from './Image'
|
||||
import Wave from './Wave'
|
||||
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 = () => {
|
||||
return (
|
||||
|
@ -46,13 +50,13 @@ const Track = ({
|
|||
return (
|
||||
<motion.div
|
||||
className='flex items-center justify-between'
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ x: '100%', opacity: 0 }}
|
||||
transition={{
|
||||
duration: 0.24,
|
||||
}}
|
||||
layout
|
||||
// initial={{ opacity: 0 }}
|
||||
// animate={{ opacity: 1 }}
|
||||
// exit={{ x: '100%', opacity: 0 }}
|
||||
// transition={{
|
||||
// duration: 0.24,
|
||||
// }}
|
||||
// layout
|
||||
onClick={e => {
|
||||
if (e.detail === 2 && track?.id) player.playTrack(track.id)
|
||||
}}
|
||||
|
@ -62,6 +66,8 @@ const Track = ({
|
|||
alt='Cover'
|
||||
className='mr-4 aspect-square h-14 w-14 flex-shrink-0 rounded-12'
|
||||
src={resizeImage(track?.al?.picUrl || '', 'sm')}
|
||||
animation={false}
|
||||
placeholder={false}
|
||||
/>
|
||||
|
||||
{/* 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 { 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 (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<div
|
||||
ref={parentRef}
|
||||
style={{
|
||||
height: `${listHeight}px`,
|
||||
}}
|
||||
className={cx(
|
||||
'no-scrollbar relative z-10 overflow-scroll',
|
||||
'no-scrollbar relative z-10 w-full overflow-auto',
|
||||
className,
|
||||
css`
|
||||
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'>
|
||||
<AnimatePresence>
|
||||
{tracks?.songs?.map((track, index) => (
|
||||
<div
|
||||
className='relative w-full'
|
||||
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
|
||||
key={track.id}
|
||||
track={track}
|
||||
index={index}
|
||||
track={tracks?.[row.index]}
|
||||
index={row.index}
|
||||
playingTrackIndex={trackIndex}
|
||||
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
|
||||
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,10 +1,134 @@
|
|||
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 Icon from '@/web/components/Icon'
|
||||
import dayjs from 'dayjs'
|
||||
import { useMemo } from 'react'
|
||||
import Image from './Image'
|
||||
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'
|
||||
|
||||
injectGlobal`
|
||||
.plyr__video-wrapper,
|
||||
.plyr--video {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
`
|
||||
|
||||
const VideoCover = ({ source }: { source?: string }) => {
|
||||
const ref = useRef<APITypes>(null)
|
||||
useEffect(() => {
|
||||
const loadVideo = async () => {
|
||||
if (!source || !Hls.isSupported()) return
|
||||
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 { data: videoCover } = useVideoCover({
|
||||
id: album?.id,
|
||||
name: album?.name,
|
||||
artist: album?.artist.name,
|
||||
})
|
||||
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='relative z-10 aspect-square w-full overflow-auto rounded-24 '>
|
||||
<Image
|
||||
className='absolute inset-0 h-full w-full'
|
||||
src={resizeImage(cover, 'lg')}
|
||||
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 */}
|
||||
{!isMobile && (
|
||||
<img
|
||||
className={cx(
|
||||
'absolute z-0 object-cover opacity-70',
|
||||
css`
|
||||
top: -400px;
|
||||
left: -370px;
|
||||
width: 1572px;
|
||||
height: 528px;
|
||||
filter: blur(256px) saturate(1.2);
|
||||
`
|
||||
)}
|
||||
src={resizeImage(cover, 'sm')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
Cover.displayName = 'Cover'
|
||||
|
||||
const TrackListHeader = ({
|
||||
album,
|
||||
|
@ -15,18 +139,16 @@ const TrackListHeader = ({
|
|||
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])
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
const cover = album?.picUrl || playlist?.coverImgUrl || ''
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'mx-2.5 rounded-48 p-8 dark:bg-white/10',
|
||||
'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`
|
||||
|
@ -34,29 +156,7 @@ const TrackListHeader = ({
|
|||
`
|
||||
)}
|
||||
>
|
||||
{/* Cover */}
|
||||
<Image
|
||||
className='z-10 aspect-square w-full rounded-24'
|
||||
src={resizeImage(cover, 'lg')}
|
||||
alt='Cover'
|
||||
/>
|
||||
|
||||
{/* Blur bg */}
|
||||
{!isMobile && (
|
||||
<img
|
||||
className={cx(
|
||||
'absolute z-0 object-cover opacity-70',
|
||||
css`
|
||||
top: -400px;
|
||||
left: -370px;
|
||||
width: 1572px;
|
||||
height: 528px;
|
||||
filter: blur(256px) saturate(1.2);
|
||||
`
|
||||
)}
|
||||
src={resizeImage(cover, 'sm')}
|
||||
/>
|
||||
)}
|
||||
<Cover {...{ album, playlist }} />
|
||||
|
||||
<div className='flex flex-col justify-between'>
|
||||
<div>
|
||||
|
|
4
packages/web/global.d.ts
vendored
4
packages/web/global.d.ts
vendored
|
@ -10,6 +10,10 @@ declare global {
|
|||
channel: T,
|
||||
params?: IpcChannelsParams[T]
|
||||
) => IpcChannelsReturns[T]
|
||||
invoke: <T extends keyof IpcChannelsParams>(
|
||||
channel: T,
|
||||
params?: IpcChannelsParams[T]
|
||||
) => Promise<IpcChannelsReturns[T]>
|
||||
send: <T extends keyof IpcChannelsParams>(
|
||||
channel: 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" />
|
||||
<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 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-status-bar-style" content="black-translucent" />
|
||||
<title>YesPlayMusic</title>
|
||||
|
|
|
@ -14,6 +14,7 @@ import ReactGA from 'react-ga4'
|
|||
import { ipcRenderer } from './ipcRenderer'
|
||||
import { QueryClientProvider } from 'react-query'
|
||||
import reactQueryClient from '@/web/utils/reactQueryClient'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
ReactGA.initialize('G-KMJJCFZDKF')
|
||||
|
||||
|
@ -34,12 +35,23 @@ ipcRenderer()
|
|||
const container = document.getElementById('root') as HTMLElement
|
||||
const root = ReactDOMClient.createRoot(container)
|
||||
|
||||
root.render(
|
||||
// root.render(
|
||||
// <StrictMode>
|
||||
// <BrowserRouter>
|
||||
// <QueryClientProvider client={reactQueryClient}>
|
||||
// <App />
|
||||
// </QueryClientProvider>
|
||||
// </BrowserRouter>
|
||||
// </StrictMode>
|
||||
// )
|
||||
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={reactQueryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
</StrictMode>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
|
|
@ -25,15 +25,19 @@
|
|||
"@emotion/css": "^11.9.0",
|
||||
"@sentry/react": "^6.19.7",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@tanstack/react-virtual": "3.0.0-beta.2",
|
||||
"axios": "^0.27.2",
|
||||
"color.js": "^1.2.0",
|
||||
"colord": "^2.9.2",
|
||||
"dayjs": "^1.11.1",
|
||||
"framer-motion": "^6.3.4",
|
||||
"hls.js": "^1.1.5",
|
||||
"howler": "^2.2.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"plyr": "^3.7.2",
|
||||
"plyr-react": "^5.0.2",
|
||||
"qrcode": "^1.5.0",
|
||||
"react": "^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 CoverRow from '@/web/components/New/CoverRow'
|
||||
import { useMemo } from 'react'
|
||||
import 'plyr-react/plyr.css'
|
||||
|
||||
const MoreByArtist = ({ album }: { album?: Album }) => {
|
||||
const { data: albums } = useArtistAlbums({
|
||||
|
|
|
@ -39,7 +39,10 @@ const Albums = () => {
|
|||
const Playlists = () => {
|
||||
const { data: playlists } = useUserPlaylists()
|
||||
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 isSafari = /Safari/.test(navigator.userAgent)
|
||||
export const isSafari = /^((?!chrome|android).)*safari/i.test(
|
||||
navigator.userAgent
|
||||
)
|
||||
export const isPWA =
|
||||
(navigator as any).standalone ||
|
||||
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 breakpoint = {
|
||||
sm: '@media (min-width: 640px)',
|
||||
md: '@media (min-width: 768px)',
|
||||
|
@ -6,3 +9,6 @@ export const breakpoint = {
|
|||
xl: '@media (min-width: 1280px)',
|
||||
'2xl': '@media (min-width: 1536px)',
|
||||
}
|
||||
|
||||
export const topbarHeight = 132 // 桌面端顶栏高度 (px)
|
||||
export const playerWidth = 318 // 桌面端播放器宽度 (px)
|
||||
|
|
|
@ -91,6 +91,11 @@ export default defineConfig({
|
|||
changeOrigin: true,
|
||||
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/': {
|
||||
target: `http://127.0.0.1:${
|
||||
process.env.ELECTRON_DEV_NETEASE_API_PORT || 3000
|
||||
|
|
107
pnpm-lock.yaml
107
pnpm-lock.yaml
|
@ -48,6 +48,7 @@ importers:
|
|||
express: ^4.18.1
|
||||
express-fileupload: ^1.4.0
|
||||
fast-folder-size: ^1.7.0
|
||||
m3u8-parser: ^4.7.1
|
||||
minimist: ^1.2.6
|
||||
music-metadata: ^7.12.3
|
||||
NeteaseCloudMusicApi: ^4.6.2
|
||||
|
@ -70,6 +71,7 @@ importers:
|
|||
electron-store: 8.0.1
|
||||
express: 4.18.1
|
||||
fast-folder-size: 1.7.0
|
||||
m3u8-parser: 4.7.1
|
||||
NeteaseCloudMusicApi: 4.6.2
|
||||
pretty-bytes: 6.0.0
|
||||
devDependencies:
|
||||
|
@ -115,6 +117,7 @@ importers:
|
|||
'@storybook/builder-vite': ^0.1.35
|
||||
'@storybook/react': ^6.5.5
|
||||
'@storybook/testing-library': ^0.0.11
|
||||
'@tanstack/react-virtual': 3.0.0-beta.2
|
||||
'@testing-library/react': ^13.3.0
|
||||
'@types/howler': ^2.2.7
|
||||
'@types/js-cookie': ^3.0.2
|
||||
|
@ -138,12 +141,15 @@ importers:
|
|||
eslint-plugin-react: ^7.30.0
|
||||
eslint-plugin-react-hooks: ^4.5.0
|
||||
framer-motion: ^6.3.4
|
||||
hls.js: ^1.1.5
|
||||
howler: ^2.2.3
|
||||
js-cookie: ^3.0.1
|
||||
jsdom: ^19.0.0
|
||||
lodash-es: ^4.17.21
|
||||
md5: ^2.3.0
|
||||
open-cli: ^7.0.1
|
||||
plyr: ^3.7.2
|
||||
plyr-react: ^5.0.2
|
||||
postcss: ^8.4.14
|
||||
prettier: '*'
|
||||
prettier-plugin-tailwindcss: ^0.1.11
|
||||
|
@ -169,15 +175,19 @@ importers:
|
|||
'@emotion/css': 11.9.0
|
||||
'@sentry/react': 6.19.7_react@18.1.0
|
||||
'@sentry/tracing': 6.19.7
|
||||
'@tanstack/react-virtual': 3.0.0-beta.2
|
||||
axios: 0.27.2
|
||||
color.js: 1.2.0
|
||||
colord: 2.9.2
|
||||
dayjs: 1.11.2
|
||||
framer-motion: 6.3.10_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
hls.js: 1.1.5
|
||||
howler: 2.2.3
|
||||
js-cookie: 3.0.1
|
||||
lodash-es: 4.17.21
|
||||
md5: 2.3.0
|
||||
plyr: 3.7.2
|
||||
plyr-react: 5.0.2_react@18.1.0
|
||||
qrcode: 1.5.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==}
|
||||
dev: true
|
||||
|
||||
/@reach/observe-rect/1.2.0:
|
||||
resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==}
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-babel/5.3.1_4kojsos35jimftt7mhjohcqk6y:
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
@ -6386,6 +6400,13 @@ packages:
|
|||
defer-to-connect: 2.0.1
|
||||
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:
|
||||
resolution: {integrity: sha512-9VHgfIatKNXQNaZTtLnalIy0jNZzY35a4S3oi08YAt9Hv1VsfZ/DfA45lM8D/UhtHBGJ4/lGwp0PZkVndRkoOQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -7119,6 +7140,15 @@ packages:
|
|||
'@unblockneteasemusic/rust-napi-win32-x64-msvc': 0.3.0
|
||||
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:
|
||||
resolution: {integrity: sha512-aurBNmMo0kz1O4qRoY+FM4epSA39y3ShWGuqfLRA/3z0oEJAdtoSfgA3aO98/PCCHAqMaduLxIxErWrVKIFzXA==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
@ -9424,7 +9454,6 @@ packages:
|
|||
/core-js/3.22.7:
|
||||
resolution: {integrity: sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg==}
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/core-util-is/1.0.2:
|
||||
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
|
||||
|
@ -9698,6 +9727,10 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/custom-event-polyfill/1.0.7:
|
||||
resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
|
||||
dev: false
|
||||
|
||||
/cyclist/1.0.1:
|
||||
resolution: {integrity: sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==}
|
||||
dev: true
|
||||
|
@ -10109,7 +10142,6 @@ packages:
|
|||
|
||||
/dom-walk/0.1.2:
|
||||
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
|
||||
dev: true
|
||||
|
||||
/domain-browser/1.2.0:
|
||||
resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==}
|
||||
|
@ -12259,7 +12291,6 @@ packages:
|
|||
dependencies:
|
||||
min-document: 2.19.0
|
||||
process: 0.11.10
|
||||
dev: true
|
||||
|
||||
/globals/11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
|
@ -12578,6 +12609,10 @@ packages:
|
|||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
|
||||
/hls.js/1.1.5:
|
||||
resolution: {integrity: sha512-mQX5TSNtJEzGo5HPpvcQgCu+BWoKDQM6YYtg/KbgWkmVAcqOCvSTi0SuqG2ZJLXxIzdnFcKU2z7Mrw/YQWhPOA==}
|
||||
dev: false
|
||||
|
||||
/hmac-drbg/1.0.1:
|
||||
resolution: {integrity: sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=}
|
||||
dependencies:
|
||||
|
@ -13861,6 +13896,10 @@ packages:
|
|||
json5: 2.2.1
|
||||
dev: true
|
||||
|
||||
/loadjs/4.2.0:
|
||||
resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==}
|
||||
dev: false
|
||||
|
||||
/local-pkg/0.4.1:
|
||||
resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -14001,6 +14040,14 @@ packages:
|
|||
readable-stream: 3.6.0
|
||||
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:
|
||||
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
|
||||
dependencies:
|
||||
|
@ -14357,10 +14404,9 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
|
||||
/min-document/2.19.0:
|
||||
resolution: {integrity: sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=}
|
||||
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
|
||||
dependencies:
|
||||
dom-walk: 0.1.2
|
||||
dev: true
|
||||
|
||||
/min-indent/1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
|
@ -15472,6 +15518,30 @@ packages:
|
|||
xmlbuilder: 9.0.7
|
||||
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:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
@ -15793,9 +15863,8 @@ packages:
|
|||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
/process/0.11.10:
|
||||
resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=}
|
||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: true
|
||||
|
||||
/progress/2.0.3:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
|
@ -16020,6 +16089,10 @@ packages:
|
|||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
/rangetouch/2.0.1:
|
||||
resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==}
|
||||
dev: false
|
||||
|
||||
/raw-body/2.5.1:
|
||||
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -16049,6 +16122,18 @@ packages:
|
|||
minimist: 1.2.6
|
||||
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:
|
||||
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
|
||||
peerDependencies:
|
||||
|
@ -18653,6 +18738,14 @@ packages:
|
|||
prepend-http: 2.0.0
|
||||
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:
|
||||
resolution: {integrity: sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in New Issue
Block a user