loukai-app 0.3.22 → 0.4.2
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/README.md +5 -5
- package/package.json +2 -2
- package/src/main/audioEngine.js +11 -10
- package/src/main/creator/conversionService.js +9 -8
- package/src/main/creator/downloadManager.js +70 -67
- package/src/main/creator/ffmpegService.js +3 -2
- package/src/main/creator/installLogger.js +5 -7
- package/src/main/creator/llmService.js +13 -7
- package/src/main/creator/lrclibService.js +5 -6
- package/src/main/creator/pythonRunner.js +3 -2
- package/src/main/creator/stemBuilder.js +29 -28
- package/src/main/handlers/audioHandlers.js +2 -1
- package/src/main/handlers/canvasHandlers.js +4 -3
- package/src/main/handlers/creatorHandlers.js +2 -1
- package/src/main/handlers/editorHandlers.js +8 -7
- package/src/main/handlers/fileHandlers.js +1 -1
- package/src/main/handlers/index.js +21 -19
- package/src/main/handlers/libraryHandlers.js +3 -0
- package/src/main/handlers/queueHandlers.js +3 -2
- package/src/main/handlers/settingsHandlers.js +2 -1
- package/src/main/handlers/webServerHandlers.js +3 -2
- package/src/main/logger.js +12 -0
- package/src/main/main.js +71 -66
- package/src/main/statePersistence.js +13 -12
- package/src/main/utils/pathValidator.js +21 -17
- package/src/main/webServer.js +102 -95
- package/src/renderer/components/creator/CreateTab.jsx +6 -6
- package/src/renderer/dist/assets/{kaiPlayer-CoMx__a_.js → kaiPlayer-DSaY7TxC.js} +2 -2
- package/src/renderer/dist/assets/kaiPlayer-DSaY7TxC.js.map +1 -0
- package/src/renderer/dist/assets/songLoaders-CcYVonLu.js +2 -0
- package/src/renderer/dist/assets/{songLoaders-BaTgGib4.js.map → songLoaders-CcYVonLu.js.map} +1 -1
- package/src/renderer/dist/renderer.css +1 -1
- package/src/renderer/dist/renderer.js +11 -51
- package/src/renderer/dist/renderer.js.map +1 -1
- package/src/renderer/js/kaiPlayer.js +9 -7
- package/src/renderer/lib/cdgraphics.js +0 -1
- package/src/shared/services/creatorService.js +2 -2
- package/src/shared/services/libraryService.js +4 -1
- package/src/shared/services/serverSettingsService.js +0 -1
- package/src/shared/services/settingsService.js +0 -2
- package/src/utils/m4aLoader.js +1 -1
- package/src/web/dist/assets/index-CGbmW1VG.js +11 -0
- package/src/web/dist/assets/{index-0H-RnRrV.js.map → index-CGbmW1VG.js.map} +1 -1
- package/src/web/dist/assets/index-GLKJK41r.css +1 -0
- package/src/web/dist/index.html +2 -2
- package/src/renderer/dist/assets/kaiPlayer-CoMx__a_.js.map +0 -1
- package/src/renderer/dist/assets/songLoaders-BaTgGib4.js +0 -2
- package/src/web/dist/assets/index-0H-RnRrV.js +0 -51
- package/src/web/dist/assets/index-DYW2zB0u.css +0 -1
package/src/main/main.js
CHANGED
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
loadAndSync,
|
|
24
24
|
getBroadcastChannel,
|
|
25
25
|
} from '../shared/services/settingsService.js';
|
|
26
|
+
import { log } from './logger.js';
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
log('📦 About to import registerAllHandlers...');
|
|
28
29
|
import { registerAllHandlers } from './handlers/index.js';
|
|
29
|
-
|
|
30
|
+
log('✅ registerAllHandlers imported:', typeof registerAllHandlers);
|
|
30
31
|
|
|
31
32
|
// ESM equivalent of __dirname
|
|
32
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -153,9 +154,13 @@ class KaiPlayerApp {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
async initialize() {
|
|
157
|
+
// Set app name to 'loukai' for consistent userData path across all installs
|
|
158
|
+
// This ensures settings go to ~/.config/loukai/ (Linux), matching creator paths
|
|
159
|
+
app.setName('loukai');
|
|
160
|
+
|
|
156
161
|
await app.whenReady();
|
|
157
162
|
|
|
158
|
-
|
|
163
|
+
log('🚀 App starting...', {
|
|
159
164
|
isPackaged: app.isPackaged,
|
|
160
165
|
__dirname,
|
|
161
166
|
resourcesPath: process.resourcesPath,
|
|
@@ -226,7 +231,7 @@ class KaiPlayerApp {
|
|
|
226
231
|
|
|
227
232
|
// Log all console messages from renderer
|
|
228
233
|
this.mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
|
|
229
|
-
|
|
234
|
+
log(`[Renderer ${level}] ${message} (${sourceId}:${line})`);
|
|
230
235
|
});
|
|
231
236
|
|
|
232
237
|
// Log renderer loading events
|
|
@@ -235,7 +240,7 @@ class KaiPlayerApp {
|
|
|
235
240
|
});
|
|
236
241
|
|
|
237
242
|
this.mainWindow.webContents.on('did-finish-load', () => {
|
|
238
|
-
|
|
243
|
+
log('✅ Renderer finished loading');
|
|
239
244
|
});
|
|
240
245
|
|
|
241
246
|
// Set dock icon on macOS
|
|
@@ -276,7 +281,7 @@ class KaiPlayerApp {
|
|
|
276
281
|
// F12 key
|
|
277
282
|
if (input.key === 'F12') {
|
|
278
283
|
event.preventDefault();
|
|
279
|
-
|
|
284
|
+
log('F12 pressed, toggling DevTools...');
|
|
280
285
|
try {
|
|
281
286
|
if (this.mainWindow.webContents.isDevToolsOpened()) {
|
|
282
287
|
this.mainWindow.webContents.closeDevTools();
|
|
@@ -290,7 +295,7 @@ class KaiPlayerApp {
|
|
|
290
295
|
// Ctrl+Shift+I
|
|
291
296
|
if ((input.control || input.meta) && input.shift && input.key === 'I') {
|
|
292
297
|
event.preventDefault();
|
|
293
|
-
|
|
298
|
+
log('Ctrl+Shift+I pressed, toggling DevTools...');
|
|
294
299
|
try {
|
|
295
300
|
if (this.mainWindow.webContents.isDevToolsOpened()) {
|
|
296
301
|
this.mainWindow.webContents.closeDevTools();
|
|
@@ -344,7 +349,7 @@ class KaiPlayerApp {
|
|
|
344
349
|
});
|
|
345
350
|
|
|
346
351
|
this.canvasWindow.on('closed', () => {
|
|
347
|
-
|
|
352
|
+
log('🔴 Child window closed, stopping streaming and cleanup');
|
|
348
353
|
this.stopCanvasStreaming();
|
|
349
354
|
this.canvasWindow = null;
|
|
350
355
|
});
|
|
@@ -405,12 +410,12 @@ class KaiPlayerApp {
|
|
|
405
410
|
|
|
406
411
|
// Only proceed if child window is still open and not destroyed
|
|
407
412
|
if (this.canvasWindow.isDestroyed()) {
|
|
408
|
-
|
|
413
|
+
log('❌ Child window destroyed, cannot start streaming');
|
|
409
414
|
return;
|
|
410
415
|
}
|
|
411
416
|
|
|
412
417
|
try {
|
|
413
|
-
|
|
418
|
+
log('Starting WebRTC canvas streaming...');
|
|
414
419
|
|
|
415
420
|
// Set up WebRTC sender in main window via IPC
|
|
416
421
|
const senderResult = await this.sendWebRTCCommand('setupSender');
|
|
@@ -430,19 +435,19 @@ class KaiPlayerApp {
|
|
|
430
435
|
await this.establishWebRTCConnection();
|
|
431
436
|
|
|
432
437
|
this.canvasStreaming.isStreaming = true;
|
|
433
|
-
|
|
438
|
+
log('✅ WebRTC canvas streaming started successfully');
|
|
434
439
|
} catch (error) {
|
|
435
440
|
console.error('Canvas streaming setup error:', error);
|
|
436
441
|
}
|
|
437
442
|
}
|
|
438
443
|
|
|
439
444
|
async establishWebRTCConnection() {
|
|
440
|
-
|
|
445
|
+
log('🤝 Starting WebRTC handshake...');
|
|
441
446
|
|
|
442
447
|
let offer;
|
|
443
448
|
try {
|
|
444
449
|
// Create offer in sender (main window) via IPC
|
|
445
|
-
|
|
450
|
+
log('📤 Creating offer in sender...');
|
|
446
451
|
|
|
447
452
|
offer = await this.sendWebRTCCommand('createOffer');
|
|
448
453
|
|
|
@@ -450,14 +455,14 @@ class KaiPlayerApp {
|
|
|
450
455
|
throw new Error('Sender error: ' + offer.error);
|
|
451
456
|
}
|
|
452
457
|
|
|
453
|
-
|
|
458
|
+
log('✅ Offer creation successful, moving to receiver...');
|
|
454
459
|
} catch (error) {
|
|
455
460
|
console.error('❌ Failed to create offer:', error);
|
|
456
461
|
throw error;
|
|
457
462
|
}
|
|
458
463
|
|
|
459
464
|
try {
|
|
460
|
-
|
|
465
|
+
log('📥 Setting offer in receiver and creating answer...');
|
|
461
466
|
|
|
462
467
|
// First check if child window is ready
|
|
463
468
|
if (!this.canvasWindow || this.canvasWindow.isDestroyed()) {
|
|
@@ -465,9 +470,9 @@ class KaiPlayerApp {
|
|
|
465
470
|
}
|
|
466
471
|
|
|
467
472
|
// Check if receiver is ready via IPC
|
|
468
|
-
|
|
473
|
+
log('🔍 Checking if child window is ready...');
|
|
469
474
|
const childReady = await this.sendCanvasWebRTCCommand('checkReceiverReady');
|
|
470
|
-
|
|
475
|
+
log('🏓 Child window status:', childReady);
|
|
471
476
|
|
|
472
477
|
if (!childReady.hasReceiverPC) {
|
|
473
478
|
throw new Error('Receiver PC not found in child window');
|
|
@@ -481,10 +486,10 @@ class KaiPlayerApp {
|
|
|
481
486
|
}
|
|
482
487
|
|
|
483
488
|
// Set answer in sender via IPC
|
|
484
|
-
|
|
489
|
+
log('📤 Setting answer in sender...');
|
|
485
490
|
await this.sendWebRTCCommand('setAnswer', answer);
|
|
486
491
|
|
|
487
|
-
|
|
492
|
+
log('✅ WebRTC peer connection handshake complete');
|
|
488
493
|
|
|
489
494
|
// Wait a bit for ICE connection to establish
|
|
490
495
|
setTimeout(() => {
|
|
@@ -501,9 +506,9 @@ class KaiPlayerApp {
|
|
|
501
506
|
|
|
502
507
|
const receiverStatus = await this.sendCanvasWebRTCCommand('getReceiverStatus');
|
|
503
508
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
509
|
+
log('📊 Connection Status:');
|
|
510
|
+
log(' Sender:', senderStatus);
|
|
511
|
+
log(' Receiver:', receiverStatus);
|
|
507
512
|
} catch (error) {
|
|
508
513
|
console.error('Error checking connection status:', error);
|
|
509
514
|
}
|
|
@@ -513,7 +518,7 @@ class KaiPlayerApp {
|
|
|
513
518
|
if (!this.canvasStreaming.isStreaming) return;
|
|
514
519
|
|
|
515
520
|
try {
|
|
516
|
-
|
|
521
|
+
log('Stopping canvas streaming...');
|
|
517
522
|
|
|
518
523
|
// Cleanup sender (main window) via IPC
|
|
519
524
|
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
|
@@ -526,7 +531,7 @@ class KaiPlayerApp {
|
|
|
526
531
|
}
|
|
527
532
|
|
|
528
533
|
this.canvasStreaming.isStreaming = false;
|
|
529
|
-
|
|
534
|
+
log('Canvas streaming stopped');
|
|
530
535
|
} catch (error) {
|
|
531
536
|
console.error('Error stopping canvas streaming:', error);
|
|
532
537
|
}
|
|
@@ -613,7 +618,7 @@ class KaiPlayerApp {
|
|
|
613
618
|
label: 'Toggle Developer Tools',
|
|
614
619
|
accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Ctrl+Shift+I',
|
|
615
620
|
click: (item, focusedWindow) => {
|
|
616
|
-
|
|
621
|
+
log('Menu: Toggle Developer Tools clicked', {
|
|
617
622
|
hasFocusedWindow: Boolean(focusedWindow),
|
|
618
623
|
windowType: focusedWindow?.getTitle(),
|
|
619
624
|
});
|
|
@@ -624,10 +629,10 @@ class KaiPlayerApp {
|
|
|
624
629
|
if (targetWindow) {
|
|
625
630
|
try {
|
|
626
631
|
if (targetWindow.webContents.isDevToolsOpened()) {
|
|
627
|
-
|
|
632
|
+
log('Closing DevTools...');
|
|
628
633
|
targetWindow.webContents.closeDevTools();
|
|
629
634
|
} else {
|
|
630
|
-
|
|
635
|
+
log('Opening DevTools...');
|
|
631
636
|
targetWindow.webContents.openDevTools();
|
|
632
637
|
}
|
|
633
638
|
} catch (error) {
|
|
@@ -760,9 +765,9 @@ class KaiPlayerApp {
|
|
|
760
765
|
// All IPC handlers have been organized into handler modules
|
|
761
766
|
// See: src/main/handlers/
|
|
762
767
|
try {
|
|
763
|
-
|
|
768
|
+
log('🔧 Setting up IPC handlers...');
|
|
764
769
|
registerAllHandlers(this);
|
|
765
|
-
|
|
770
|
+
log('✅ IPC setup complete');
|
|
766
771
|
} catch (error) {
|
|
767
772
|
console.error('❌ Failed to setup IPC handlers:', error);
|
|
768
773
|
console.error('Stack:', error.stack);
|
|
@@ -1134,7 +1139,7 @@ class KaiPlayerApp {
|
|
|
1134
1139
|
}
|
|
1135
1140
|
} catch {
|
|
1136
1141
|
// No kara atom or error reading it - not a karaoke file
|
|
1137
|
-
|
|
1142
|
+
log(`No kara atom in ${m4aFilePath}`);
|
|
1138
1143
|
}
|
|
1139
1144
|
|
|
1140
1145
|
// Fallback to filename parsing if no tags
|
|
@@ -1221,7 +1226,7 @@ class KaiPlayerApp {
|
|
|
1221
1226
|
|
|
1222
1227
|
async loadCDGFile(mp3Path, cdgPath, format, queueItemId = null) {
|
|
1223
1228
|
try {
|
|
1224
|
-
|
|
1229
|
+
log('💿 Loading CDG file:', { mp3Path, cdgPath, format, queueItemId });
|
|
1225
1230
|
|
|
1226
1231
|
// Get requester from queue if queueItemId is provided
|
|
1227
1232
|
let requester = 'KJ';
|
|
@@ -1255,7 +1260,7 @@ class KaiPlayerApp {
|
|
|
1255
1260
|
};
|
|
1256
1261
|
this.appState.setCurrentSong(songData);
|
|
1257
1262
|
|
|
1258
|
-
|
|
1263
|
+
log('💿 CDG loaded, sending to renderer');
|
|
1259
1264
|
this.sendToRenderer('song:loaded', cdgData.metadata || {});
|
|
1260
1265
|
this.sendToRenderer('song:data', cdgData);
|
|
1261
1266
|
|
|
@@ -1285,7 +1290,7 @@ class KaiPlayerApp {
|
|
|
1285
1290
|
|
|
1286
1291
|
async loadM4AFile(m4aPath, queueItemId = null) {
|
|
1287
1292
|
try {
|
|
1288
|
-
|
|
1293
|
+
log('🎵 Loading M4A file:', { m4aPath, queueItemId });
|
|
1289
1294
|
|
|
1290
1295
|
// Get requester from queue if queueItemId is provided
|
|
1291
1296
|
let requester = 'KJ';
|
|
@@ -1324,7 +1329,7 @@ class KaiPlayerApp {
|
|
|
1324
1329
|
};
|
|
1325
1330
|
this.appState.setCurrentSong(songData);
|
|
1326
1331
|
|
|
1327
|
-
|
|
1332
|
+
log('🎵 M4A loaded, sending to renderer');
|
|
1328
1333
|
this.sendToRenderer('song:loaded', m4aData.metadata || {});
|
|
1329
1334
|
this.sendToRenderer('song:data', m4aData);
|
|
1330
1335
|
|
|
@@ -1357,17 +1362,17 @@ class KaiPlayerApp {
|
|
|
1357
1362
|
const songsFolder = this.settings.getSongsFolder();
|
|
1358
1363
|
|
|
1359
1364
|
if (!songsFolder) {
|
|
1360
|
-
|
|
1365
|
+
log('📁 No songs folder set, prompting user...');
|
|
1361
1366
|
await this.promptForSongsFolder();
|
|
1362
1367
|
} else {
|
|
1363
|
-
|
|
1368
|
+
log('📁 Songs folder:', songsFolder);
|
|
1364
1369
|
// Verify folder still exists
|
|
1365
1370
|
if (!fs.existsSync(songsFolder)) {
|
|
1366
|
-
|
|
1371
|
+
log('⚠️ Songs folder no longer exists, prompting for new one...');
|
|
1367
1372
|
await this.promptForSongsFolder();
|
|
1368
1373
|
} else {
|
|
1369
1374
|
// Trigger library scan on startup in background
|
|
1370
|
-
|
|
1375
|
+
log('📚 Starting library scan...');
|
|
1371
1376
|
this.scanLibraryInBackground(songsFolder);
|
|
1372
1377
|
}
|
|
1373
1378
|
}
|
|
@@ -1383,7 +1388,7 @@ class KaiPlayerApp {
|
|
|
1383
1388
|
const cacheData = JSON.parse(await fsPromises.readFile(cacheFile, 'utf8'));
|
|
1384
1389
|
// Check if cache is for the same folder
|
|
1385
1390
|
if (cacheData.songsFolder === songsFolder) {
|
|
1386
|
-
|
|
1391
|
+
log(`📂 Found library cache with ${cacheData.files.length} songs`);
|
|
1387
1392
|
|
|
1388
1393
|
// Load from cache
|
|
1389
1394
|
const files = cacheData.files;
|
|
@@ -1400,21 +1405,21 @@ class KaiPlayerApp {
|
|
|
1400
1405
|
|
|
1401
1406
|
// Notify renderer
|
|
1402
1407
|
this.sendToRenderer('library:scanComplete', { count: files.length });
|
|
1403
|
-
|
|
1408
|
+
log(`✅ Library loaded from cache: ${files.length} songs`);
|
|
1404
1409
|
useCache = true;
|
|
1405
1410
|
}
|
|
1406
1411
|
} catch {
|
|
1407
1412
|
// Cache doesn't exist or is invalid, will scan
|
|
1408
|
-
|
|
1413
|
+
log('📚 No valid cache found, scanning library...');
|
|
1409
1414
|
}
|
|
1410
1415
|
|
|
1411
1416
|
if (useCache) return;
|
|
1412
1417
|
|
|
1413
1418
|
// First, quickly count all files
|
|
1414
|
-
|
|
1419
|
+
log('📊 Counting files...');
|
|
1415
1420
|
const allFiles = await this.findAllKaiFiles(songsFolder);
|
|
1416
1421
|
const totalFiles = allFiles.length;
|
|
1417
|
-
|
|
1422
|
+
log(`📊 Found ${totalFiles} files to process`);
|
|
1418
1423
|
|
|
1419
1424
|
// Notify renderer of total count
|
|
1420
1425
|
this.sendToRenderer('library:scanProgress', { current: 0, total: totalFiles });
|
|
@@ -1422,7 +1427,7 @@ class KaiPlayerApp {
|
|
|
1422
1427
|
// Now process files with metadata extraction and progress updates
|
|
1423
1428
|
// Pass null for progressCallback since this.sendToRenderer() is called directly in the method
|
|
1424
1429
|
const files = await this.scanForKaiFilesWithProgress(songsFolder, totalFiles, null);
|
|
1425
|
-
|
|
1430
|
+
log(`✅ Library scan complete: ${files.length} songs found`);
|
|
1426
1431
|
|
|
1427
1432
|
// Store in main process
|
|
1428
1433
|
this.cachedLibrary = files;
|
|
@@ -1445,7 +1450,7 @@ class KaiPlayerApp {
|
|
|
1445
1450
|
}),
|
|
1446
1451
|
'utf8'
|
|
1447
1452
|
);
|
|
1448
|
-
|
|
1453
|
+
log('💾 Library cache saved to disk');
|
|
1449
1454
|
} catch (err) {
|
|
1450
1455
|
console.error('Failed to save library cache:', err);
|
|
1451
1456
|
}
|
|
@@ -1887,7 +1892,7 @@ class KaiPlayerApp {
|
|
|
1887
1892
|
*/
|
|
1888
1893
|
setSongsFolderAndScan(folder) {
|
|
1889
1894
|
this.settings.setSongsFolder(folder);
|
|
1890
|
-
|
|
1895
|
+
log('📁 Songs folder set to:', folder);
|
|
1891
1896
|
|
|
1892
1897
|
// Notify renderer about the new library
|
|
1893
1898
|
this.sendToRenderer('library:folderSet', folder);
|
|
@@ -1967,7 +1972,7 @@ class KaiPlayerApp {
|
|
|
1967
1972
|
this.webServer.io.emit(channel, value);
|
|
1968
1973
|
}
|
|
1969
1974
|
|
|
1970
|
-
|
|
1975
|
+
log(`📡 Settings broadcast: ${key} -> ${channel}`);
|
|
1971
1976
|
}
|
|
1972
1977
|
|
|
1973
1978
|
// Web Server Integration Methods
|
|
@@ -1976,8 +1981,8 @@ class KaiPlayerApp {
|
|
|
1976
1981
|
this.webServer = new WebServer(this);
|
|
1977
1982
|
const port = await this.webServer.start(3069);
|
|
1978
1983
|
|
|
1979
|
-
|
|
1980
|
-
|
|
1984
|
+
log(`🌐 Web server started at http://localhost:${port}`);
|
|
1985
|
+
log(`📱 Song requests available at: http://localhost:${port}`);
|
|
1981
1986
|
|
|
1982
1987
|
// Connect to Socket.IO server
|
|
1983
1988
|
await this.connectToSocketServer(port);
|
|
@@ -1998,32 +2003,32 @@ class KaiPlayerApp {
|
|
|
1998
2003
|
this.socket = io(`http://localhost:${port}`);
|
|
1999
2004
|
|
|
2000
2005
|
this.socket.on('connect', () => {
|
|
2001
|
-
|
|
2006
|
+
log('📡 Connected to Socket.IO server');
|
|
2002
2007
|
|
|
2003
2008
|
// Identify as electron app
|
|
2004
2009
|
this.socket.emit('identify', { type: 'electron-app' });
|
|
2005
2010
|
});
|
|
2006
2011
|
|
|
2007
2012
|
this.socket.on('disconnect', () => {
|
|
2008
|
-
|
|
2013
|
+
log('📡 Disconnected from Socket.IO server');
|
|
2009
2014
|
});
|
|
2010
2015
|
|
|
2011
2016
|
this.socket.on('song-request', (request) => {
|
|
2012
|
-
|
|
2017
|
+
log('🎵 Song request received:', request);
|
|
2013
2018
|
|
|
2014
2019
|
// Notify renderer about new request
|
|
2015
2020
|
this.sendToRenderer('songRequest:new', request);
|
|
2016
2021
|
});
|
|
2017
2022
|
|
|
2018
2023
|
this.socket.on('request-approved', (request) => {
|
|
2019
|
-
|
|
2024
|
+
log('✅ Request approved:', request);
|
|
2020
2025
|
|
|
2021
2026
|
// Notify renderer about approved request
|
|
2022
2027
|
this.sendToRenderer('songRequest:approved', request);
|
|
2023
2028
|
});
|
|
2024
2029
|
|
|
2025
2030
|
this.socket.on('request-rejected', (request) => {
|
|
2026
|
-
|
|
2031
|
+
log('❌ Request rejected:', request);
|
|
2027
2032
|
|
|
2028
2033
|
// Notify renderer about rejected request
|
|
2029
2034
|
this.sendToRenderer('songRequest:rejected', request);
|
|
@@ -2034,12 +2039,12 @@ class KaiPlayerApp {
|
|
|
2034
2039
|
});
|
|
2035
2040
|
|
|
2036
2041
|
this.socket.on('effect-control', (data) => {
|
|
2037
|
-
|
|
2042
|
+
log('🎨 Effect control received:', data.action);
|
|
2038
2043
|
this.handleEffectControl(data.action);
|
|
2039
2044
|
});
|
|
2040
2045
|
|
|
2041
2046
|
this.socket.on('settings-update', (settings) => {
|
|
2042
|
-
|
|
2047
|
+
log('🔧 Settings update received from server:', settings);
|
|
2043
2048
|
this.handleSettingsUpdate(settings);
|
|
2044
2049
|
});
|
|
2045
2050
|
} catch (error) {
|
|
@@ -2073,10 +2078,10 @@ class KaiPlayerApp {
|
|
|
2073
2078
|
// Send effect control command to renderer process
|
|
2074
2079
|
if (action === 'previous') {
|
|
2075
2080
|
this.sendToRenderer('effect:previous', {});
|
|
2076
|
-
|
|
2081
|
+
log('🎨 Sent previous effect command to renderer');
|
|
2077
2082
|
} else if (action === 'next') {
|
|
2078
2083
|
this.sendToRenderer('effect:next', {});
|
|
2079
|
-
|
|
2084
|
+
log('🎨 Sent next effect command to renderer');
|
|
2080
2085
|
}
|
|
2081
2086
|
}
|
|
2082
2087
|
|
|
@@ -2089,7 +2094,7 @@ class KaiPlayerApp {
|
|
|
2089
2094
|
|
|
2090
2095
|
// Send settings update to renderer to update UI
|
|
2091
2096
|
this.sendToRenderer('settings:update', settings);
|
|
2092
|
-
|
|
2097
|
+
log('🔧 Settings update sent to renderer');
|
|
2093
2098
|
}
|
|
2094
2099
|
|
|
2095
2100
|
// Methods called by WebServer
|
|
@@ -2112,7 +2117,7 @@ class KaiPlayerApp {
|
|
|
2112
2117
|
}
|
|
2113
2118
|
|
|
2114
2119
|
async addSongToQueue(queueItem) {
|
|
2115
|
-
|
|
2120
|
+
log('🎵 MAIN addSongToQueue called with:', queueItem);
|
|
2116
2121
|
|
|
2117
2122
|
// Use shared queueService
|
|
2118
2123
|
const result = queueService.addSongToQueue(this.appState, queueItem);
|
|
@@ -2122,31 +2127,31 @@ class KaiPlayerApp {
|
|
|
2122
2127
|
throw new Error(result.error);
|
|
2123
2128
|
}
|
|
2124
2129
|
|
|
2125
|
-
|
|
2130
|
+
log('🎵 Created new queue item:', result.queueItem);
|
|
2126
2131
|
|
|
2127
2132
|
// Update legacy songQueue for compatibility
|
|
2128
2133
|
this.songQueue = result.queue;
|
|
2129
2134
|
|
|
2130
2135
|
// If queue was empty, automatically load and start playing the first song
|
|
2131
2136
|
if (result.wasEmpty) {
|
|
2132
|
-
|
|
2137
|
+
log(`🎵 Queue was empty, auto-loading "${result.queueItem.title}"`);
|
|
2133
2138
|
try {
|
|
2134
2139
|
// Use the returned queueItem which has the generated ID
|
|
2135
2140
|
await this.loadKaiFile(result.queueItem.path, result.queueItem.id);
|
|
2136
|
-
|
|
2141
|
+
log('✅ Successfully auto-loaded song from queue');
|
|
2137
2142
|
} catch (error) {
|
|
2138
2143
|
console.error('❌ Failed to auto-load song from queue:', error);
|
|
2139
2144
|
}
|
|
2140
2145
|
}
|
|
2141
2146
|
|
|
2142
|
-
|
|
2147
|
+
log(`➕ Added "${queueItem.title}" to queue (requested by ${queueItem.requester})`);
|
|
2143
2148
|
return result;
|
|
2144
2149
|
}
|
|
2145
2150
|
|
|
2146
2151
|
onSongRequest(request) {
|
|
2147
2152
|
// Notify renderer about new song request
|
|
2148
2153
|
this.sendToRenderer('songRequest:new', request);
|
|
2149
|
-
|
|
2154
|
+
log(`🎤 New song request: "${request.song.title}" by ${request.requesterName}`);
|
|
2150
2155
|
}
|
|
2151
2156
|
|
|
2152
2157
|
// Effects management methods for web server
|
|
@@ -2229,7 +2234,7 @@ class KaiPlayerApp {
|
|
|
2229
2234
|
|
|
2230
2235
|
// Player control methods for web server
|
|
2231
2236
|
playerPlay() {
|
|
2232
|
-
|
|
2237
|
+
log('🎮 Admin play command - using playerService');
|
|
2233
2238
|
return playerService.play(this);
|
|
2234
2239
|
}
|
|
2235
2240
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { app } from 'electron';
|
|
4
|
+
import { log } from './logger.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* StatePersistence - Saves and loads AppState to/from disk
|
|
@@ -38,13 +39,13 @@ class StatePersistence {
|
|
|
38
39
|
// Restore mixer state if available
|
|
39
40
|
if (savedState.mixer) {
|
|
40
41
|
this.appState.state.mixer = savedState.mixer;
|
|
41
|
-
|
|
42
|
+
log('📂 Loaded mixer state');
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
// Restore effects state if available
|
|
45
46
|
if (savedState.effects) {
|
|
46
47
|
this.appState.state.effects = savedState.effects;
|
|
47
|
-
|
|
48
|
+
log('📂 Loaded effects state');
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
// Restore preferences if available
|
|
@@ -53,14 +54,14 @@ class StatePersistence {
|
|
|
53
54
|
...this.appState.state.preferences,
|
|
54
55
|
...savedState.preferences,
|
|
55
56
|
};
|
|
56
|
-
|
|
57
|
+
log('📂 Loaded preferences');
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
log('✅ State loaded from disk');
|
|
60
61
|
return true;
|
|
61
62
|
} catch (error) {
|
|
62
63
|
if (error.code === 'ENOENT') {
|
|
63
|
-
|
|
64
|
+
log('ℹ️ No saved state found (first run)');
|
|
64
65
|
return false;
|
|
65
66
|
}
|
|
66
67
|
|
|
@@ -74,12 +75,12 @@ class StatePersistence {
|
|
|
74
75
|
// Restore from backup
|
|
75
76
|
if (savedState.mixer) {
|
|
76
77
|
this.appState.state.mixer = savedState.mixer;
|
|
77
|
-
|
|
78
|
+
log('📂 Loaded mixer state from backup');
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (savedState.effects) {
|
|
81
82
|
this.appState.state.effects = savedState.effects;
|
|
82
|
-
|
|
83
|
+
log('📂 Loaded effects state from backup');
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
if (savedState.preferences) {
|
|
@@ -87,10 +88,10 @@ class StatePersistence {
|
|
|
87
88
|
...this.appState.state.preferences,
|
|
88
89
|
...savedState.preferences,
|
|
89
90
|
};
|
|
90
|
-
|
|
91
|
+
log('📂 Loaded preferences from backup');
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
log('✅ State restored from backup');
|
|
94
95
|
|
|
95
96
|
// Mark as dirty to save the restored state
|
|
96
97
|
this.isDirty = true;
|
|
@@ -146,7 +147,7 @@ class StatePersistence {
|
|
|
146
147
|
await fs.rename(tempPath, this.stateFile);
|
|
147
148
|
|
|
148
149
|
this.isDirty = false;
|
|
149
|
-
|
|
150
|
+
log('💾 State saved to disk');
|
|
150
151
|
} catch (error) {
|
|
151
152
|
console.error('❌ Failed to save state:', error);
|
|
152
153
|
throw error; // Propagate error so caller knows save failed
|
|
@@ -167,7 +168,7 @@ class StatePersistence {
|
|
|
167
168
|
}
|
|
168
169
|
}, 30000); // 30 seconds
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
log('⏰ Started periodic state persistence (30s interval)');
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
/**
|
|
@@ -177,7 +178,7 @@ class StatePersistence {
|
|
|
177
178
|
if (this.saveInterval) {
|
|
178
179
|
clearInterval(this.saveInterval);
|
|
179
180
|
this.saveInterval = null;
|
|
180
|
-
|
|
181
|
+
log('⏸️ Stopped periodic state persistence');
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -28,22 +28,26 @@ export function validateSongPath(filePath, songsFolder) {
|
|
|
28
28
|
|
|
29
29
|
// Check if the resolved path starts with the songs folder
|
|
30
30
|
// This prevents ../../../etc/passwd style attacks
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
if (
|
|
32
|
+
!resolvedFilePath.startsWith(resolvedSongsFolder + path.sep) &&
|
|
33
|
+
resolvedFilePath !== resolvedSongsFolder
|
|
34
|
+
) {
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: 'Access denied: path is outside songs directory',
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
// Additional check: if the path was absolute, verify it's within songs folder
|
|
40
42
|
if (path.isAbsolute(filePath)) {
|
|
41
43
|
const resolvedAbsolute = path.resolve(filePath);
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
if (
|
|
45
|
+
!resolvedAbsolute.startsWith(resolvedSongsFolder + path.sep) &&
|
|
46
|
+
resolvedAbsolute !== resolvedSongsFolder
|
|
47
|
+
) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: 'Access denied: absolute path is outside songs directory',
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
// Use the absolute path as-is if it's valid
|
|
@@ -66,7 +70,7 @@ export function validateBase64Path(encodedPath, songsFolder) {
|
|
|
66
70
|
|
|
67
71
|
try {
|
|
68
72
|
const decoded = Buffer.from(encodedPath, 'base64url').toString('utf8');
|
|
69
|
-
|
|
73
|
+
|
|
70
74
|
// The decoded format is "path:filename" or "path:trackName:trackIndex"
|
|
71
75
|
const parts = decoded.split(':');
|
|
72
76
|
if (parts.length < 2) {
|
|
@@ -74,19 +78,19 @@ export function validateBase64Path(encodedPath, songsFolder) {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
const filePath = parts[0];
|
|
77
|
-
|
|
81
|
+
|
|
78
82
|
// Validate the file path portion
|
|
79
83
|
const validation = validateSongPath(filePath, songsFolder);
|
|
80
84
|
if (!validation.valid) {
|
|
81
85
|
return validation;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
|
-
return {
|
|
85
|
-
valid: true,
|
|
88
|
+
return {
|
|
89
|
+
valid: true,
|
|
86
90
|
decodedPath: decoded,
|
|
87
|
-
resolvedPath: validation.resolvedPath
|
|
91
|
+
resolvedPath: validation.resolvedPath,
|
|
88
92
|
};
|
|
89
|
-
} catch
|
|
93
|
+
} catch {
|
|
90
94
|
return { valid: false, error: 'Invalid base64 encoding' };
|
|
91
95
|
}
|
|
92
96
|
}
|
|
@@ -108,5 +112,5 @@ export function isValidFile(filePath) {
|
|
|
108
112
|
export default {
|
|
109
113
|
validateSongPath,
|
|
110
114
|
validateBase64Path,
|
|
111
|
-
isValidFile
|
|
115
|
+
isValidFile,
|
|
112
116
|
};
|