@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): Promise<void>;
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
- else {
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, next, dispatch]);
492
+ }, [state.progress, state.duration, state.repeat, dispatch]);
460
493
  return null;
461
494
  }
462
495
  export function PlayerProvider({ children }) {
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@involvex/youtube-music-cli",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "- A Commandline music player for youtube-music",
5
5
  "repository": {
6
6
  "type": "git",