@naxodev/opencode-music-player 0.2.4 → 0.2.5

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.
Files changed (2) hide show
  1. package/package.json +7 -6
  2. package/system-media.ts +37 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naxodev/opencode-music-player",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "macOS system Now Playing controls for the OpenCode 2 TUI",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -62,20 +62,21 @@
62
62
  "format:check": "prettier --check .",
63
63
  "check": "tsc --noEmit",
64
64
  "test": "bun test --preload @opentui/solid/preload",
65
- "pack:check": "bun run scripts/verify-pack.ts",
65
+ "pack:check": "bun run ../../scripts/music-release.ts opencode-music-player --staged-only && bun run scripts/verify-pack.ts",
66
+ "prepublish:core": "bun run ../../scripts/music-release.ts opencode-music-player",
66
67
  "smoke:package": "bun run scripts/package-smoke.ts",
67
68
  "validate": "bun run format:check && bun run check && bun run test && bun run pack:check"
68
69
  },
69
70
  "dependencies": {
70
71
  "@naxodev/music-core": "^0.1.1",
71
72
  "@opencode-ai/plugin": "0.0.0-next-17444",
72
- "@opentui/core": "0.5.3",
73
- "@opentui/solid": "0.5.3",
73
+ "@opentui/core": "0.5.4",
74
+ "@opentui/solid": "0.5.4",
74
75
  "pngjs": "^7.0.0",
75
- "solid-js": "1.9.12"
76
+ "solid-js": "1.9.15"
76
77
  },
77
78
  "devDependencies": {
78
- "@opencode-ai/theme": "0.0.0-next-17444",
79
+ "@opencode-ai/theme": "0.0.0",
79
80
  "@types/bun": "^1.3.14",
80
81
  "@types/pngjs": "^6.0.5",
81
82
  "prettier": "3.9.6",
package/system-media.ts CHANGED
@@ -52,7 +52,9 @@ const waitingArtwork = new Map<string, DeferredArtwork>()
52
52
  type ArtworkResolver = typeof resolveArtworkDetails
53
53
 
54
54
  /** Public-contract-only seam for the reconnecting session adapter. */
55
- export type SessionClientFactory = () => Promise<ReconnectingMusicSessionClient>
55
+ export type SessionClientFactory = (
56
+ signal?: AbortSignal,
57
+ ) => Promise<ReconnectingMusicSessionClient>
56
58
  export type SessionSystemMediaOverrides = {
57
59
  readonly createClient?: SessionClientFactory
58
60
  readonly resolveArtworkDetails?: ArtworkResolver
@@ -60,11 +62,12 @@ export type SessionSystemMediaOverrides = {
60
62
  }
61
63
 
62
64
  let sessionClientSequence = 0
63
- const createOpenCodeSessionClient: SessionClientFactory = () =>
65
+ const createOpenCodeSessionClient: SessionClientFactory = (signal) =>
64
66
  createReconnectingMusicSessionClient({
65
67
  clientId: `opencode-music-player-${++sessionClientSequence}`,
66
68
  hostKind: "opencode",
67
69
  capabilities: [...baselineCapabilities],
70
+ ...(signal ? { signal } : {}),
68
71
  })
69
72
 
70
73
  type PresentationHost = {
@@ -302,6 +305,7 @@ export function createSessionSystemMedia(
302
305
  const now = overrides.now ?? Date.now
303
306
  const listeners = new Set<(event: SessionMediaEvent) => void>()
304
307
  const presentationListeners = new Set<ArtworkPresentationListener>()
308
+ const acquisitionAbort = new AbortController()
305
309
  let disposed = false
306
310
  let currentArtworkIdentity: string | null = null
307
311
  let client: ReconnectingMusicSessionClient | undefined
@@ -315,10 +319,10 @@ export function createSessionSystemMedia(
315
319
  let clientReleased = false
316
320
  let disposal: Promise<void> | undefined
317
321
 
318
- const releaseClient = (next: ReconnectingMusicSessionClient) => {
319
- if (clientReleased) return Promise.resolve()
322
+ const releaseClient = async (next: ReconnectingMusicSessionClient) => {
323
+ if (clientReleased) return
320
324
  clientReleased = true
321
- return Promise.resolve(next.dispose()).catch(() => {})
325
+ await next.dispose()
322
326
  }
323
327
  const emit = (event: SessionMediaEvent) => {
324
328
  if (disposed) return
@@ -458,10 +462,12 @@ export function createSessionSystemMedia(
458
462
  ]
459
463
  }
460
464
  const clientPromise = Promise.resolve()
461
- .then(factory)
465
+ .then(() => factory(acquisitionAbort.signal))
462
466
  .then((next) => {
463
467
  if (disposed) {
464
- void releaseClient(next)
468
+ void releaseClient(next).catch((error) => {
469
+ console.error("Failed to dispose late music session client", error)
470
+ })
465
471
  return next
466
472
  }
467
473
  install(next)
@@ -528,18 +534,30 @@ export function createSessionSystemMedia(
528
534
  dispose() {
529
535
  if (disposal) return disposal
530
536
  disposed = true
531
- for (const unsubscribe of unsubscribers.splice(0)) {
532
- try {
533
- unsubscribe()
534
- } catch {}
535
- }
536
- listeners.clear()
537
- presentationListeners.clear()
538
- removeArtworkInterests(host)
539
- disposal = clientPromise.then(
540
- (next) => releaseClient(next),
541
- () => undefined,
542
- )
537
+ disposal = Promise.resolve().then(async () => {
538
+ const errors: unknown[] = []
539
+ for (const unsubscribe of unsubscribers.splice(0)) {
540
+ try {
541
+ unsubscribe()
542
+ } catch (error) {
543
+ errors.push(error)
544
+ }
545
+ }
546
+ listeners.clear()
547
+ presentationListeners.clear()
548
+ removeArtworkInterests(host)
549
+ // Acquisition is cooperative. A late client is released by its callback.
550
+ if (client) {
551
+ try {
552
+ await releaseClient(client)
553
+ } catch (error) {
554
+ errors.push(error)
555
+ }
556
+ }
557
+ if (errors.length)
558
+ throw new AggregateError(errors, "music session cleanup failed")
559
+ })
560
+ acquisitionAbort.abort()
543
561
  return disposal
544
562
  },
545
563
  }