Skip to content

Commit 3ede105

Browse files
committed
fix: restore local audio loading
1 parent d812e11 commit 3ede105

16 files changed

Lines changed: 366 additions & 48 deletions

File tree

CLAUDE.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## Commands
66

77
```bash
8-
npm run dev # start Electron app in development (hot reload)
9-
npm run tsc # type-check without emitting
10-
npm run lint # ESLint
11-
npm run lint:fix # ESLint with auto-fix
12-
npm run prettier:fix # format src/
13-
npm run build # full production build (tsc + vite + electron-builder)
8+
pnpm dev # start Electron app in development (hot reload)
9+
pnpm tsc # type-check without emitting
10+
pnpm lint # ESLint
11+
pnpm lint:fix # ESLint with auto-fix
12+
pnpm prettier:fix # format src/
13+
pnpm build # full production build (tsc + vite + electron-builder)
14+
pnpm tag patch # bump version, push main, and push release tag
1415
```
1516

16-
There are no tests yet. After any change, `npm run tsc` is the fastest correctness check.
17+
This project uses pnpm (`packageManager` is pinned in `package.json`). There are no tests yet. After any change, `pnpm tsc` is the fastest correctness check.
1718

18-
After `npm install`, the `postinstall` script runs `electron-rebuild -f -w better-sqlite3` automatically. If the app crashes on startup with a native module error, re-run `npm install` to rebuild.
19+
After `pnpm install`, the `postinstall` script runs `electron-rebuild -f -w better-sqlite3` automatically. If the app crashes on startup with a native module error, re-run `pnpm install` to rebuild.
1920

2021
## Architecture
2122

22-
SampleByte is an Electron 33 + React 19 + TypeScript desktop app. The full vision and technical decisions are in `docs/ARCHITECTURE.md`. The product roadmap is in `docs/ROADMAP.md`. UI design system, color tokens, and macOS conventions are in `docs/DESIGN.md` — read it before touching any UI code.
23+
SampleByte is an Electron 41 + React 19 + TypeScript desktop app. The full vision and technical decisions are in `docs/ARCHITECTURE.md`. The product roadmap is in `docs/ROADMAP.md`. UI design system, color tokens, and macOS conventions are in `docs/DESIGN.md` — read it before touching any UI code.
2324

2425
### Three processes
2526

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ No YouTube. Not because it isn't useful (the original version of this app was bu
5858

5959
## Tech Stack
6060

61-
- **Electron 33** + **React 19** + **TypeScript**
61+
- **Electron 41** + **React 19** + **TypeScript**
6262
- **Vite** + vite-plugin-electron
6363
- **Zustand** for state management
6464
- **better-sqlite3** for the sample library database
@@ -119,12 +119,12 @@ Click "More info" then "Run anyway" to get past the unsigned-app warning.
119119
```bash
120120
git clone https://github.com/RubenGlez/samplebyte
121121
cd samplebyte
122-
npm install
123-
npm run dev # development (hot reload)
124-
npm run build # production build
122+
pnpm install
123+
pnpm dev # development (hot reload)
124+
pnpm build # production build
125125
```
126126

127-
Requires Node.js v18+.
127+
Requires Node.js v18+ and pnpm. The project pins its pnpm version in `package.json`.
128128

129129
---
130130

docs/ARCHITECTURE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ Analysis runs once when audio is loaded into the Chop view. Results are stored o
141141

142142
---
143143

144+
## Local Audio Files
145+
146+
Renderer code does not read native file paths directly through `file://`. Local audio is exposed through the privileged `local-file://` protocol registered in `electron/main/index.ts`, which streams files with CORS headers so WaveSurfer, `<audio>`, and `fetch`-based analysis can all load the same URL.
147+
148+
When a user drops a file, the renderer asks the preload bridge for the native path via `webUtils.getPathForFile(file)`. Build `local-file://` URLs with `toLocalFileUrl()` from `src/utils/index.ts`; do not concatenate raw paths by hand. The protocol handler accepts Chromium's host/path URL form and reconstructs the absolute path before forwarding to `net.fetch(pathToFileURL(...))`.
149+
150+
---
151+
144152
## Freesound Integration
145153

146154
Freesound has a public REST API with Creative Commons licensed audio. The API key is stored in the main process (never exposed to the renderer) and all requests are proxied through the `freesound:*` IPC handlers in `electron/main/ipc/freesound.ts`. Downloaded files are added to the library automatically.

electron/main/index.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,17 @@ process.env.VITE_PUBLIC = process.env.VITE_DEV_SERVER_URL
2222

2323
// Must be called before app.ready
2424
protocol.registerSchemesAsPrivileged([
25-
{ scheme: 'local-file', privileges: { secure: true, supportFetchAPI: true, bypassCSP: true, stream: true } },
25+
{
26+
scheme: 'local-file',
27+
privileges: {
28+
standard: true,
29+
secure: true,
30+
supportFetchAPI: true,
31+
corsEnabled: true,
32+
bypassCSP: true,
33+
stream: true,
34+
},
35+
},
2636
])
2737

2838
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
@@ -96,11 +106,22 @@ async function createWindow() {
96106

97107
app.whenReady().then(() => {
98108
// Serve local audio files to the renderer without cross-origin restrictions
99-
protocol.handle('local-file', (request) => {
100-
const filePath = decodeURIComponent(request.url.slice('local-file://'.length))
109+
protocol.handle('local-file', async (request) => {
110+
const url = new URL(request.url)
111+
const filePath = decodeURIComponent(
112+
url.hostname ? `/${url.hostname}${url.pathname}` : url.pathname
113+
)
101114
// pathToFileURL properly percent-encodes spaces and special chars (e.g. paths under
102115
// "Application Support"). Plain `file://${filePath}` breaks on macOS userData paths.
103-
return net.fetch(pathToFileURL(filePath).href)
116+
const response = await net.fetch(pathToFileURL(filePath).href)
117+
const headers = new Headers(response.headers)
118+
headers.set('Access-Control-Allow-Origin', '*')
119+
headers.set('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS')
120+
return new Response(response.body, {
121+
status: response.status,
122+
statusText: response.statusText,
123+
headers,
124+
})
104125
})
105126

106127
if (!url) {

electron/preload/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ipcRenderer, contextBridge } from 'electron'
1+
import { ipcRenderer, contextBridge, webUtils } from 'electron'
22
import type { Sample, Pack, PackSlot, Project, ProjectRegion, ExportRegionsParams, FreesoundPage } from '../types'
33

44
contextBridge.exposeInMainWorld('api', {
@@ -52,6 +52,9 @@ contextBridge.exposeInMainWorld('api', {
5252
},
5353

5454
fs: {
55+
getPathForFile: (file: File): string =>
56+
webUtils.getPathForFile(file),
57+
5558
pickFile: (): Promise<string | null> =>
5659
ipcRenderer.invoke('fs:pickFile'),
5760

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,18 @@
4646
},
4747
"devDependencies": {
4848
"@electron/rebuild": "^4.0.4",
49+
"@eslint/js": "^10.0.1",
4950
"@types/better-sqlite3": "^7.6.13",
5051
"@types/fluent-ffmpeg": "^2.1.24",
5152
"@types/react": "^19.2.14",
5253
"@types/react-dom": "^19.2.3",
53-
"@eslint/js": "^10.0.1",
5454
"@typescript-eslint/eslint-plugin": "^8.59.4",
5555
"@typescript-eslint/parser": "^8.59.4",
5656
"@vitejs/plugin-react": "^4.0.4",
5757
"autoprefixer": "^10.4.16",
5858
"electron": "41.6.1",
5959
"electron-builder": "^26.8.1",
60+
"esbuild": "^0.28.0",
6061
"eslint": "^10.4.0",
6162
"eslint-plugin-react-hooks": "^7.0.1",
6263
"eslint-plugin-react-refresh": "^0.5.2",

0 commit comments

Comments
 (0)