@involvex/youtube-music-cli 0.0.30 → 0.0.31
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.0.31](https://github.com/involvex/youtube-music-cli/compare/v0.0.30...v0.0.31) (2026-02-22)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- improve mpv process management and fix EOF/pause race condition ([b5e9786](https://github.com/involvex/youtube-music-cli/commit/b5e9786d99bdc0dc81cd32bef65ac5467c032231))
|
|
6
|
+
|
|
1
7
|
## [0.0.30](https://github.com/involvex/youtube-music-cli/compare/v0.0.29...v0.0.30) (2026-02-22)
|
|
2
8
|
|
|
3
9
|
## [0.0.29](https://github.com/involvex/youtube-music-cli/compare/v0.0.28...v0.0.29) (2026-02-22)
|
|
@@ -65,7 +65,10 @@ declare class PlayerService {
|
|
|
65
65
|
/**
|
|
66
66
|
* Reattach to an existing mpv process via IPC
|
|
67
67
|
*/
|
|
68
|
-
reattach(ipcPath: string
|
|
68
|
+
reattach(ipcPath: string, options?: {
|
|
69
|
+
trackId?: string;
|
|
70
|
+
currentUrl?: string;
|
|
71
|
+
}): Promise<void>;
|
|
69
72
|
setVolume(volume: number): void;
|
|
70
73
|
getVolume(): number;
|
|
71
74
|
setSpeed(speed: number): void;
|
|
@@ -378,6 +378,10 @@ class PlayerService {
|
|
|
378
378
|
currentUrl: this.currentUrl,
|
|
379
379
|
};
|
|
380
380
|
if (this.mpvProcess) {
|
|
381
|
+
// Close piped stdio handles so Node has no open references that could
|
|
382
|
+
// prevent clean exit or send SIGHUP to the detached mpv process.
|
|
383
|
+
this.mpvProcess.stdout?.destroy();
|
|
384
|
+
this.mpvProcess.stderr?.destroy();
|
|
381
385
|
// Allow detached mpv process to survive after CLI exits.
|
|
382
386
|
this.mpvProcess.unref();
|
|
383
387
|
}
|
|
@@ -391,11 +395,15 @@ class PlayerService {
|
|
|
391
395
|
/**
|
|
392
396
|
* Reattach to an existing mpv process via IPC
|
|
393
397
|
*/
|
|
394
|
-
async reattach(ipcPath) {
|
|
398
|
+
async reattach(ipcPath, options) {
|
|
395
399
|
logger.info('PlayerService', 'Reattaching to player', { ipcPath });
|
|
396
400
|
this.ipcPath = ipcPath;
|
|
397
401
|
await this.connectIpc();
|
|
398
402
|
this.isPlaying = true;
|
|
403
|
+
if (options?.trackId)
|
|
404
|
+
this.currentTrackId = options.trackId;
|
|
405
|
+
if (options?.currentUrl)
|
|
406
|
+
this.currentUrl = options.currentUrl;
|
|
399
407
|
logger.info('PlayerService', 'Successfully reattached to player');
|
|
400
408
|
}
|
|
401
409
|
setVolume(volume) {
|
|
@@ -241,6 +241,7 @@ function PlayerManager() {
|
|
|
241
241
|
});
|
|
242
242
|
}, [dispatch]);
|
|
243
243
|
// Register event handler for mpv IPC events
|
|
244
|
+
const eofTimestampRef = useRef(0);
|
|
244
245
|
useEffect(() => {
|
|
245
246
|
let lastProgressUpdate = 0;
|
|
246
247
|
const PROGRESS_THROTTLE_MS = 1000; // Update progress max once per second
|
|
@@ -256,7 +257,19 @@ function PlayerManager() {
|
|
|
256
257
|
lastProgressUpdate = now;
|
|
257
258
|
}
|
|
258
259
|
}
|
|
260
|
+
if (event.eof) {
|
|
261
|
+
// Track ended — record timestamp so we can suppress mpv's spurious
|
|
262
|
+
// pause event that immediately follows EOF (idle state).
|
|
263
|
+
eofTimestampRef.current = Date.now();
|
|
264
|
+
next();
|
|
265
|
+
}
|
|
259
266
|
if (event.paused !== undefined) {
|
|
267
|
+
// mpv sends pause=true when a track ends and it enters idle mode.
|
|
268
|
+
// Suppress this for ~2s after EOF to prevent it from overwriting
|
|
269
|
+
// the isPlaying:true set by NEXT, which would block autoplay.
|
|
270
|
+
if (event.paused && Date.now() - eofTimestampRef.current < 2000) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
260
273
|
if (event.paused) {
|
|
261
274
|
dispatch({ category: 'PAUSE' });
|
|
262
275
|
}
|
|
@@ -264,10 +277,6 @@ function PlayerManager() {
|
|
|
264
277
|
dispatch({ category: 'RESUME' });
|
|
265
278
|
}
|
|
266
279
|
}
|
|
267
|
-
if (event.eof) {
|
|
268
|
-
// Track ended, play next
|
|
269
|
-
next();
|
|
270
|
-
}
|
|
271
280
|
});
|
|
272
281
|
}, [playerService, dispatch, next]);
|
|
273
282
|
// Initialize audio on mount
|
|
@@ -310,6 +319,32 @@ function PlayerManager() {
|
|
|
310
319
|
videoId: track.videoId,
|
|
311
320
|
});
|
|
312
321
|
const loadAndPlayTrack = async () => {
|
|
322
|
+
// If a detached background session exists for this exact track, reattach
|
|
323
|
+
// to the still-running mpv process instead of spawning a new one.
|
|
324
|
+
const config = getConfigService();
|
|
325
|
+
const bgState = config.getBackgroundPlaybackState();
|
|
326
|
+
const trackUrl = `https://www.youtube.com/watch?v=${track.videoId}`;
|
|
327
|
+
if (bgState.enabled &&
|
|
328
|
+
bgState.ipcPath &&
|
|
329
|
+
bgState.currentUrl === trackUrl) {
|
|
330
|
+
try {
|
|
331
|
+
await playerService.reattach(bgState.ipcPath, {
|
|
332
|
+
trackId: track.videoId,
|
|
333
|
+
currentUrl: trackUrl,
|
|
334
|
+
});
|
|
335
|
+
config.clearBackgroundPlaybackState();
|
|
336
|
+
dispatch({ category: 'SET_LOADING', loading: false });
|
|
337
|
+
logger.info('PlayerManager', 'Reattached to background mpv session');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
logger.warn('PlayerManager', 'Failed to reattach background session, starting fresh', {
|
|
342
|
+
error: error instanceof Error ? error.message : String(error),
|
|
343
|
+
});
|
|
344
|
+
config.clearBackgroundPlaybackState();
|
|
345
|
+
// Fall through to normal play()
|
|
346
|
+
}
|
|
347
|
+
}
|
|
313
348
|
dispatch({ category: 'SET_LOADING', loading: true });
|
|
314
349
|
const MAX_RETRIES = 3;
|
|
315
350
|
const RETRY_DELAY_MS = 1500;
|
|
@@ -452,11 +487,9 @@ function PlayerManager() {
|
|
|
452
487
|
if (state.repeat === 'one') {
|
|
453
488
|
dispatch({ category: 'SEEK', position: 0 });
|
|
454
489
|
}
|
|
455
|
-
|
|
456
|
-
next();
|
|
457
|
-
}
|
|
490
|
+
// next() for regular track completion is handled by the eof IPC event
|
|
458
491
|
}
|
|
459
|
-
}, [state.progress, state.duration, state.repeat,
|
|
492
|
+
}, [state.progress, state.duration, state.repeat, dispatch]);
|
|
460
493
|
return null;
|
|
461
494
|
}
|
|
462
495
|
export function PlayerProvider({ children }) {
|
|
Binary file
|