@lox-audioserver/node-librespot 0.3.6 → 0.4.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/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ConnectHandle, ConnectEvent, CreateSessionOpts, CredentialsResult, LibrespotSession, LogEvent, DownloadTrackOpts, DownloadHandle } from './types';
2
2
  declare const native: {
3
3
  createSession(opts: CreateSessionOpts): Promise<LibrespotSession>;
4
- createSessionWithCredentials(credentialsPath: string, deviceName?: string | null): Promise<LibrespotSession>;
4
+ createSessionWithCredentials(credentialsPath: string, deviceName?: string | null, cacheDir?: string | null, cacheSizeLimitMb?: number | null): Promise<LibrespotSession>;
5
5
  setLogLevel(level: string): void;
6
6
  loginWithAccessToken(accessToken: string, deviceName?: string): Promise<CredentialsResult>;
7
7
  downloadTrack(opts: DownloadTrackOpts, onChunk: (chunk: Buffer) => void, onLog?: (event: LogEvent) => void): Promise<DownloadHandle>;
@@ -11,7 +11,7 @@ declare const native: {
11
11
  startConnectDeviceWithToken(accessToken: string, clientId: string | undefined, name: string, deviceId: string, onChunk: (chunk: Buffer) => void, onEvent?: (event: ConnectEvent) => void, onLog?: (event: LogEvent) => void): Promise<ConnectHandle>;
12
12
  };
13
13
  export declare function createSession(opts: CreateSessionOpts): Promise<LibrespotSession>;
14
- export declare function createSessionWithCredentials(credentialsPathOrJson: string, deviceName?: string | null): Promise<LibrespotSession>;
14
+ export declare function createSessionWithCredentials(credentialsPathOrJson: string, deviceName?: string | null, cacheDir?: string | null, cacheSizeLimitMb?: number | null): Promise<LibrespotSession>;
15
15
  export declare function loginWithAccessToken(accessToken: string, deviceName?: string): Promise<CredentialsResult>;
16
16
  export declare function startZeroconfLogin(deviceId: string, name?: string, timeoutMs?: number): Promise<CredentialsResult>;
17
17
  export declare function startConnectDevice(credentialsPath: string, name: string, deviceId: string, onChunk: (chunk: Buffer) => void, onEvent?: (event: ConnectEvent) => void, onLog?: (event: LogEvent) => void): Promise<ConnectHandle>;
package/dist/index.js CHANGED
@@ -100,12 +100,14 @@ function createSession(opts) {
100
100
  accessToken: opts.accessToken ?? opts.access_token,
101
101
  clientId: opts.clientId ?? opts.client_id,
102
102
  deviceName: opts.deviceName ?? opts.device_name,
103
+ cacheDir: opts.cacheDir,
104
+ cacheSizeLimitMb: opts.cacheSizeLimitMb,
103
105
  };
104
106
  return native.createSession(nativeOpts).then((sess) => wrapSession(sess));
105
107
  }
106
- function createSessionWithCredentials(credentialsPathOrJson, deviceName) {
108
+ function createSessionWithCredentials(credentialsPathOrJson, deviceName, cacheDir, cacheSizeLimitMb) {
107
109
  return native
108
- .createSessionWithCredentials(credentialsPathOrJson, deviceName ?? null)
110
+ .createSessionWithCredentials(credentialsPathOrJson, deviceName ?? null, cacheDir ?? null, cacheSizeLimitMb ?? null)
109
111
  .then((sess) => wrapSession(sess));
110
112
  }
111
113
  function loginWithAccessToken(accessToken, deviceName) {
package/dist/types.d.ts CHANGED
@@ -3,6 +3,12 @@ export interface CreateSessionOpts {
3
3
  accessToken?: string;
4
4
  clientId?: string;
5
5
  deviceName?: string;
6
+ /** Directory for the audio file cache. When set, decoded audio is stored so
7
+ * subsequent plays of the same track skip the CDN download. */
8
+ cacheDir?: string;
9
+ /** Maximum size of the audio cache in megabytes. Only meaningful when
10
+ * `cacheDir` is set. Omit for no limit. */
11
+ cacheSizeLimitMb?: number;
6
12
  }
7
13
  /** Options for streaming a track. */
8
14
  export interface StreamTrackOpts {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lox-audioserver/node-librespot",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Node.js bindings for librespot (Spotify Connect) via N-API with prebuild support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/index.ts CHANGED
@@ -70,6 +70,8 @@ const native = require(resolveNativeBinding()) as {
70
70
  createSessionWithCredentials(
71
71
  credentialsPath: string,
72
72
  deviceName?: string | null,
73
+ cacheDir?: string | null,
74
+ cacheSizeLimitMb?: number | null,
73
75
  ): Promise<LibrespotSession>;
74
76
  setLogLevel(level: string): void;
75
77
  loginWithAccessToken(
@@ -148,20 +150,24 @@ function wrapSession(session: LibrespotSession) {
148
150
  }
149
151
 
150
152
  export function createSession(opts: CreateSessionOpts): Promise<LibrespotSession> {
151
- const nativeOpts = {
153
+ const nativeOpts: CreateSessionOpts = {
152
154
  accessToken: (opts as any).accessToken ?? (opts as any).access_token,
153
155
  clientId: (opts as any).clientId ?? (opts as any).client_id,
154
156
  deviceName: (opts as any).deviceName ?? (opts as any).device_name,
157
+ cacheDir: opts.cacheDir,
158
+ cacheSizeLimitMb: opts.cacheSizeLimitMb,
155
159
  };
156
- return native.createSession(nativeOpts as CreateSessionOpts).then((sess) => wrapSession(sess) as any);
160
+ return native.createSession(nativeOpts).then((sess) => wrapSession(sess) as any);
157
161
  }
158
162
 
159
163
  export function createSessionWithCredentials(
160
164
  credentialsPathOrJson: string,
161
165
  deviceName?: string | null,
166
+ cacheDir?: string | null,
167
+ cacheSizeLimitMb?: number | null,
162
168
  ): Promise<LibrespotSession> {
163
169
  return native
164
- .createSessionWithCredentials(credentialsPathOrJson, deviceName ?? null)
170
+ .createSessionWithCredentials(credentialsPathOrJson, deviceName ?? null, cacheDir ?? null, cacheSizeLimitMb ?? null)
165
171
  .then((sess) => wrapSession(sess) as any);
166
172
  }
167
173
 
package/src/lib.rs CHANGED
@@ -120,6 +120,12 @@ pub struct CreateSessionOpts {
120
120
  pub access_token: Option<String>,
121
121
  pub client_id: Option<String>,
122
122
  pub device_name: Option<String>,
123
+ /// Directory for the librespot audio file cache. When set, decoded audio
124
+ /// is written here so subsequent plays avoid a CDN round-trip.
125
+ pub cache_dir: Option<String>,
126
+ /// Maximum size of the audio cache in megabytes. Only used when
127
+ /// `cache_dir` is set. None means no size limit is enforced.
128
+ pub cache_size_limit_mb: Option<u32>,
123
129
  }
124
130
 
125
131
  /// Options for streaming a track.
@@ -1515,6 +1521,29 @@ impl LibrespotSession {
1515
1521
  }
1516
1522
  }
1517
1523
 
1524
+ /// Build a librespot `Cache` for audio file storage.
1525
+ /// Returns `None` if `cache_dir` is absent, the directory cannot be created,
1526
+ /// or `Cache::new` fails — in which case streaming still works without cache.
1527
+ fn build_audio_cache(cache_dir: Option<&str>, cache_size_limit_mb: Option<u32>) -> Option<Cache> {
1528
+ let dir = cache_dir?;
1529
+ if dir.trim().is_empty() {
1530
+ return None;
1531
+ }
1532
+ let path = std::path::Path::new(dir);
1533
+ if let Err(e) = fs::create_dir_all(path) {
1534
+ eprintln!("[lox-librespot] could not create cache dir {dir}: {e}");
1535
+ return None;
1536
+ }
1537
+ let size_limit = cache_size_limit_mb.map(|mb| mb as u64 * 1024 * 1024);
1538
+ Cache::new(
1539
+ Some(path),
1540
+ None::<&std::path::Path>,
1541
+ None::<&std::path::Path>,
1542
+ size_limit,
1543
+ )
1544
+ .ok()
1545
+ }
1546
+
1518
1547
  /// Create a session using a Web API access token (client id optional via opts or env).
1519
1548
  #[napi]
1520
1549
  pub async fn create_session(opts: CreateSessionOpts) -> Result<LibrespotSession> {
@@ -1543,7 +1572,8 @@ pub async fn create_session(opts: CreateSessionOpts) -> Result<LibrespotSession>
1543
1572
  }
1544
1573
  }
1545
1574
 
1546
- let session = Session::new(session_config, None);
1575
+ let cache = build_audio_cache(opts.cache_dir.as_deref(), opts.cache_size_limit_mb);
1576
+ let session = Session::new(session_config, cache);
1547
1577
  session
1548
1578
  .connect(credentials, false)
1549
1579
  .await
@@ -1565,6 +1595,8 @@ pub async fn create_session(opts: CreateSessionOpts) -> Result<LibrespotSession>
1565
1595
  pub async fn create_session_with_credentials(
1566
1596
  credentials_path: String,
1567
1597
  device_name: Option<String>,
1598
+ cache_dir: Option<String>,
1599
+ cache_size_limit_mb: Option<u32>,
1568
1600
  ) -> Result<LibrespotSession> {
1569
1601
  if credentials_path.trim().is_empty() {
1570
1602
  return Err(Error::from_reason("credentials payload is required"));
@@ -1595,7 +1627,8 @@ pub async fn create_session_with_credentials(
1595
1627
  }
1596
1628
  }
1597
1629
 
1598
- let session = Session::new(session_config, None);
1630
+ let cache = build_audio_cache(cache_dir.as_deref(), cache_size_limit_mb);
1631
+ let session = Session::new(session_config, cache);
1599
1632
  session
1600
1633
  .connect(credentials, false)
1601
1634
  .await
package/src/types.ts CHANGED
@@ -5,6 +5,12 @@ export interface CreateSessionOpts {
5
5
  accessToken?: string;
6
6
  clientId?: string;
7
7
  deviceName?: string;
8
+ /** Directory for the audio file cache. When set, decoded audio is stored so
9
+ * subsequent plays of the same track skip the CDN download. */
10
+ cacheDir?: string;
11
+ /** Maximum size of the audio cache in megabytes. Only meaningful when
12
+ * `cacheDir` is set. Omit for no limit. */
13
+ cacheSizeLimitMb?: number;
8
14
  }
9
15
 
10
16
  /** Options for streaming a track. */