@xiboplayer/cache 0.1.3 → 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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @xiboplayer/cache
2
+
3
+ **Offline caching and download management with parallel chunk downloads.**
4
+
5
+ ## Overview
6
+
7
+ Manages media downloads and offline storage for Xibo players:
8
+
9
+ - **Parallel chunk downloads** — large files (100MB+) split into configurable chunks, downloaded concurrently
10
+ - **Header+trailer first** — MP4 moov atom fetched first for instant playback start before full download
11
+ - **MD5 verification** — integrity checking with CRC32-based skip optimization
12
+ - **Download queue** — flat queue with barriers for layout-ordered downloading
13
+ - **CacheProxy** — browser-side proxy that communicates with a Service Worker backend
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @xiboplayer/cache
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```javascript
24
+ import { CacheProxy } from '@xiboplayer/cache';
25
+
26
+ const cache = new CacheProxy();
27
+ await cache.init();
28
+
29
+ // Request downloads (delegated to Service Worker)
30
+ await cache.requestDownload({ layoutOrder, files });
31
+
32
+ // Check if a file is cached
33
+ const isCached = await cache.has(fileId);
34
+ ```
35
+
36
+ ## Exports
37
+
38
+ | Export | Description |
39
+ |--------|-------------|
40
+ | `CacheProxy` | Browser-side proxy communicating with SW backend |
41
+ | `DownloadManager` | Core download queue with barrier-based ordering |
42
+
43
+ ## Dependencies
44
+
45
+ - `@xiboplayer/utils` — logger, events
46
+ - `spark-md5` — MD5 hashing for file verification
47
+
48
+ ---
49
+
50
+ **Part of the [XiboPlayer SDK](https://github.com/linuxnow/xiboplayer)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiboplayer/cache",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Offline caching and download management with parallel chunk downloads",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "spark-md5": "^3.0.2",
15
- "@xiboplayer/utils": "0.1.3"
15
+ "@xiboplayer/utils": "0.3.0"
16
16
  },
17
17
  "devDependencies": {
18
18
  "vitest": "^2.0.0",
@@ -170,14 +170,19 @@ class ServiceWorkerBackend extends EventEmitter {
170
170
 
171
171
  /**
172
172
  * Request downloads from Service Worker (non-blocking)
173
- * @param {Array} files - Array of { id, type, path, md5 }
173
+ * @param {Object|Array} payload - Either { layouts: [{ layoutId, mediaFiles }] } or flat Array of files
174
174
  * @returns {Promise<void>}
175
175
  */
176
- async requestDownload(files) {
176
+ async requestDownload(payload) {
177
177
  if (!this.controller) {
178
178
  throw new Error('Service Worker not available');
179
179
  }
180
180
 
181
+ // Support both grouped and flat payload (backward compat)
182
+ const data = Array.isArray(payload)
183
+ ? { files: payload }
184
+ : payload;
185
+
181
186
  return new Promise((resolve, reject) => {
182
187
  const messageChannel = new MessageChannel();
183
188
 
@@ -195,7 +200,7 @@ class ServiceWorkerBackend extends EventEmitter {
195
200
  this.controller.postMessage(
196
201
  {
197
202
  type: 'DOWNLOAD_FILES',
198
- data: { files }
203
+ data
199
204
  },
200
205
  [messageChannel.port2]
201
206
  );
@@ -222,20 +227,13 @@ class ServiceWorkerBackend extends EventEmitter {
222
227
  }
223
228
 
224
229
  /**
225
- * Check if file is cached
230
+ * Check if file is cached (delegates to hasFile for consistent fetchReady handling)
226
231
  * @param {string} type - 'media', 'layout', 'widget'
227
232
  * @param {string} id - File ID
228
233
  * @returns {Promise<boolean>}
229
234
  */
230
235
  async isCached(type, id) {
231
- const cacheUrl = `${BASE}/cache/${type}/${id}`;
232
-
233
- try {
234
- const response = await fetch(cacheUrl, { method: 'HEAD' });
235
- return response.ok;
236
- } catch (error) {
237
- return false;
238
- }
236
+ return this.hasFile(type, id);
239
237
  }
240
238
 
241
239
  /**
@@ -384,16 +382,13 @@ export class CacheProxy extends EventEmitter {
384
382
  }
385
383
 
386
384
  /**
387
- * Check if file is cached
385
+ * Check if file is cached (delegates to hasFile for consistent fetchReady handling)
388
386
  * @param {string} type - 'media', 'layout', 'widget'
389
387
  * @param {string} id - File ID
390
388
  * @returns {Promise<boolean>}
391
389
  */
392
390
  async isCached(type, id) {
393
- if (!this.backend) {
394
- throw new Error('CacheProxy not initialized');
395
- }
396
- return await this.backend.isCached(type, id);
391
+ return this.hasFile(type, id);
397
392
  }
398
393
 
399
394
  /**
package/src/cache.test.js CHANGED
@@ -217,7 +217,7 @@ describe('CacheManager', () => {
217
217
  beforeEach(async () => {
218
218
  await manager.init();
219
219
 
220
- // Helper: create a mock blob with arrayBuffer() support
220
+ // Helper: create a mock blob with arrayBuffer()/stream() support
221
221
  function createMockBlob(content, type) {
222
222
  const blob = new Blob([content], { type });
223
223
  // Polyfill arrayBuffer() for jsdom environments that lack it
@@ -230,6 +230,15 @@ describe('CacheManager', () => {
230
230
  });
231
231
  };
232
232
  }
233
+ // Polyfill stream() for jsdom environments that lack it
234
+ if (!blob.stream) {
235
+ blob.stream = () => new ReadableStream({
236
+ start(controller) {
237
+ controller.enqueue(new TextEncoder().encode(content));
238
+ controller.close();
239
+ }
240
+ });
241
+ }
233
242
  return blob;
234
243
  }
235
244
 
@@ -516,7 +525,8 @@ describe('CacheManager', () => {
516
525
 
517
526
  const retrieved = await manager.getCachedFile('media', '1');
518
527
 
519
- expect(retrieved).toBeInstanceOf(Blob);
528
+ expect(retrieved).toBeTruthy();
529
+ expect(retrieved.size).toBeGreaterThan(0);
520
530
  });
521
531
 
522
532
  it('should return null for non-cached file', async () => {