@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 +50 -0
- package/package.json +2 -2
- package/src/cache-proxy.js +12 -17
- package/src/cache.test.js +12 -2
- package/src/download-manager.js +767 -303
- package/src/download-manager.test.js +1130 -325
- package/src/index.js +3 -1
- package/docs/README.md +0 -118
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.
|
|
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.
|
|
15
|
+
"@xiboplayer/utils": "0.3.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"vitest": "^2.0.0",
|
package/src/cache-proxy.js
CHANGED
|
@@ -170,14 +170,19 @@ class ServiceWorkerBackend extends EventEmitter {
|
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Request downloads from Service Worker (non-blocking)
|
|
173
|
-
* @param {Array}
|
|
173
|
+
* @param {Object|Array} payload - Either { layouts: [{ layoutId, mediaFiles }] } or flat Array of files
|
|
174
174
|
* @returns {Promise<void>}
|
|
175
175
|
*/
|
|
176
|
-
async requestDownload(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
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 () => {
|