@samitouri / QOSami-HFS / commits / 82350173

plugins: frontend event 'showPlay'

Massimo Melina committed Jun 21, 2024 at 12:17 UTC 823501739ed99a7e1302b673876116cd7f087d48
4 files changed +24 -11
dev-plugins.md
+5
@@ -354,6 +354,9 @@ This is a list of available frontend-events, with respective object parameter an
354 - you receive an entry of the list, and optionally produce React Component for visualization.
355 - parameter `{ entry: Entry }` (refer above for Entry object)
356 - output `ReactComponent`
357 +- `showPlay`
358 + - emitted on each file played inside file-show. Use setCover if you want to customize the background picture.
359 + - parameter `{ entry: Entry, setCover(uri: string), meta: { title, album, artist, year } }`
360 - `menuZip`
361 - parameter `{ def: ReactNode }`
362 - output `Html`
@@ -610,6 +613,8 @@ If you want to override a text regardless of the language, use the special langu
613
614 ## API version history
615
616 +- 8.9 (v0.54.0)
617 + - frontend event: showPlay
618 - 8.891 (v0.53.0)
619 - api.openDb
620 - frontend event: menuZip
frontend/src/show.ts
+14 -6
@@ -140,7 +140,7 @@ export function fileShow(entry: DirEntry, { startPlaying=false } = {}) {
140 h('div', {}, cur.name),
141 t`Loading failed`
142 ) : h('div', { className: 'showing-container', ref: containerRef },
143 - h('div', { className: 'cover ' + (cover ? '' : 'none'), style: { backgroundImage: `url("${pathEncode(cover)}")`, } }),
143 + h('div', { className: 'cover ' + (cover ? '' : 'none'), style: { backgroundImage: `url("${cover}")`, } }),
144 h(getShowType(cur) || Fragment, {
145 src: cur.uri,
146 className: 'showing',
@@ -152,19 +152,27 @@ export function fileShow(entry: DirEntry, { startPlaying=false } = {}) {
152 async onPlay() {
153 const folder = dirname(cur.n)
154 const covers = !isAudio ? [] : state.list.filter(x => folder === dirname(x.n) // same folder
155 - && x.name.match(/(?:folder|cover|albumart.*)\.jpe?g$/i))
156 - setCover(_.maxBy(covers, 's')?.n || '')
155 + && x.name.match(/(?:folder|cover|front|albumart.*)\.jpe?g$/i))
156 + setCover(pathEncode(_.maxBy(covers, 's')?.n || ''))
157 const meta = {
158 title: cur.name,
159 album: decodeURIComponent(basename(dirname(cur.uri))),
160 artwork: covers.map(x => ({ src: x.n }))
161 }
162 - if (window.MediaMetadata)
163 - navigator.mediaSession.metadata = new MediaMetadata(meta)
162 + const m = window.MediaMetadata && (navigator.mediaSession.metadata = new MediaMetadata(meta))
163 if (cur.ext === 'mp3') {
164 setTags(Object.assign(meta, await getId3Tags(location.pathname + cur.n).catch(() => {})))
166 - Object.assign(navigator.mediaSession?.metadata || {}, meta)
165 + if (m) Object.assign(m, meta)
166 }
167 + hfsEvent('showPlay', {
168 + entry: cur,
169 + meta,
170 + setCover(src: any) {
171 + if (typeof src !== 'string') return
172 + setCover(src)
173 + if (m) navigator.mediaSession.metadata = new MediaMetadata(Object.assign(meta, { artwork: [{ src }] }))
174 + }
175 + })
176 }
177 }),
178 tags && h('div', { className: 'meta-tags' },
shared/api.ts
+3 -3
@@ -55,7 +55,7 @@ export function apiCall<T=any>(cmd: string, params?: Dict, options: ApiCallOptio
55 await options.onResponse?.(res, result)
56 if (!res.ok)
57 throw new ApiError(res.status, data === undefined ? body : `Failed API ${cmd}: ${res.statusText}`, data)
58 - return result as Awaited<T extends (...args: any[]) => infer R ? R : T>
58 + return result as Awaited<T extends (...args: any[]) => infer R ? Awaited<R> : T>
59 }, err => {
60 stop?.()
61 if (err?.message?.includes('fetch')) {
@@ -81,7 +81,7 @@ export class ApiError extends Error {
81
82 export type UseApi<T=unknown> = ReturnType<typeof useApi<T>>
83 export function useApi<T=any>(cmd: string | Falsy, params?: object, options: ApiCallOptions={}) {
84 - const [data, setData] = useStateMounted<T | undefined>(undefined)
84 + const [data, setData] = useStateMounted<Awaited<ReturnType<typeof apiCall<T>>> | undefined>(undefined)
85 const [error, setError] = useStateMounted<Error | undefined>(undefined)
86 const [forcer, setForcer] = useStateMounted(0)
87 const loadingRef = useRef<ReturnType<typeof apiCall>>()
@@ -95,7 +95,7 @@ export function useApi<T=any>(cmd: string | Falsy, params?: object, options: Api
95 let req: undefined | ReturnType<typeof apiCall>
96 const wholePromise = wait(0) // postpone a bit, so that if it is aborted immediately, it is never really fired (happens mostly in dev mode)
97 .then(() => !cmd || aborted ? undefined : req = apiCall<T>(cmd, params, options))
98 - .then(res => aborted || setData(dataRef.current = res), err => {
98 + .then(res => aborted || setData(dataRef.current = res as any), err => {
99 if (aborted) return
100 setError(err)
101 setData(dataRef.current = undefined)
src/const.ts
+2 -2
@@ -7,7 +7,7 @@ import { mkdirSync } from 'fs'
7 import { basename, dirname, join } from 'path'
8 export * from './cross-const'
9
10 -export const API_VERSION = 8.891
10 +export const API_VERSION = 8.9
11 export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
12 export const HFS_REPO = 'rejetto/hfs'
13
@@ -51,5 +51,5 @@ console.log('cwd', process.cwd())
51 if (APP_PATH !== process.cwd())
52 console.log('app', APP_PATH)
53 console.log('node', process.version)
54 -console.log('platform', process.platform, process.arch)
54 +console.log('platform', process.platform)
55 console.log('pid', process.pid)