@xiboplayer/cache 0.2.0 → 0.3.1
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 +22 -90
- package/package.json +2 -2
- package/src/cache-proxy.js +8 -3
- package/src/cache.test.js +12 -2
- package/src/download-manager.js +767 -301
- package/src/download-manager.test.js +1130 -325
- package/src/index.js +3 -1
package/README.md
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
# @xiboplayer/cache
|
|
1
|
+
# @xiboplayer/cache
|
|
2
2
|
|
|
3
3
|
**Offline caching and download management with parallel chunk downloads.**
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Manages media downloads and offline storage for Xibo players:
|
|
8
8
|
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
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
|
|
12
14
|
|
|
13
15
|
## Installation
|
|
14
16
|
|
|
@@ -19,100 +21,30 @@ npm install @xiboplayer/cache
|
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
21
23
|
```javascript
|
|
22
|
-
import {
|
|
24
|
+
import { CacheProxy } from '@xiboplayer/cache';
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
await cache.initialize();
|
|
26
|
+
const cache = new CacheProxy();
|
|
27
|
+
await cache.init();
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
await downloader.downloadFile(url, { chunkSize: 1024 * 1024 });
|
|
29
|
+
// Request downloads (delegated to Service Worker)
|
|
30
|
+
await cache.requestDownload({ layoutOrder, files });
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
const
|
|
32
|
+
// Check if a file is cached
|
|
33
|
+
const isCached = await cache.has(fileId);
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## Exports
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```javascript
|
|
43
|
-
const CONCURRENT_CHUNKS = 4; // Adjust 2-6 based on network
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Cache Validation
|
|
47
|
-
|
|
48
|
-
Automatically validates cached entries:
|
|
49
|
-
- Content-Type verification
|
|
50
|
-
- Size validation (> 100 bytes)
|
|
51
|
-
- Corrupted entry detection
|
|
52
|
-
|
|
53
|
-
### Blob URL Lifecycle
|
|
54
|
-
|
|
55
|
-
Proper blob URL management prevents memory leaks:
|
|
56
|
-
- Layout-scoped tracking
|
|
57
|
-
- Automatic revocation on layout switch
|
|
58
|
-
- Media URL cleanup
|
|
59
|
-
|
|
60
|
-
## API Reference
|
|
61
|
-
|
|
62
|
-
### CacheManager
|
|
63
|
-
|
|
64
|
-
```javascript
|
|
65
|
-
class CacheManager {
|
|
66
|
-
async initialize()
|
|
67
|
-
async get(key)
|
|
68
|
-
async set(key, blob)
|
|
69
|
-
async has(key)
|
|
70
|
-
async delete(key)
|
|
71
|
-
async clear()
|
|
72
|
-
async getSize()
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### DownloadManager
|
|
77
|
-
|
|
78
|
-
```javascript
|
|
79
|
-
class DownloadManager {
|
|
80
|
-
constructor(cacheManager, options)
|
|
81
|
-
async downloadFile(url, options)
|
|
82
|
-
async downloadBatch(urls)
|
|
83
|
-
getProgress(url)
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### CacheProxy
|
|
88
|
-
|
|
89
|
-
```javascript
|
|
90
|
-
class CacheProxy {
|
|
91
|
-
constructor(cacheManager)
|
|
92
|
-
register(serviceWorkerUrl)
|
|
93
|
-
unregister()
|
|
94
|
-
update()
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Performance
|
|
99
|
-
|
|
100
|
-
| Operation | Time |
|
|
101
|
-
|-----------|------|
|
|
102
|
-
| 1GB file download | 1-2 min (vs 5 min sequential) |
|
|
103
|
-
| Cache lookup | <10ms |
|
|
104
|
-
| Cache write | <50ms |
|
|
38
|
+
| Export | Description |
|
|
39
|
+
|--------|-------------|
|
|
40
|
+
| `CacheProxy` | Browser-side proxy communicating with SW backend |
|
|
41
|
+
| `DownloadManager` | Core download queue with barrier-based ordering |
|
|
105
42
|
|
|
106
43
|
## Dependencies
|
|
107
44
|
|
|
108
|
-
- `@xiboplayer/utils`
|
|
109
|
-
|
|
110
|
-
## Related Packages
|
|
111
|
-
|
|
112
|
-
- [@xiboplayer/core](../../core/docs/) - Player core
|
|
113
|
-
- [@xiboplayer/sw](../../sw/docs/) - Service Worker toolkit
|
|
45
|
+
- `@xiboplayer/utils` — logger, events
|
|
46
|
+
- `spark-md5` — MD5 hashing for file verification
|
|
114
47
|
|
|
115
48
|
---
|
|
116
49
|
|
|
117
|
-
**
|
|
118
|
-
**Last Updated**: 2026-02-10
|
|
50
|
+
**Part of the [XiboPlayer SDK](https://github.com/xibo-players/xiboplayer)** | [MCP Server](https://github.com/xibo-players/xiboplayer/tree/main/mcp-server) for AI-assisted development
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiboplayer/cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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.1"
|
|
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
|
);
|
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 () => {
|