@naxodev/opencode-music-player 0.4.0 → 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 +2 -0
- package/dist/index.js +67 -47
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ Read the [music session architecture field guide](../../docs/music-session-archi
|
|
|
14
14
|
|
|
15
15
|
## Artwork
|
|
16
16
|
|
|
17
|
+
The player retains a completed cover when the same recording loses duration metadata or reports a duration within one second of the resolved value. Conflicting recording metadata starts a new acquisition. A catalog duration keeps the seek bar usable when the provider has no duration; this host-only fallback does not change daemon playback state. A valid provider duration takes priority.
|
|
18
|
+
|
|
17
19
|
The daemon performs the bounded native `media-control get --now` read and validates the complete recording identity before and after the read. OpenCode uses those bytes when available, then keeps iTunes Search fallback, image downloads, conversion, cache/job ownership, and terminal rendering locally. Artwork failure never blocks playback state.
|
|
18
20
|
|
|
19
21
|
Catalog matching, bounded downloads, cancellation, and transient retries use the [shared host-side acquisition policy](../music-core/README.md#host-side-catalog-artwork). OpenCode keeps image conversion and its shared presentation cache/jobs. Track changes remove only the changing view's interest; another view can keep the same job alive.
|
package/dist/index.js
CHANGED
|
@@ -181,6 +181,53 @@ async function resolveArtworkDetails(id, target, nativeBase64, legacyId = id, fe
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// types.ts
|
|
185
|
+
import {
|
|
186
|
+
emptyPlayer as emptyCorePlayer,
|
|
187
|
+
formatMs,
|
|
188
|
+
isMac
|
|
189
|
+
} from "@naxodev/music-core";
|
|
190
|
+
function presentationDuration(track) {
|
|
191
|
+
for (const duration of [track?.duration_ms, track?.artwork_duration_ms])
|
|
192
|
+
if (typeof duration === "number" && Number.isFinite(duration) && duration > 0 && duration <= 86400000)
|
|
193
|
+
return duration;
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
function compatibleArtworkDuration(previous, resolved, next) {
|
|
197
|
+
return next === previous || next === 0 || typeof resolved === "number" && resolved > 0 && Number.isFinite(resolved) && Math.abs(next - resolved) <= 1000;
|
|
198
|
+
}
|
|
199
|
+
function mergeArtworkCompletion(player, event) {
|
|
200
|
+
const track = player?.track;
|
|
201
|
+
if (!track || track.name !== event.identity.title || track.artists !== event.identity.artist || track.album !== event.identity.album || track.duration_ms > 0 && track.duration_ms !== event.identity.duration_ms) {
|
|
202
|
+
return player;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
...player,
|
|
206
|
+
track: {
|
|
207
|
+
...track,
|
|
208
|
+
artwork: event.artwork,
|
|
209
|
+
artwork_loading: false,
|
|
210
|
+
artwork_duration_ms: event.duration_ms
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function mergePlayerSnapshot(player, snapshot) {
|
|
215
|
+
const current = player?.track;
|
|
216
|
+
const next = snapshot.track;
|
|
217
|
+
if (!current || !next || current.name !== next.name || current.artists !== next.artists || current.album !== next.album || !compatibleArtworkDuration(current.duration_ms, current.artwork_duration_ms, next.duration_ms) || current.artwork === null && current.artwork_loading !== false) {
|
|
218
|
+
return snapshot;
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
...snapshot,
|
|
222
|
+
track: {
|
|
223
|
+
...next,
|
|
224
|
+
artwork: next.artwork ?? current.artwork,
|
|
225
|
+
artwork_loading: false,
|
|
226
|
+
artwork_duration_ms: next.artwork_duration_ms || current.artwork_duration_ms || 0
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
184
231
|
// system-media.ts
|
|
185
232
|
var MAX_ARTWORK_ENTRIES = 32;
|
|
186
233
|
var artworkCache = new Map;
|
|
@@ -377,6 +424,7 @@ function createSessionSystemMedia(overrides = {}) {
|
|
|
377
424
|
let disposed = false;
|
|
378
425
|
let currentArtworkIdentity = null;
|
|
379
426
|
let currentArtworkKey = null;
|
|
427
|
+
let currentArtworkTrack = null;
|
|
380
428
|
let client;
|
|
381
429
|
let installed = false;
|
|
382
430
|
let latest;
|
|
@@ -449,10 +497,15 @@ function createSessionSystemMedia(overrides = {}) {
|
|
|
449
497
|
emit(event);
|
|
450
498
|
};
|
|
451
499
|
const project = (state) => {
|
|
452
|
-
const
|
|
500
|
+
const nextIdentity = state?.state.track ? identityFromTrack(state.state.track) : null;
|
|
501
|
+
let nextKey = nextIdentity ? artworkCacheKey(nextIdentity) : null;
|
|
502
|
+
const retained = currentArtworkKey ? artworkCache.get(currentArtworkKey) : undefined;
|
|
503
|
+
if (nextIdentity && currentArtworkTrack && retained?.value && nextIdentity.title === currentArtworkTrack.title && nextIdentity.artist === currentArtworkTrack.artist && nextIdentity.album === currentArtworkTrack.album && compatibleArtworkDuration(currentArtworkTrack.duration_ms, retained.duration_ms, nextIdentity.duration_ms))
|
|
504
|
+
nextKey = currentArtworkKey;
|
|
453
505
|
if (nextKey !== currentArtworkKey)
|
|
454
506
|
removeArtworkInterests(host);
|
|
455
507
|
currentArtworkKey = nextKey;
|
|
508
|
+
currentArtworkTrack = nextIdentity;
|
|
456
509
|
if (!state) {
|
|
457
510
|
currentArtworkIdentity = null;
|
|
458
511
|
return null;
|
|
@@ -464,7 +517,7 @@ function createSessionSystemMedia(overrides = {}) {
|
|
|
464
517
|
const track = state.state.track;
|
|
465
518
|
const identity = identityFromTrack(track);
|
|
466
519
|
currentArtworkIdentity = artworkIdentityKey(identity);
|
|
467
|
-
const artworkState = artworkForTrack(
|
|
520
|
+
const artworkState = artworkForTrack(currentArtworkKey, artworkIdentityKey(identity), {
|
|
468
521
|
title: track.name,
|
|
469
522
|
artist: track.artists,
|
|
470
523
|
album: track.album,
|
|
@@ -487,7 +540,8 @@ function createSessionSystemMedia(overrides = {}) {
|
|
|
487
540
|
track: {
|
|
488
541
|
...track,
|
|
489
542
|
artwork: artworkState.artwork,
|
|
490
|
-
artwork_loading: artworkState.loading
|
|
543
|
+
artwork_loading: artworkState.loading,
|
|
544
|
+
artwork_duration_ms: artworkState.duration_ms
|
|
491
545
|
}
|
|
492
546
|
};
|
|
493
547
|
};
|
|
@@ -552,10 +606,12 @@ function createSessionSystemMedia(overrides = {}) {
|
|
|
552
606
|
const track = latest?.state.track;
|
|
553
607
|
if (!track)
|
|
554
608
|
return;
|
|
555
|
-
const key = artworkCacheKey(identityFromTrack(track));
|
|
556
|
-
|
|
609
|
+
const key = currentArtworkKey ?? artworkCacheKey(identityFromTrack(track));
|
|
610
|
+
const requestedKey = artworkCacheKey(identityFromTrack(track));
|
|
611
|
+
if (artworkJobs.has(key) || artworkJobs.has(requestedKey))
|
|
557
612
|
return;
|
|
558
613
|
artworkCache.delete(key);
|
|
614
|
+
artworkCache.delete(requestedKey);
|
|
559
615
|
emit({ type: "snapshot", state: project(latest) });
|
|
560
616
|
},
|
|
561
617
|
async play() {
|
|
@@ -644,42 +700,6 @@ function openNowPlayingApp() {
|
|
|
644
700
|
});
|
|
645
701
|
}
|
|
646
702
|
|
|
647
|
-
// types.ts
|
|
648
|
-
import {
|
|
649
|
-
emptyPlayer as emptyCorePlayer,
|
|
650
|
-
formatMs,
|
|
651
|
-
isMac
|
|
652
|
-
} from "@naxodev/music-core";
|
|
653
|
-
function mergeArtworkCompletion(player, event) {
|
|
654
|
-
const track = player?.track;
|
|
655
|
-
if (!track || track.name !== event.identity.title || track.artists !== event.identity.artist || track.album !== event.identity.album || track.duration_ms > 0 && track.duration_ms !== event.identity.duration_ms) {
|
|
656
|
-
return player;
|
|
657
|
-
}
|
|
658
|
-
return {
|
|
659
|
-
...player,
|
|
660
|
-
track: {
|
|
661
|
-
...track,
|
|
662
|
-
artwork: event.artwork,
|
|
663
|
-
artwork_loading: false
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
|
-
function mergePlayerSnapshot(player, snapshot) {
|
|
668
|
-
const current = player?.track;
|
|
669
|
-
const next = snapshot.track;
|
|
670
|
-
if (!current || !next || current.name !== next.name || current.artists !== next.artists || current.album !== next.album || current.duration_ms > 0 && next.duration_ms > 0 && current.duration_ms !== next.duration_ms || current.artwork === null && current.artwork_loading !== false) {
|
|
671
|
-
return snapshot;
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
...snapshot,
|
|
675
|
-
track: {
|
|
676
|
-
...next,
|
|
677
|
-
artwork: next.artwork ?? current.artwork,
|
|
678
|
-
artwork_loading: false
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
|
|
683
703
|
// ui.tsx
|
|
684
704
|
import { memo as _$memo } from "@opentui/solid";
|
|
685
705
|
import { effect as _$effect2 } from "@opentui/solid";
|
|
@@ -1415,7 +1435,7 @@ function CompactPlayer(props) {
|
|
|
1415
1435
|
}
|
|
1416
1436
|
if (cell < 0 || cell >= seekWidth)
|
|
1417
1437
|
return;
|
|
1418
|
-
const position = seekPositionForCell(cell, seekWidth, current
|
|
1438
|
+
const position = seekPositionForCell(cell, seekWidth, presentationDuration(current));
|
|
1419
1439
|
if (position === null)
|
|
1420
1440
|
return;
|
|
1421
1441
|
event.preventDefault();
|
|
@@ -1439,7 +1459,7 @@ function liveProgress(player, now = Date.now()) {
|
|
|
1439
1459
|
return 0;
|
|
1440
1460
|
if (!player.is_playing)
|
|
1441
1461
|
return player.progress_ms;
|
|
1442
|
-
return Math.min(player.track
|
|
1462
|
+
return Math.min(presentationDuration(player.track), player.progress_ms + (Math.max(now, player.fetched_at) - player.fetched_at));
|
|
1443
1463
|
}
|
|
1444
1464
|
function progressSegments(progress, duration, width) {
|
|
1445
1465
|
if (duration <= 0)
|
|
@@ -1465,7 +1485,7 @@ function ProgressBar(props) {
|
|
|
1465
1485
|
const width = () => Math.max(8, props.width ?? 28);
|
|
1466
1486
|
const rendered = () => {
|
|
1467
1487
|
const player = props.source.current();
|
|
1468
|
-
return progressSegments(liveProgress(player), player?.track
|
|
1488
|
+
return progressSegments(liveProgress(player), presentationDuration(player?.track), width());
|
|
1469
1489
|
};
|
|
1470
1490
|
const paint = () => {
|
|
1471
1491
|
const next = rendered();
|
|
@@ -1496,7 +1516,7 @@ function ProgressBar(props) {
|
|
|
1496
1516
|
_$setProp2(_el$3, "onMouseDown", (event) => {
|
|
1497
1517
|
if (event.button !== MouseButton.LEFT || !bar)
|
|
1498
1518
|
return;
|
|
1499
|
-
const position = seekPositionForCell(event.x - bar.x, bar.width, props.source.current()?.track
|
|
1519
|
+
const position = seekPositionForCell(event.x - bar.x, bar.width, presentationDuration(props.source.current()?.track));
|
|
1500
1520
|
if (position === null)
|
|
1501
1521
|
return;
|
|
1502
1522
|
event.preventDefault();
|
|
@@ -1642,7 +1662,7 @@ function SidebarPlayer(props) {
|
|
|
1642
1662
|
const player = createMemo(() => props.state.player);
|
|
1643
1663
|
const track = createMemo(() => player()?.track);
|
|
1644
1664
|
const playing = createMemo(() => !!player()?.is_playing);
|
|
1645
|
-
const duration = createMemo(() => track()
|
|
1665
|
+
const duration = createMemo(() => presentationDuration(track()));
|
|
1646
1666
|
return (() => {
|
|
1647
1667
|
var _el$1 = _$createElement3("box"), _el$10 = _$createElement3("box"), _el$11 = _$createElement3("box"), _el$12 = _$createElement3("box"), _el$16 = _$createElement3("box");
|
|
1648
1668
|
_$insertNode2(_el$1, _el$10);
|
|
@@ -2026,7 +2046,7 @@ function createController(context, dependencies = controllerDependencies) {
|
|
|
2026
2046
|
};
|
|
2027
2047
|
const playPause = () => runCommand(() => media.toggle());
|
|
2028
2048
|
const seek = (positionMs) => {
|
|
2029
|
-
const target = seekTarget(positionMs, session.player?.track
|
|
2049
|
+
const target = seekTarget(positionMs, presentationDuration(session.player?.track));
|
|
2030
2050
|
if (!session.player?.track || target === null || !isActive())
|
|
2031
2051
|
return Promise.resolve();
|
|
2032
2052
|
return new Promise((resolve) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naxodev/opencode-music-player",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "macOS system Now Playing controls for the OpenCode 2 TUI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"validate": "bun run format:check && bun run check && bun run test && bun run pack:check"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@naxodev/music-core": "^0.1.
|
|
63
|
+
"@naxodev/music-core": "^0.1.5",
|
|
64
64
|
"pngjs": "^7.0.0"
|
|
65
65
|
},
|
|
66
66
|
"peerDependencies": {
|