@tomorrowos/sdk 0.4.4 → 0.4.6
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/dist/media-probe.d.ts +3 -2
- package/dist/media-probe.d.ts.map +1 -1
- package/dist/media-probe.js +112 -38
- package/dist/store/types.d.ts +4 -0
- package/dist/store/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/templates/cms-starter/package.json +3 -2
- package/templates/cms-starter/public/index.html +9 -17
- package/templates/cms-starter/public/methods.js +18 -13
package/dist/media-probe.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Video duration from file bytes (no ffprobe).
|
|
3
|
+
* MP4/MOV: ISO BMFF box walk (moov/mvhd only — never scans inside mdat).
|
|
4
|
+
* WebM: Duration element in the file header region only.
|
|
4
5
|
*/
|
|
5
6
|
export declare function probeVideoDurationMs(body: Buffer, filename: string): number | null;
|
|
6
7
|
//# sourceMappingURL=media-probe.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-probe.d.ts","sourceRoot":"","sources":["../src/media-probe.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"media-probe.d.ts","sourceRoot":"","sources":["../src/media-probe.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2IH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,MAAM,GAAG,IAAI,CAOf"}
|
package/dist/media-probe.js
CHANGED
|
@@ -1,54 +1,133 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Video duration from file bytes (no ffprobe).
|
|
3
|
+
* MP4/MOV: ISO BMFF box walk (moov/mvhd only — never scans inside mdat).
|
|
4
|
+
* WebM: Duration element in the file header region only.
|
|
4
5
|
*/
|
|
5
6
|
function isVideoFilename(name) {
|
|
6
7
|
const lower = String(name || "").toLowerCase();
|
|
7
8
|
return /\.(mp4|m4v|mov|webm|mkv|avi)$/.test(lower);
|
|
8
9
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
function clampDurationMs(ms) {
|
|
11
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
12
|
+
return 0;
|
|
13
|
+
return Math.min(3600 * 1000, Math.max(1000, Math.round(ms)));
|
|
14
|
+
}
|
|
15
|
+
function readBoxBounds(buf, offset) {
|
|
16
|
+
if (offset + 8 > buf.length)
|
|
17
|
+
return null;
|
|
18
|
+
let size = buf.readUInt32BE(offset);
|
|
19
|
+
const type = buf.toString("ascii", offset + 4, offset + 8);
|
|
20
|
+
let headerSize = 8;
|
|
21
|
+
if (size === 1) {
|
|
22
|
+
if (offset + 16 > buf.length)
|
|
23
|
+
return null;
|
|
24
|
+
const sizeBig = buf.readBigUInt64BE(offset + 8);
|
|
25
|
+
if (sizeBig < BigInt(headerSize))
|
|
26
|
+
return null;
|
|
27
|
+
if (sizeBig > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
28
|
+
// Box larger than JS can index; still validate against buffer length.
|
|
29
|
+
const endBig = BigInt(offset) + sizeBig;
|
|
30
|
+
if (endBig > BigInt(buf.length))
|
|
31
|
+
return null;
|
|
32
|
+
size = buf.length - offset;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
size = Number(sizeBig);
|
|
36
|
+
}
|
|
37
|
+
headerSize = 16;
|
|
38
|
+
}
|
|
39
|
+
else if (size === 0) {
|
|
40
|
+
size = buf.length - offset;
|
|
41
|
+
}
|
|
42
|
+
if (size < headerSize)
|
|
43
|
+
return null;
|
|
44
|
+
const end = offset + size;
|
|
45
|
+
if (end > buf.length)
|
|
46
|
+
return null;
|
|
47
|
+
return { type, contentStart: offset + headerSize, end };
|
|
48
|
+
}
|
|
49
|
+
function parseMvhdDurationMs(buf, contentStart, boxEnd) {
|
|
50
|
+
if (contentStart + 4 > boxEnd)
|
|
51
|
+
return null;
|
|
52
|
+
const version = buf[contentStart];
|
|
53
|
+
if (version === 0) {
|
|
54
|
+
if (contentStart + 28 > boxEnd)
|
|
55
|
+
return null;
|
|
56
|
+
const timescale = buf.readUInt32BE(contentStart + 20);
|
|
57
|
+
const duration = buf.readUInt32BE(contentStart + 24);
|
|
58
|
+
if (timescale <= 0 || duration <= 0)
|
|
59
|
+
return null;
|
|
60
|
+
return (duration / timescale) * 1000;
|
|
61
|
+
}
|
|
62
|
+
if (version === 1) {
|
|
63
|
+
if (contentStart + 40 > boxEnd)
|
|
64
|
+
return null;
|
|
65
|
+
const timescale = buf.readUInt32BE(contentStart + 28);
|
|
66
|
+
const durationHi = buf.readUInt32BE(contentStart + 32);
|
|
67
|
+
const durationLo = buf.readUInt32BE(contentStart + 36);
|
|
68
|
+
const duration = durationHi * 2 ** 32 + durationLo;
|
|
69
|
+
if (timescale <= 0 || duration <= 0)
|
|
70
|
+
return null;
|
|
71
|
+
return (duration / timescale) * 1000;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function findMvhdDurationInContainer(buf, start, end) {
|
|
76
|
+
let offset = start;
|
|
77
|
+
while (offset + 8 <= end) {
|
|
78
|
+
const box = readBoxBounds(buf, offset);
|
|
79
|
+
if (!box || box.end > end)
|
|
14
80
|
break;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
81
|
+
if (box.type === "mvhd") {
|
|
82
|
+
const ms = parseMvhdDurationMs(buf, box.contentStart, box.end);
|
|
83
|
+
if (ms != null)
|
|
84
|
+
return ms;
|
|
19
85
|
}
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
86
|
+
// moov/trak/mdia/minf/stbl can nest further boxes; only recurse into containers.
|
|
87
|
+
if (box.type === "moov" ||
|
|
88
|
+
box.type === "trak" ||
|
|
89
|
+
box.type === "mdia" ||
|
|
90
|
+
box.type === "minf" ||
|
|
91
|
+
box.type === "stbl") {
|
|
92
|
+
const nested = findMvhdDurationInContainer(buf, box.contentStart, box.end);
|
|
93
|
+
if (nested != null)
|
|
94
|
+
return nested;
|
|
27
95
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
96
|
+
offset = box.end;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
/** Walk top-level ISO boxes; skip mdat without scanning its payload. */
|
|
101
|
+
function probeMp4DurationMs(buf) {
|
|
102
|
+
let offset = 0;
|
|
103
|
+
while (offset + 8 <= buf.length) {
|
|
104
|
+
const box = readBoxBounds(buf, offset);
|
|
105
|
+
if (!box)
|
|
106
|
+
break;
|
|
107
|
+
if (box.type === "moov") {
|
|
108
|
+
const ms = findMvhdDurationInContainer(buf, box.contentStart, box.end);
|
|
109
|
+
if (ms != null)
|
|
110
|
+
return clampDurationMs(ms);
|
|
34
111
|
}
|
|
35
|
-
|
|
112
|
+
offset = box.end;
|
|
36
113
|
}
|
|
37
114
|
return null;
|
|
38
115
|
}
|
|
116
|
+
const WEBM_HEADER_PROBE_BYTES = 512 * 1024;
|
|
39
117
|
function probeWebmDurationMs(buf) {
|
|
118
|
+
const header = buf.subarray(0, Math.min(buf.length, WEBM_HEADER_PROBE_BYTES));
|
|
40
119
|
const marker = Buffer.from([0x44, 0x89]);
|
|
41
120
|
let searchFrom = 0;
|
|
42
|
-
while (searchFrom <
|
|
43
|
-
const idx =
|
|
121
|
+
while (searchFrom < header.length - 12) {
|
|
122
|
+
const idx = header.indexOf(marker, searchFrom);
|
|
44
123
|
if (idx < 0)
|
|
45
124
|
break;
|
|
46
125
|
const sizePos = idx + 2;
|
|
47
|
-
if (sizePos + 9 >
|
|
126
|
+
if (sizePos + 9 > header.length)
|
|
48
127
|
break;
|
|
49
|
-
const size =
|
|
50
|
-
if (size === 0x84 && sizePos + 9 <=
|
|
51
|
-
const duration =
|
|
128
|
+
const size = header[sizePos];
|
|
129
|
+
if (size === 0x84 && sizePos + 9 <= header.length) {
|
|
130
|
+
const duration = header.readFloatBE(sizePos + 1);
|
|
52
131
|
if (Number.isFinite(duration) && duration > 0) {
|
|
53
132
|
return clampDurationMs(duration);
|
|
54
133
|
}
|
|
@@ -57,17 +136,12 @@ function probeWebmDurationMs(buf) {
|
|
|
57
136
|
}
|
|
58
137
|
return null;
|
|
59
138
|
}
|
|
60
|
-
function clampDurationMs(ms) {
|
|
61
|
-
if (!Number.isFinite(ms) || ms <= 0)
|
|
62
|
-
return 0;
|
|
63
|
-
return Math.min(3600 * 1000, Math.max(1000, Math.round(ms)));
|
|
64
|
-
}
|
|
65
139
|
export function probeVideoDurationMs(body, filename) {
|
|
66
140
|
if (!body?.length || !isVideoFilename(filename))
|
|
67
141
|
return null;
|
|
68
142
|
const lower = filename.toLowerCase();
|
|
69
143
|
if (lower.endsWith(".webm")) {
|
|
70
|
-
return probeWebmDurationMs(body) ??
|
|
144
|
+
return probeWebmDurationMs(body) ?? probeMp4DurationMs(body);
|
|
71
145
|
}
|
|
72
|
-
return
|
|
146
|
+
return probeMp4DurationMs(body) ?? probeWebmDurationMs(body);
|
|
73
147
|
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -34,11 +34,15 @@ export interface PlaylistItemRecord {
|
|
|
34
34
|
type?: string;
|
|
35
35
|
durationMs?: number;
|
|
36
36
|
}
|
|
37
|
+
/** Device-local run window: startDate+start → endDate+end (one continuous period, not daily repeat). */
|
|
37
38
|
export interface PlaylistSchedule {
|
|
38
39
|
startDate?: string;
|
|
39
40
|
endDate?: string;
|
|
41
|
+
/** @deprecated Ignored by player; kept for backward compatibility in stored JSON. */
|
|
40
42
|
daysOfWeek?: number[];
|
|
43
|
+
/** HH:mm — combined with startDate for run-from datetime. */
|
|
41
44
|
start?: string;
|
|
45
|
+
/** HH:mm — combined with endDate for run-until datetime (exclusive). */
|
|
42
46
|
end?: string;
|
|
43
47
|
}
|
|
44
48
|
export interface StoredPlaylist {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "TomorrowOS CMS server SDK
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -41,3 +41,4 @@
|
|
|
41
41
|
},
|
|
42
42
|
"license": "Apache-2.0"
|
|
43
43
|
}
|
|
44
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-cms",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"build-player": "tomorrowos build --platform tizen"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tomorrowos/sdk": "^0.4.
|
|
13
|
+
"@tomorrowos/sdk": "^0.4.6"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^20.0.0",
|
|
@@ -18,3 +18,4 @@
|
|
|
18
18
|
"typescript": "^5.5.0"
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
|
|
@@ -66,35 +66,27 @@
|
|
|
66
66
|
<h3 class="subheading">When this playlist plays</h3>
|
|
67
67
|
<p class="hint">
|
|
68
68
|
Leave blank for always on. Uses the <strong>TV/device local clock</strong> (not CMS server time).
|
|
69
|
-
|
|
69
|
+
Schedule is one continuous run: <strong>from date/time → until date/time</strong>
|
|
70
|
+
(e.g. May 5 13:00 through May 8 14:00), not a daily repeat inside those dates.
|
|
71
|
+
“Until” time stops at that moment (exclusive).
|
|
70
72
|
</p>
|
|
71
73
|
<div class="schedule-grid">
|
|
72
74
|
<label>
|
|
73
|
-
|
|
75
|
+
Run from (date)
|
|
74
76
|
<input type="date" id="scheduleStartDate" />
|
|
75
77
|
</label>
|
|
76
78
|
<label>
|
|
77
|
-
|
|
78
|
-
<input type="
|
|
79
|
+
Run from (time)
|
|
80
|
+
<input type="time" id="scheduleStartTime" />
|
|
79
81
|
</label>
|
|
80
82
|
<label>
|
|
81
|
-
|
|
82
|
-
<input type="
|
|
83
|
+
Run until (date)
|
|
84
|
+
<input type="date" id="scheduleEndDate" />
|
|
83
85
|
</label>
|
|
84
86
|
<label>
|
|
85
|
-
|
|
87
|
+
Run until (time)
|
|
86
88
|
<input type="time" id="scheduleEndTime" />
|
|
87
89
|
</label>
|
|
88
|
-
<div class="days-row">
|
|
89
|
-
<span style="width: 100%; color: #444">Days</span>
|
|
90
|
-
<label><input type="checkbox" class="day-checkbox" value="0" /> Sun</label>
|
|
91
|
-
<label><input type="checkbox" class="day-checkbox" value="1" /> Mon</label>
|
|
92
|
-
<label><input type="checkbox" class="day-checkbox" value="2" /> Tue</label>
|
|
93
|
-
<label><input type="checkbox" class="day-checkbox" value="3" /> Wed</label>
|
|
94
|
-
<label><input type="checkbox" class="day-checkbox" value="4" /> Thu</label>
|
|
95
|
-
<label><input type="checkbox" class="day-checkbox" value="5" /> Fri</label>
|
|
96
|
-
<label><input type="checkbox" class="day-checkbox" value="6" /> Sat</label>
|
|
97
|
-
</div>
|
|
98
90
|
</div>
|
|
99
91
|
|
|
100
92
|
<div class="editor-actions">
|
|
@@ -273,14 +273,17 @@ function probeVideoDurationInBrowser(fileOrUrl) {
|
|
|
273
273
|
async function resolveVideoDurationMs(file, type, uploadData) {
|
|
274
274
|
if (type !== "video") return defaultDurationMs(type);
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
if (Number.isFinite(fromServer) && fromServer > 0) {
|
|
278
|
-
return clampVideoDurationMs(fromServer) ?? defaultDurationMs(type);
|
|
279
|
-
}
|
|
280
|
-
|
|
276
|
+
// Local File metadata is the most reliable source during upload.
|
|
281
277
|
const fromFile = await probeVideoDurationInBrowser(file);
|
|
282
278
|
if (fromFile) return fromFile;
|
|
283
279
|
|
|
280
|
+
const fromServerRaw = Number(uploadData?.durationMs);
|
|
281
|
+
const fromServer =
|
|
282
|
+
Number.isFinite(fromServerRaw) && fromServerRaw > 0
|
|
283
|
+
? clampVideoDurationMs(fromServerRaw)
|
|
284
|
+
: null;
|
|
285
|
+
if (fromServer) return fromServer;
|
|
286
|
+
|
|
284
287
|
if (uploadData?.url) {
|
|
285
288
|
try {
|
|
286
289
|
const fromUrl = await probeVideoDurationInBrowser(absoluteMediaUrl(uploadData.url));
|
|
@@ -306,14 +309,10 @@ function buildScheduleFromForm() {
|
|
|
306
309
|
const endDate = document.getElementById("scheduleEndDate")?.value?.trim();
|
|
307
310
|
const startTime = document.getElementById("scheduleStartTime")?.value?.trim();
|
|
308
311
|
const endTime = document.getElementById("scheduleEndTime")?.value?.trim();
|
|
309
|
-
const days = [...document.querySelectorAll(".day-checkbox:checked")].map((el) =>
|
|
310
|
-
Number(el.value)
|
|
311
|
-
);
|
|
312
312
|
if (startDate) schedule.startDate = startDate;
|
|
313
313
|
if (endDate) schedule.endDate = endDate;
|
|
314
314
|
if (startTime) schedule.start = startTime;
|
|
315
315
|
if (endTime) schedule.end = endTime;
|
|
316
|
-
if (days.length > 0) schedule.daysOfWeek = days;
|
|
317
316
|
return Object.keys(schedule).length > 0 ? schedule : undefined;
|
|
318
317
|
}
|
|
319
318
|
|
|
@@ -323,10 +322,6 @@ function loadScheduleIntoForm(schedule) {
|
|
|
323
322
|
document.getElementById("scheduleEndDate").value = s.endDate || "";
|
|
324
323
|
document.getElementById("scheduleStartTime").value = s.start || "";
|
|
325
324
|
document.getElementById("scheduleEndTime").value = s.end || "";
|
|
326
|
-
document.querySelectorAll(".day-checkbox").forEach((el) => {
|
|
327
|
-
el.checked =
|
|
328
|
-
Array.isArray(s.daysOfWeek) && s.daysOfWeek.includes(Number(el.value));
|
|
329
|
-
});
|
|
330
325
|
}
|
|
331
326
|
|
|
332
327
|
function getSelectedPlaylist() {
|
|
@@ -492,11 +487,21 @@ function renderEditorAssets() {
|
|
|
492
487
|
const actions = document.createElement("div");
|
|
493
488
|
actions.className = "playlist-item-actions";
|
|
494
489
|
const durInput = document.createElement("input");
|
|
490
|
+
const isVideo = item.type === "video";
|
|
495
491
|
durInput.type = "number";
|
|
496
492
|
durInput.min = "1";
|
|
497
493
|
durInput.max = "3600";
|
|
498
494
|
durInput.value = String(Math.round(item.durationMs / 1000));
|
|
495
|
+
if (isVideo) {
|
|
496
|
+
durInput.readOnly = true;
|
|
497
|
+
durInput.disabled = true;
|
|
498
|
+
durInput.title = "Video duration is auto-detected and cannot be edited.";
|
|
499
|
+
}
|
|
499
500
|
durInput.addEventListener("change", () => {
|
|
501
|
+
if (isVideo) {
|
|
502
|
+
durInput.value = String(Math.round(item.durationMs / 1000));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
500
505
|
item.durationMs = Math.min(3600, Math.max(1, Number(durInput.value) || 10)) * 1000;
|
|
501
506
|
meta.textContent = `${item.type} · ${Math.round(item.durationMs / 1000)}s`;
|
|
502
507
|
});
|