@xiboplayer/core 0.1.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/docs/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # @xiboplayer/core Documentation
2
+
3
+ **Player core orchestration and lifecycle management.**
4
+
5
+ ## 📖 Contents
6
+
7
+ - [ARCHITECTURE.md](ARCHITECTURE.md) - Core system architecture and design
8
+
9
+ ## Overview
10
+
11
+ The `@xiboplayer/core` package provides the foundational player logic:
12
+
13
+ - **Player lifecycle** - Initialization, start, stop, restart
14
+ - **Module orchestration** - Coordinates renderer, cache, schedule, XMDS
15
+ - **Event system** - Pub/sub event bus for inter-module communication
16
+ - **Configuration** - Player configuration management
17
+ - **Error handling** - Centralized error management
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @xiboplayer/core
23
+ ```
24
+
25
+ ## Basic Usage
26
+
27
+ ```javascript
28
+ import { PlayerCore } from '@xiboplayer/core';
29
+
30
+ const player = new PlayerCore({
31
+ cmsUrl: 'https://cms.example.com',
32
+ displayId: 123,
33
+ hardwareKey: 'abc123'
34
+ });
35
+
36
+ await player.initialize();
37
+ player.start();
38
+ ```
39
+
40
+ ## Architecture
41
+
42
+ See: [ARCHITECTURE.md](ARCHITECTURE.md) for detailed system design.
43
+
44
+ ### Key Components
45
+
46
+ - **PlayerCore** - Main orchestrator
47
+ - **EventEmitter** - Event bus implementation
48
+ - **Config** - Configuration management
49
+ - **Logger** - Structured logging
50
+
51
+ ## API Reference
52
+
53
+ ### PlayerCore
54
+
55
+ ```javascript
56
+ class PlayerCore {
57
+ constructor(config)
58
+ async initialize()
59
+ start()
60
+ stop()
61
+ restart()
62
+ on(event, callback)
63
+ emit(event, data)
64
+ }
65
+ ```
66
+
67
+ ### Events
68
+
69
+ - `player:ready` - Player initialized and ready
70
+ - `player:error` - Player encountered error
71
+ - `layout:start` - Layout playback started
72
+ - `layout:end` - Layout playback ended
73
+ - `media:download` - Media file downloaded
74
+
75
+ ## Dependencies
76
+
77
+ - `@xiboplayer/utils` - Shared utilities
78
+ - `@xiboplayer/xmds` - CMS communication (peer)
79
+ - `@xiboplayer/cache` - Offline storage (peer)
80
+ - `@xiboplayer/renderer` - Layout rendering (peer)
81
+ - `@xiboplayer/schedule` - Campaign scheduling (peer)
82
+
83
+ ## Related Packages
84
+
85
+ - [@xiboplayer/renderer](../../renderer/docs/) - Rendering engine
86
+ - [@xiboplayer/cache](../../cache/docs/) - Cache management
87
+ - [@xiboplayer/schedule](../../schedule/docs/) - Scheduling logic
88
+
89
+ ---
90
+
91
+ **Package Version**: 1.0.0
92
+ **Last Updated**: 2026-02-10
@@ -0,0 +1,190 @@
1
+ {
2
+ "description": "Example schedule demonstrating dayparting (recurring schedules) functionality",
3
+ "note": "This schedule shows realistic dayparting scenarios for a digital signage display",
4
+
5
+ "schedule": {
6
+ "default": "0",
7
+
8
+ "layouts": [
9
+ {
10
+ "description": "Main menu - Weekday business hours",
11
+ "file": "100",
12
+ "priority": 5,
13
+ "fromdt": "2025-01-30T09:00:00Z",
14
+ "todt": "2025-01-30T17:00:00Z",
15
+ "recurrenceType": "Week",
16
+ "recurrenceRepeatsOn": "1,2,3,4,5",
17
+ "comment": "Shows Monday-Friday, 9am-5pm. Base content during business hours."
18
+ },
19
+ {
20
+ "description": "Weekend menu",
21
+ "file": "101",
22
+ "priority": 5,
23
+ "fromdt": "2025-01-30T10:00:00Z",
24
+ "todt": "2025-01-30T18:00:00Z",
25
+ "recurrenceType": "Week",
26
+ "recurrenceRepeatsOn": "6,7",
27
+ "comment": "Shows Saturday-Sunday, 10am-6pm. Different hours on weekends."
28
+ },
29
+ {
30
+ "description": "Lunch specials - Mon/Wed/Fri only",
31
+ "file": "200",
32
+ "priority": 10,
33
+ "fromdt": "2025-01-30T12:00:00Z",
34
+ "todt": "2025-01-30T14:00:00Z",
35
+ "recurrenceType": "Week",
36
+ "recurrenceRepeatsOn": "1,3,5",
37
+ "comment": "Higher priority. Overrides main menu during lunch on Mon/Wed/Fri."
38
+ },
39
+ {
40
+ "description": "Night mode - After hours",
41
+ "file": "300",
42
+ "priority": 3,
43
+ "fromdt": "2025-01-30T18:00:00Z",
44
+ "todt": "2025-01-30T08:00:00Z",
45
+ "recurrenceType": "Week",
46
+ "recurrenceRepeatsOn": "1,2,3,4,5,6,7",
47
+ "comment": "Crosses midnight. Shows 6pm-8am daily. Lower priority than business hours."
48
+ },
49
+ {
50
+ "description": "Tuesday special promotion",
51
+ "file": "400",
52
+ "priority": 15,
53
+ "fromdt": "2025-01-30T09:00:00Z",
54
+ "todt": "2025-01-30T17:00:00Z",
55
+ "recurrenceType": "Week",
56
+ "recurrenceRepeatsOn": "2",
57
+ "comment": "Highest priority. Takes over entire Tuesday."
58
+ },
59
+ {
60
+ "description": "Year-end sale - Limited duration recurring",
61
+ "file": "500",
62
+ "priority": 12,
63
+ "fromdt": "2025-01-30T10:00:00Z",
64
+ "todt": "2025-01-30T20:00:00Z",
65
+ "recurrenceType": "Week",
66
+ "recurrenceRepeatsOn": "6,7",
67
+ "recurrenceRange": "2025-12-31T23:59:59Z",
68
+ "comment": "Recurring on weekends, but only until end of 2025."
69
+ }
70
+ ],
71
+
72
+ "campaigns": [
73
+ {
74
+ "description": "Morning news campaign - Weekday mornings",
75
+ "id": "1",
76
+ "priority": 8,
77
+ "fromdt": "2025-01-30T08:00:00Z",
78
+ "todt": "2025-01-30T10:00:00Z",
79
+ "recurrenceType": "Week",
80
+ "recurrenceRepeatsOn": "1,2,3,4,5",
81
+ "layouts": [
82
+ { "file": "600", "comment": "News headlines" },
83
+ { "file": "601", "comment": "Weather forecast" },
84
+ { "file": "602", "comment": "Traffic updates" }
85
+ ],
86
+ "comment": "Cycles through 3 layouts during morning hours on weekdays."
87
+ },
88
+ {
89
+ "description": "Happy hour campaign - Thurs-Fri evenings",
90
+ "id": "2",
91
+ "priority": 11,
92
+ "fromdt": "2025-01-30T17:00:00Z",
93
+ "todt": "2025-01-30T19:00:00Z",
94
+ "recurrenceType": "Week",
95
+ "recurrenceRepeatsOn": "4,5",
96
+ "layouts": [
97
+ { "file": "700", "comment": "Drink specials" },
98
+ { "file": "701", "comment": "Appetizer deals" }
99
+ ],
100
+ "comment": "Shows on Thursday and Friday evenings only."
101
+ }
102
+ ]
103
+ },
104
+
105
+ "scenarios": {
106
+ "monday_noon": {
107
+ "time": "Monday, 12:30 PM",
108
+ "active_schedules": [
109
+ "file 200 (Lunch specials - priority 10)"
110
+ ],
111
+ "explanation": "Lunch special has higher priority than main menu"
112
+ },
113
+
114
+ "monday_2pm": {
115
+ "time": "Monday, 2:00 PM",
116
+ "active_schedules": [
117
+ "file 100 (Main menu - priority 5)"
118
+ ],
119
+ "explanation": "After lunch hours, back to main menu"
120
+ },
121
+
122
+ "tuesday_10am": {
123
+ "time": "Tuesday, 10:00 AM",
124
+ "active_schedules": [
125
+ "file 400 (Tuesday special - priority 15)"
126
+ ],
127
+ "explanation": "Tuesday special has highest priority, takes over all day"
128
+ },
129
+
130
+ "wednesday_noon": {
131
+ "time": "Wednesday, 12:30 PM",
132
+ "active_schedules": [
133
+ "file 200 (Lunch specials - priority 10)"
134
+ ],
135
+ "explanation": "Lunch special active on Mon/Wed/Fri"
136
+ },
137
+
138
+ "thursday_5pm": {
139
+ "time": "Thursday, 5:30 PM",
140
+ "active_schedules": [
141
+ "campaign 2 (Happy hour - priority 11)",
142
+ "layouts: [700, 701]"
143
+ ],
144
+ "explanation": "Happy hour campaign active Thu-Fri evenings"
145
+ },
146
+
147
+ "friday_9am": {
148
+ "time": "Friday, 9:00 AM",
149
+ "active_schedules": [
150
+ "campaign 1 (Morning news - priority 8)"
151
+ ],
152
+ "explanation": "Morning news campaign during 8-10am on weekdays"
153
+ },
154
+
155
+ "saturday_2pm": {
156
+ "time": "Saturday, 2:00 PM",
157
+ "active_schedules": [
158
+ "file 500 (Year-end sale - priority 12)",
159
+ "file 101 (Weekend menu - priority 5)"
160
+ ],
161
+ "explanation": "Year-end sale wins (priority 12 > 5)"
162
+ },
163
+
164
+ "sunday_10pm": {
165
+ "time": "Sunday, 10:00 PM",
166
+ "active_schedules": [
167
+ "file 300 (Night mode - priority 3)"
168
+ ],
169
+ "explanation": "After weekend hours, shows night mode"
170
+ },
171
+
172
+ "monday_11pm": {
173
+ "time": "Monday, 11:00 PM",
174
+ "active_schedules": [
175
+ "file 300 (Night mode - priority 3)"
176
+ ],
177
+ "explanation": "Night mode handles midnight crossing (6pm-8am)"
178
+ }
179
+ },
180
+
181
+ "priority_notes": [
182
+ "Priority 15: Tuesday special (highest)",
183
+ "Priority 12: Year-end sale",
184
+ "Priority 11: Happy hour campaign",
185
+ "Priority 10: Lunch specials",
186
+ "Priority 8: Morning news campaign",
187
+ "Priority 5: Main/weekend menus",
188
+ "Priority 3: Night mode (lowest active)"
189
+ ]
190
+ }
package/index.html ADDED
@@ -0,0 +1,262 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Xibo Player</title>
7
+ <link rel="manifest" href="/manifest.json">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ html, body {
16
+ width: 100%;
17
+ height: 100%;
18
+ overflow: hidden;
19
+ background-color: #000;
20
+ font-family: sans-serif;
21
+ }
22
+
23
+ #layout-container {
24
+ position: absolute;
25
+ top: 0;
26
+ left: 0;
27
+ width: 100%;
28
+ height: 100%;
29
+ overflow: hidden;
30
+ }
31
+
32
+ #message {
33
+ position: absolute;
34
+ top: 50%;
35
+ left: 50%;
36
+ transform: translate(-50%, -50%);
37
+ padding: 20px 40px;
38
+ background: rgba(0, 0, 0, 0.9);
39
+ color: white;
40
+ font-size: 24px;
41
+ text-align: center;
42
+ border-radius: 8px;
43
+ display: none;
44
+ z-index: 9999;
45
+ }
46
+
47
+ #download-progress {
48
+ display: none;
49
+ position: fixed;
50
+ bottom: 20px;
51
+ right: 20px;
52
+ background: rgba(0,0,0,0.95);
53
+ padding: 16px;
54
+ border-radius: 8px;
55
+ min-width: 320px;
56
+ z-index: 10000;
57
+ box-shadow: 0 4px 12px rgba(0,0,0,0.5);
58
+ }
59
+
60
+ #download-progress .filename {
61
+ font-size: 14px;
62
+ margin-bottom: 8px;
63
+ color: #ccc;
64
+ white-space: nowrap;
65
+ overflow: hidden;
66
+ text-overflow: ellipsis;
67
+ }
68
+
69
+ #download-progress .progress-bar {
70
+ width: 100%;
71
+ height: 6px;
72
+ background: #333;
73
+ border-radius: 3px;
74
+ overflow: hidden;
75
+ margin-bottom: 6px;
76
+ }
77
+
78
+ #download-progress .progress-fill {
79
+ height: 100%;
80
+ background: linear-gradient(90deg, #4CAF50, #66BB6A);
81
+ transition: width 0.3s ease;
82
+ border-radius: 3px;
83
+ }
84
+
85
+ #download-progress .progress-text {
86
+ font-size: 12px;
87
+ color: #999;
88
+ display: flex;
89
+ justify-content: space-between;
90
+ }
91
+
92
+ #network-panel {
93
+ display: none;
94
+ position: fixed;
95
+ top: 50%;
96
+ left: 50%;
97
+ transform: translate(-50%, -50%);
98
+ background: rgba(0,0,0,0.98);
99
+ padding: 24px;
100
+ border-radius: 12px;
101
+ width: 90%;
102
+ max-width: 800px;
103
+ max-height: 80%;
104
+ overflow-y: auto;
105
+ z-index: 10001;
106
+ box-shadow: 0 8px 32px rgba(0,0,0,0.9);
107
+ border: 1px solid #333;
108
+ }
109
+
110
+ #network-panel h2 {
111
+ margin: 0 0 16px 0;
112
+ font-size: 18px;
113
+ border-bottom: 1px solid #444;
114
+ padding-bottom: 12px;
115
+ color: #fff;
116
+ }
117
+
118
+ #network-panel .close {
119
+ position: absolute;
120
+ top: 16px;
121
+ right: 20px;
122
+ background: none;
123
+ border: none;
124
+ color: #999;
125
+ font-size: 28px;
126
+ cursor: pointer;
127
+ padding: 0;
128
+ line-height: 1;
129
+ }
130
+
131
+ #network-panel .close:hover {
132
+ color: white;
133
+ }
134
+
135
+ #network-panel .activity-list {
136
+ list-style: none;
137
+ padding: 0;
138
+ margin: 0;
139
+ font-size: 13px;
140
+ font-family: monospace;
141
+ }
142
+
143
+ #network-panel .activity-list li {
144
+ padding: 10px;
145
+ border-bottom: 1px solid #222;
146
+ display: grid;
147
+ grid-template-columns: 80px 1fr 100px 80px;
148
+ gap: 12px;
149
+ align-items: center;
150
+ }
151
+
152
+ #network-panel .activity-list li:hover {
153
+ background: rgba(255,255,255,0.05);
154
+ }
155
+
156
+ #network-panel .time {
157
+ color: #666;
158
+ font-size: 11px;
159
+ }
160
+
161
+ #network-panel .file {
162
+ color: #fff;
163
+ overflow: hidden;
164
+ text-overflow: ellipsis;
165
+ white-space: nowrap;
166
+ }
167
+
168
+ #network-panel .size {
169
+ color: #999;
170
+ text-align: right;
171
+ font-size: 11px;
172
+ }
173
+
174
+ #network-panel .status {
175
+ padding: 4px 8px;
176
+ border-radius: 4px;
177
+ font-size: 10px;
178
+ font-weight: bold;
179
+ text-align: center;
180
+ text-transform: uppercase;
181
+ }
182
+
183
+ #network-panel .status.success {
184
+ background: #2e7d32;
185
+ color: white;
186
+ }
187
+
188
+ #network-panel .status.error {
189
+ background: #c62828;
190
+ color: white;
191
+ }
192
+
193
+ #network-panel .status.loading {
194
+ background: #1976d2;
195
+ color: white;
196
+ }
197
+
198
+ #network-panel .empty {
199
+ text-align: center;
200
+ padding: 40px;
201
+ color: #666;
202
+ }
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div id="layout-container"></div>
207
+ <div id="message"></div>
208
+
209
+ <!-- Download Progress Indicator -->
210
+ <div id="download-progress">
211
+ <div class="filename" id="progress-filename">Downloading...</div>
212
+ <div class="progress-bar">
213
+ <div class="progress-fill" id="progress-fill" style="width: 0%"></div>
214
+ </div>
215
+ <div class="progress-text">
216
+ <span id="progress-percent">0%</span>
217
+ <span id="progress-size">0 / 0 MB</span>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- Network Activity Panel (Ctrl+N to toggle) -->
222
+ <div id="network-panel">
223
+ <button class="close" id="close-network">&times;</button>
224
+ <h2>📡 Network Activity</h2>
225
+ <ul class="activity-list" id="activity-list">
226
+ <li class="empty">No network activity yet</li>
227
+ </ul>
228
+ </div>
229
+
230
+ <script type="module" src="/src/main.js"></script>
231
+ <script>
232
+ // Register service worker
233
+ if ('serviceWorker' in navigator) {
234
+ navigator.serviceWorker.register('/player/sw.js', { scope: '/player/' })
235
+ .then(reg => {
236
+ console.log('[SW] Registered:', reg.scope);
237
+ console.log('[SW] State:', reg.active ? reg.active.state : 'installing');
238
+
239
+ // Force update on page load to get latest SW
240
+ reg.update();
241
+
242
+ // Log when SW updates
243
+ reg.addEventListener('updatefound', () => {
244
+ console.log('[SW] Update found, installing new version...');
245
+ });
246
+ })
247
+ .catch(err => console.error('[SW] Registration failed:', err));
248
+
249
+ // Check if SW is controlling this page
250
+ navigator.serviceWorker.ready.then(reg => {
251
+ if (navigator.serviceWorker.controller) {
252
+ console.log('[SW] Controlling page:', navigator.serviceWorker.controller.scriptURL);
253
+ } else {
254
+ console.warn('[SW] NOT controlling page - hard refresh may be needed');
255
+ }
256
+ });
257
+ } else {
258
+ console.warn('[SW] Service workers not supported');
259
+ }
260
+ </script>
261
+ </body>
262
+ </html>
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@xiboplayer/core",
3
+ "version": "0.1.0",
4
+ "description": "Xibo Player core orchestration and lifecycle management",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./player-core": "./src/player-core.js"
10
+ },
11
+ "dependencies": {
12
+ "@xibosignage/xibo-communication-framework": "^0.0.6",
13
+ "nanoevents": "^9.1.0",
14
+ "@xiboplayer/utils": "0.1.0"
15
+ },
16
+ "peerDependencies": {
17
+ "@xiboplayer/renderer": "0.1.0",
18
+ "@xiboplayer/cache": "0.1.0",
19
+ "@xiboplayer/xmds": "0.1.0",
20
+ "@xiboplayer/schedule": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "vite": "^7.3.1",
24
+ "vitest": "^2.0.0",
25
+ "jsdom": "^25.0.0",
26
+ "@vitest/ui": "^2.0.0",
27
+ "@vitest/coverage-v8": "^2.0.0"
28
+ },
29
+ "keywords": [
30
+ "xibo",
31
+ "digital-signage",
32
+ "player",
33
+ "core",
34
+ "orchestration"
35
+ ],
36
+ "author": "Pau Aliagas <linuxnow@gmail.com>",
37
+ "license": "AGPL-3.0-or-later",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/xibo-players/xiboplayer.git",
41
+ "directory": "packages/core"
42
+ },
43
+ "scripts": {
44
+ "dev": "vite",
45
+ "build": "vite build",
46
+ "preview": "vite preview",
47
+ "proxy": "node proxy.js",
48
+ "test": "vitest run",
49
+ "test:watch": "vitest",
50
+ "test:ui": "vitest --ui",
51
+ "test:coverage": "vitest run --coverage"
52
+ }
53
+ }
package/proxy.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Simple CORS proxy for development
3
+ * Usage: CMS_URL=https://your-cms node proxy.js
4
+ * Then set CMS address to http://localhost:8080 in the player
5
+ */
6
+
7
+ import http from 'http';
8
+ import https from 'https';
9
+ import { URL } from 'url';
10
+
11
+ const ACTUAL_CMS = process.env.CMS_URL || 'http://your-cms-address';
12
+ const PORT = 8080;
13
+
14
+ const server = http.createServer(async (req, res) => {
15
+ console.log(`[Proxy] ${req.method} ${req.url}`);
16
+
17
+ // Handle OPTIONS preflight
18
+ if (req.method === 'OPTIONS') {
19
+ res.writeHead(204, {
20
+ 'Access-Control-Allow-Origin': '*',
21
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
22
+ 'Access-Control-Allow-Headers': 'Content-Type'
23
+ });
24
+ res.end();
25
+ return;
26
+ }
27
+
28
+ const targetUrl = new URL(ACTUAL_CMS + req.url);
29
+ const isHttps = targetUrl.protocol === 'https:';
30
+
31
+ console.log(`[Proxy] Forwarding to ${targetUrl.href}`);
32
+
33
+ // Choose http or https module
34
+ const httpModule = isHttps ? https : http;
35
+
36
+ // Forward request to actual CMS
37
+ const options = {
38
+ method: req.method,
39
+ hostname: targetUrl.hostname,
40
+ port: targetUrl.port || (isHttps ? 443 : 80),
41
+ path: targetUrl.pathname + targetUrl.search,
42
+ headers: {
43
+ ...req.headers,
44
+ host: targetUrl.host
45
+ }
46
+ };
47
+
48
+ const proxyReq = httpModule.request(options, (proxyRes) => {
49
+ // Add CORS headers
50
+ res.writeHead(proxyRes.statusCode, {
51
+ ...proxyRes.headers,
52
+ 'Access-Control-Allow-Origin': '*',
53
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
54
+ 'Access-Control-Allow-Headers': 'Content-Type'
55
+ });
56
+ proxyRes.pipe(res);
57
+ });
58
+
59
+ proxyReq.on('error', (err) => {
60
+ console.error('[Proxy] Error:', err);
61
+ res.writeHead(500);
62
+ res.end('Proxy error');
63
+ });
64
+
65
+ req.pipe(proxyReq);
66
+ });
67
+
68
+ server.listen(PORT, () => {
69
+ console.log(`[Proxy] Running on http://localhost:${PORT}`);
70
+ console.log(`[Proxy] Forwarding to ${ACTUAL_CMS}`);
71
+ console.log('\nIn the player, set CMS address to: http://localhost:8080');
72
+ });
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "Xibo Player",
3
+ "short_name": "Xibo",
4
+ "description": "Free Xibo-compatible digital signage player",
5
+ "start_url": "/player/",
6
+ "display": "fullscreen",
7
+ "orientation": "any",
8
+ "background_color": "#000000",
9
+ "theme_color": "#000000",
10
+ "icons": [
11
+ {
12
+ "src": "/icon-192.png",
13
+ "sizes": "192x192",
14
+ "type": "image/png"
15
+ },
16
+ {
17
+ "src": "/icon-512.png",
18
+ "sizes": "512x512",
19
+ "type": "image/png"
20
+ }
21
+ ]
22
+ }