@naxodev/opencode-music-player 0.2.5 → 0.3.0

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/tmux-offset.ts DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * Tmux pane-origin offset for absolute Kitty coordinates.
3
- *
4
- * Cache is keyed by OpenTUI **pre-offset** slot geometry so resize/layout
5
- * changes force a re-query even when wall-clock TTL would still allow a hit.
6
- */
7
-
8
- export type SlotGeometry = {
9
- screenX: number
10
- screenY: number
11
- width: number
12
- height: number
13
- }
14
-
15
- export type TmuxOffset = { x: number; y: number }
16
-
17
- export type TmuxOffsetQueryResult = {
18
- ok: boolean
19
- stdout: string
20
- }
21
-
22
- export type TmuxOffsetCache = {
23
- offset: TmuxOffset | null
24
- slot: SlotGeometry | null
25
- }
26
-
27
- const ZERO: TmuxOffset = { x: 0, y: 0 }
28
-
29
- export function createTmuxOffsetCache(): TmuxOffsetCache {
30
- return { offset: null, slot: null }
31
- }
32
-
33
- /** True only when running under tmux without Herdr (Herdr already owns passthrough). */
34
- export function shouldUseTmuxOffset(
35
- env: NodeJS.ProcessEnv = process.env,
36
- ): boolean {
37
- return Boolean(env.TMUX) && !env.HERDR_ENV
38
- }
39
-
40
- function slotKey(slot: SlotGeometry): string {
41
- return [slot.screenX, slot.screenY, slot.width, slot.height].join(":")
42
- }
43
-
44
- function sameSlotGeometry(
45
- a: SlotGeometry | null,
46
- b: SlotGeometry | null,
47
- ): boolean {
48
- if (a === null || b === null) return false
49
- return slotKey(a) === slotKey(b)
50
- }
51
-
52
- /**
53
- * Parse `tmux display-message -p '#{pane_left}\t#{pane_top}\t#{status-position}\t#{status}'`.
54
- * Status bar on top adds one row when status is not off.
55
- */
56
- export function parseTmuxOffsetMessage(stdout: string): TmuxOffset {
57
- const [left, top, statusPosition, status] = stdout.trim().split("\t")
58
- return {
59
- x: Number.parseInt(left ?? "0", 10) || 0,
60
- y:
61
- (Number.parseInt(top ?? "0", 10) || 0) +
62
- (statusPosition === "top" && status !== "off" ? 1 : 0),
63
- }
64
- }
65
-
66
- function queryTmuxOffset(): TmuxOffsetQueryResult {
67
- const result = Bun.spawnSync([
68
- "tmux",
69
- "display-message",
70
- "-p",
71
- "#{pane_left}\t#{pane_top}\t#{status-position}\t#{status}",
72
- ])
73
- return {
74
- ok: result.exitCode === 0,
75
- stdout: result.stdout.toString(),
76
- }
77
- }
78
-
79
- /**
80
- * Resolve pane origin for absolute Kitty placement.
81
- *
82
- * - No TMUX / HERDR_ENV → zeros (no spawn).
83
- * - Slot geometry changed or first paint / empty cache → re-query.
84
- * - Identical slot + prior successful cache → reuse (no spawn).
85
- * - Failed query → zeros and clear cache so the next paint retries.
86
- */
87
- export function resolveTerminalOffset(options: {
88
- env?: NodeJS.ProcessEnv
89
- slot: SlotGeometry | null
90
- cache: TmuxOffsetCache
91
- query?: () => TmuxOffsetQueryResult
92
- }): TmuxOffset {
93
- const env = options.env ?? process.env
94
- if (!shouldUseTmuxOffset(env)) {
95
- options.cache.offset = null
96
- options.cache.slot = null
97
- return ZERO
98
- }
99
-
100
- if (
101
- options.cache.offset !== null &&
102
- options.slot !== null &&
103
- sameSlotGeometry(options.cache.slot, options.slot)
104
- ) {
105
- return options.cache.offset
106
- }
107
-
108
- const query = options.query ?? queryTmuxOffset
109
- const result = query()
110
- if (!result.ok) {
111
- options.cache.offset = null
112
- options.cache.slot = null
113
- return ZERO
114
- }
115
-
116
- const offset = parseTmuxOffsetMessage(result.stdout)
117
- options.cache.offset = offset
118
- options.cache.slot = options.slot ? { ...options.slot } : null
119
- return offset
120
- }
package/tui.tsx DELETED
@@ -1 +0,0 @@
1
- export { default } from "./index.tsx"
package/types.ts DELETED
@@ -1,143 +0,0 @@
1
- import type {
2
- PlayerState as CorePlayerState,
3
- Track as CoreTrack,
4
- } from "@naxodev/music-core"
5
- import {
6
- emptyPlayer as emptyCorePlayer,
7
- formatMs,
8
- isMac,
9
- } from "@naxodev/music-core"
10
-
11
- export type { Device, MusicError } from "@naxodev/music-core"
12
- export { formatMs, isMac }
13
-
14
- export type Artwork = {
15
- id: string
16
- legacy_id?: string
17
- png_base64: string
18
- accent: string
19
- cells: Array<Array<{ upper: string; lower: string }>>
20
- }
21
-
22
- /** Native IDs validate artwork samples; recording metadata keys artwork work. */
23
- export type ArtworkIdentity = {
24
- uid: string
25
- title: string
26
- artist: string
27
- album: string
28
- duration_ms: number
29
- }
30
-
31
- export type ArtworkCompletionEvent = {
32
- type: "artwork-completion"
33
- identity: ArtworkIdentity
34
- artwork: Artwork | null
35
- duration_ms: number
36
- }
37
-
38
- export type ArtworkPresentationListener = (
39
- event: ArtworkCompletionEvent,
40
- ) => void
41
-
42
- export type Track = CoreTrack & {
43
- artwork: Artwork | null
44
- artwork_loading?: boolean
45
- }
46
-
47
- export type PlayerState = Omit<CorePlayerState, "track"> & {
48
- track: Track | null
49
- }
50
-
51
- export type SessionMediaSnapshotEvent = {
52
- type: "snapshot"
53
- state: PlayerState
54
- }
55
-
56
- /** Host-local projection of session provider/connection lifecycle. */
57
- export type SessionMediaLifecycleEvent = {
58
- type: "lifecycle"
59
- message: string | null
60
- source: "connection" | "provider" | "acquisition"
61
- }
62
-
63
- export type SessionMediaEvent =
64
- SessionMediaSnapshotEvent | SessionMediaLifecycleEvent
65
- export type SessionMediaListener = (event: SessionMediaEvent) => void
66
- export type SessionMediaDisposer = () => void
67
-
68
- /** The OpenCode controller's session-only media contract. */
69
- export type SessionMedia = {
70
- player: () => Promise<PlayerState | null>
71
- play: () => Promise<unknown>
72
- pause: () => Promise<unknown>
73
- next: () => Promise<unknown>
74
- previous: () => Promise<unknown>
75
- seek: (positionMs: number) => Promise<unknown>
76
- subscribe: (listener: SessionMediaListener) => SessionMediaDisposer
77
- subscribePresentation: (
78
- listener: ArtworkPresentationListener,
79
- ) => SessionMediaDisposer
80
- dispose: () => Promise<void>
81
- }
82
-
83
- export function emptyPlayer(): PlayerState {
84
- return {
85
- ...emptyCorePlayer(),
86
- track: null,
87
- }
88
- }
89
-
90
- /** Merge an independent artwork result without accepting stale playback data. */
91
- export function mergeArtworkCompletion(
92
- player: PlayerState | null,
93
- event: ArtworkCompletionEvent,
94
- ): PlayerState | null {
95
- const track = player?.track
96
- if (
97
- !track ||
98
- track.name !== event.identity.title ||
99
- track.artists !== event.identity.artist ||
100
- track.album !== event.identity.album ||
101
- (track.duration_ms > 0 && track.duration_ms !== event.identity.duration_ms)
102
- ) {
103
- return player
104
- }
105
- return {
106
- ...player,
107
- track: {
108
- ...track,
109
- artwork: event.artwork,
110
- artwork_loading: false,
111
- },
112
- }
113
- }
114
-
115
- /** Keep host-local artwork presentation across playback-only snapshots. */
116
- export function mergePlayerSnapshot(
117
- player: PlayerState | null,
118
- snapshot: PlayerState,
119
- ): PlayerState {
120
- const current = player?.track
121
- const next = snapshot.track
122
- if (
123
- !current ||
124
- !next ||
125
- current.name !== next.name ||
126
- current.artists !== next.artists ||
127
- current.album !== next.album ||
128
- (current.duration_ms > 0 &&
129
- next.duration_ms > 0 &&
130
- current.duration_ms !== next.duration_ms) ||
131
- (current.artwork === null && current.artwork_loading !== false)
132
- ) {
133
- return snapshot
134
- }
135
- return {
136
- ...snapshot,
137
- track: {
138
- ...next,
139
- artwork: next.artwork ?? current.artwork,
140
- artwork_loading: false,
141
- },
142
- }
143
- }