muzical-ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +30 -0
- package/CLAUDE.md +1 -0
- package/LICENSE.md +21 -0
- package/README.md +36 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +67 -0
- package/app/layout.tsx +49 -0
- package/app/musicbrainz/page.tsx +6 -0
- package/app/page.tsx +12 -0
- package/app/settings/display/page.tsx +11 -0
- package/app/settings/layout.tsx +19 -0
- package/app/settings/library/page.tsx +11 -0
- package/app/settings/page.tsx +5 -0
- package/app/settings/playback/page.tsx +11 -0
- package/app/settings/youtube/page.tsx +11 -0
- package/bin/stt-ui.js +25 -0
- package/components/AlbumCoverThumb.tsx +82 -0
- package/components/BrowsePanel.tsx +64 -0
- package/components/DisplaySettingsPanel.tsx +30 -0
- package/components/FavoriteStarButton.tsx +41 -0
- package/components/LibraryBrowser.tsx +1180 -0
- package/components/LibraryProvider.tsx +1023 -0
- package/components/LibraryScanNotification.tsx +62 -0
- package/components/LibraryScanOptionsSection.tsx +123 -0
- package/components/LibrarySettingsPanel.tsx +116 -0
- package/components/LibraryStatistics.tsx +54 -0
- package/components/MusicBrainzBrowser.tsx +395 -0
- package/components/MusicBrainzTrackRow.tsx +52 -0
- package/components/MusicPlayer.tsx +1531 -0
- package/components/PanelResizeHandle.tsx +65 -0
- package/components/PlaybackSettingsPanel.tsx +32 -0
- package/components/QueueLoadingSpinner.tsx +19 -0
- package/components/SettingsNav.tsx +37 -0
- package/components/SettingsOverview.tsx +34 -0
- package/components/SettingsShell.tsx +47 -0
- package/components/SettingsSwitchRow.tsx +38 -0
- package/components/ThemeProvider.tsx +75 -0
- package/components/ThemeToggle.tsx +38 -0
- package/components/YouTubeSettingsPanel.tsx +79 -0
- package/components/YouTubeStreamNotification.tsx +30 -0
- package/components/format-library-root-added.ts +13 -0
- package/components/settings-nav-items.ts +40 -0
- package/eslint.config.mjs +18 -0
- package/lib/format-duration.ts +9 -0
- package/lib/format-total-library-duration.ts +14 -0
- package/lib/library/audio-filename.ts +31 -0
- package/lib/library/collect-tracks-for-meta.ts +91 -0
- package/lib/library/compute-library-stats.ts +37 -0
- package/lib/library/constants.ts +27 -0
- package/lib/library/cover-bytes-cache.ts +59 -0
- package/lib/library/default-library-scan-preferences.ts +13 -0
- package/lib/library/extract-cover-bytes-from-audio-file.ts +41 -0
- package/lib/library/extract-cover-object-url-from-audio-file.ts +31 -0
- package/lib/library/favorite-keys.ts +14 -0
- package/lib/library/format-fs-access-error.ts +29 -0
- package/lib/library/idb.ts +270 -0
- package/lib/library/read-audio-metadata.ts +34 -0
- package/lib/library/read-stored-library-scan-preferences.ts +43 -0
- package/lib/library/resolve-track-file.ts +26 -0
- package/lib/library/scan-preferences-to-tree-options.ts +15 -0
- package/lib/library/scan-progress-label.ts +18 -0
- package/lib/library/scan-progress-percent.ts +19 -0
- package/lib/library/scan-progress-tick.ts +9 -0
- package/lib/library/scan-tree.ts +191 -0
- package/lib/library/write-stored-library-scan-preferences.ts +19 -0
- package/lib/mock-playlist.ts +47 -0
- package/lib/musicbrainz/build-musicbrainz-lucene-queries.ts +46 -0
- package/lib/musicbrainz/escape-lucene-term.ts +6 -0
- package/lib/musicbrainz/fetch-musicbrainz-json.ts +55 -0
- package/lib/musicbrainz/fetch-release-tracks.ts +53 -0
- package/lib/musicbrainz/group-tracks-by-album.ts +26 -0
- package/lib/musicbrainz/group-tracks-by-artist.ts +23 -0
- package/lib/musicbrainz/merge-tracks-by-id.ts +16 -0
- package/lib/musicbrainz/musicbrainz-recording-to-track.ts +42 -0
- package/lib/musicbrainz/pick-preferred-release.ts +32 -0
- package/lib/musicbrainz/pick-release-group-release-id.ts +12 -0
- package/lib/musicbrainz/release-group-artist-name.ts +13 -0
- package/lib/musicbrainz/search-musicbrainz-recordings.ts +33 -0
- package/lib/musicbrainz/search-musicbrainz-release-groups.ts +24 -0
- package/lib/musicbrainz/search-musicbrainz.ts +65 -0
- package/lib/musicbrainz/types.ts +43 -0
- package/lib/musicbrainz.ts +3 -0
- package/lib/playback/build-queue-from-snapshot.ts +49 -0
- package/lib/playback/parse-persisted-track.ts +45 -0
- package/lib/playback/read-stored-playback-snapshot.ts +45 -0
- package/lib/playback/write-stored-playback-snapshot.ts +19 -0
- package/lib/theme-constants.ts +4 -0
- package/lib/theme-init-script.ts +9 -0
- package/lib/youtube/clear-youtube-data-api-blocked.ts +8 -0
- package/lib/youtube/collect-youtube-prefetch-targets.ts +20 -0
- package/lib/youtube/is-youtube-quota-error-message.ts +7 -0
- package/lib/youtube/mark-youtube-data-api-blocked.ts +8 -0
- package/lib/youtube/prefetch-youtube-video-ids.ts +55 -0
- package/lib/youtube/read-stored-youtube-api-key.ts +16 -0
- package/lib/youtube/read-youtube-data-api-blocked.ts +12 -0
- package/lib/youtube/search-youtube-video-id.ts +60 -0
- package/lib/youtube/should-use-youtube-search-playback.ts +19 -0
- package/lib/youtube/write-stored-youtube-api-key.ts +18 -0
- package/next.config.ts +7 -0
- package/package.json +94 -0
- package/pnpm-workspace.yaml +6 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
- package/types/file-system-access.d.ts +22 -0
- package/types/library-root-meta.ts +5 -0
- package/types/library-scan-preferences.ts +9 -0
- package/types/library-scan-progress.ts +8 -0
- package/types/persisted-playback-snapshot.ts +11 -0
- package/types/queue.ts +7 -0
- package/types/scan-tree-options.ts +6 -0
- package/types/track.ts +29 -0
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "muzical-ui",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Local Node.JS music player with library browse, queue playback, and MusicBrainz discovery.",
|
|
5
|
+
"author": "f3rnox",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/f3rnox/muzical-ui#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/f3rnox/muzical-ui.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/f3rnox/muzical-ui/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"music",
|
|
17
|
+
"player",
|
|
18
|
+
"local-library",
|
|
19
|
+
"musicbrainz",
|
|
20
|
+
"nextjs"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"stt-ui": "./bin/stt-ui.js"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"dev": "next dev",
|
|
30
|
+
"build": "next build",
|
|
31
|
+
"start": "next start",
|
|
32
|
+
"build:docs": "typedoc --out ./docs ./src && cp LICENSE.md docs/LICENSE.md && cp CHANGELOG.md docs/CHANGELOG.md",
|
|
33
|
+
"build:ts": "NODE_PATH=./src tsc -p tsconfig.json",
|
|
34
|
+
"format": "pnpm format:prettier",
|
|
35
|
+
"format:check": "pnpm format:prettier:check",
|
|
36
|
+
"format:prettier": "prettier --write .",
|
|
37
|
+
"format:prettier:check": "prettier --check .",
|
|
38
|
+
"lint": "pnpm lint:markdownlint && pnpm lint:eslint",
|
|
39
|
+
"lint:eslint": "eslint package.json src/**/*",
|
|
40
|
+
"lint:eslint:fix": "eslint package.json src/**/*",
|
|
41
|
+
"lint:fix": "pnpm lint:markdownlint:fix && pnpm lint:eslint:fix",
|
|
42
|
+
"lint:markdownlint": "markdownlint README.md",
|
|
43
|
+
"lint:markdownlint:fix": "markdownlint --fix README.md",
|
|
44
|
+
"prepare": "husky",
|
|
45
|
+
"prepare-release": "pnpm build && git add docs",
|
|
46
|
+
"prepublish": "pnpm build",
|
|
47
|
+
"release": "pnpm prepare-release && pnpm update-version && git push --follow-tags origin main",
|
|
48
|
+
"serve:coverage": "http-server ./coverage",
|
|
49
|
+
"serve:docs": "http-server ./docs",
|
|
50
|
+
"test": "NODE_PATH=./src NODE_ENV=test vitest run",
|
|
51
|
+
"update-deps": "updates -u -g -c",
|
|
52
|
+
"update-version": "standard-version -a"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"music-metadata": "^11.12.3",
|
|
56
|
+
"next": "16.2.6",
|
|
57
|
+
"react": "19.2.4",
|
|
58
|
+
"react-dom": "19.2.4"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@tailwindcss/postcss": "^4",
|
|
62
|
+
"@types/node": "^20",
|
|
63
|
+
"@types/react": "^19",
|
|
64
|
+
"@types/react-dom": "^19",
|
|
65
|
+
"eslint": "^9",
|
|
66
|
+
"eslint-config-next": "16.2.6",
|
|
67
|
+
"tailwindcss": "^4",
|
|
68
|
+
"@commitlint/cli": "^20.5.0",
|
|
69
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
70
|
+
"@commitlint/format": "^20.5.0",
|
|
71
|
+
"@commitlint/types": "^20.5.0",
|
|
72
|
+
"@eslint/js": "^10.0.1",
|
|
73
|
+
"@istanbuljs/nyc-config-typescript": "^1.0.2",
|
|
74
|
+
"eslint-plugin-package-json": "^0.91.1",
|
|
75
|
+
"globals": "^17.4.0",
|
|
76
|
+
"http-server": "^14.1.1",
|
|
77
|
+
"husky": "^9.1.7",
|
|
78
|
+
"jiti": "^2.6.1",
|
|
79
|
+
"lint-staged": "^16.4.0",
|
|
80
|
+
"markdownlint-cli": "^0.48.0",
|
|
81
|
+
"nyc": "^18.0.0",
|
|
82
|
+
"prettier": "^3.8.1",
|
|
83
|
+
"remark": "^15.0.1",
|
|
84
|
+
"standard-version": "^9.5.0",
|
|
85
|
+
"ts-node": "^10.9.2",
|
|
86
|
+
"tslib": "^2.8.1",
|
|
87
|
+
"tsx": "^4.21.0",
|
|
88
|
+
"typedoc": "^0.28.18",
|
|
89
|
+
"typescript": "^6.0.2",
|
|
90
|
+
"typescript-eslint": "^8.58.0",
|
|
91
|
+
"updates": "^17.13.5",
|
|
92
|
+
"vitest": "^4.1.2"
|
|
93
|
+
}
|
|
94
|
+
}
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/next.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Augment DOM types for File System Access API (directory picker + async iteration).
|
|
3
|
+
*/
|
|
4
|
+
export {};
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
showDirectoryPicker(options?: {
|
|
9
|
+
mode?: "read" | "readwrite";
|
|
10
|
+
}): Promise<FileSystemDirectoryHandle>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FileSystemDirectoryHandle {
|
|
14
|
+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
|
|
15
|
+
queryPermission?(options?: {
|
|
16
|
+
mode?: "read" | "readwrite";
|
|
17
|
+
}): Promise<PermissionState>;
|
|
18
|
+
requestPermission?(options?: {
|
|
19
|
+
mode?: "read" | "readwrite";
|
|
20
|
+
}): Promise<PermissionState>;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** User-configurable options applied during library folder scans. */
|
|
2
|
+
export type LibraryScanPreferences = {
|
|
3
|
+
/** `0` scans all nested folders; otherwise max subdirectory depth from each root. */
|
|
4
|
+
maxScanDepth: number;
|
|
5
|
+
/** When true, directory symlinks may be followed (browser-dependent); cycles are skipped. */
|
|
6
|
+
followSymlinks: boolean;
|
|
7
|
+
/** Lowercase extensions including dot, subset of `LIBRARY_AUDIO_EXTENSIONS`. */
|
|
8
|
+
enabledExtensions: string[];
|
|
9
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Track } from '@/types/track'
|
|
2
|
+
|
|
3
|
+
/** Saved queue and playhead for “remember last track / queue”. */
|
|
4
|
+
export type PersistedPlaybackSnapshot = {
|
|
5
|
+
/** Legacy; kept when writing for older readers. */
|
|
6
|
+
trackIds: string[];
|
|
7
|
+
/** Full queue tracks in order (includes MusicBrainz and other non-library rows). */
|
|
8
|
+
tracks?: Track[];
|
|
9
|
+
activeTrackId: string | null;
|
|
10
|
+
positionSec: number;
|
|
11
|
+
};
|
package/types/queue.ts
ADDED
package/types/track.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser file-system library reference (File System Access API).
|
|
3
|
+
*/
|
|
4
|
+
export type LibraryTrackRef = {
|
|
5
|
+
rootId: string;
|
|
6
|
+
relativePath: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Local library track shape. Align with your API contract when the backend is ready.
|
|
11
|
+
*/
|
|
12
|
+
export type Track = {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
artist: string;
|
|
16
|
+
album: string;
|
|
17
|
+
/** Seconds; from metadata, decode, or API */
|
|
18
|
+
durationSec: number;
|
|
19
|
+
/** Stream URL once API serves files or signed URLs */
|
|
20
|
+
audioUrl?: string | null;
|
|
21
|
+
/** YouTube search query for browser playback */
|
|
22
|
+
youtubeQuery?: string;
|
|
23
|
+
/** Resolved YouTube video id (YouTube Data API v3) */
|
|
24
|
+
youtubeVideoId?: string;
|
|
25
|
+
/** Origin source for the track */
|
|
26
|
+
source?: 'library' | 'musicbrainz' | 'youtube';
|
|
27
|
+
/** Set when the row comes from a configured local scan directory */
|
|
28
|
+
library?: LibraryTrackRef;
|
|
29
|
+
};
|