@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/CAMPAIGNS.md +254 -0
- package/README.md +163 -0
- package/TESTING_STATUS.md +281 -0
- package/TEST_STANDARDIZATION_COMPLETE.md +287 -0
- package/docs/ARCHITECTURE.md +714 -0
- package/docs/README.md +92 -0
- package/examples/dayparting-schedule-example.json +190 -0
- package/index.html +262 -0
- package/package.json +53 -0
- package/proxy.js +72 -0
- package/public/manifest.json +22 -0
- package/public/sw.js +218 -0
- package/setup.html +220 -0
- package/src/data-connectors.js +198 -0
- package/src/index.js +4 -0
- package/src/main.js +580 -0
- package/src/player-core.js +1120 -0
- package/src/player-core.test.js +1796 -0
- package/src/state.js +54 -0
- package/src/state.test.js +206 -0
- package/src/test-utils.js +217 -0
- package/src/xmds-test.html +109 -0
- package/src/xmds.test.js +516 -0
- package/vite.config.js +51 -0
- package/vitest.config.js +35 -0
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">×</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
|
+
}
|