@flow-player/test-streams 0.7.3 → 0.7.5
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/cli/serve.js +81 -0
- package/dist/index.js +81 -0
- package/dist/server/routes.d.ts +12 -0
- package/dist/server/server.js +81 -0
- package/package.json +1 -1
package/dist/cli/serve.js
CHANGED
|
@@ -223,6 +223,77 @@ function handleVodList(res, manifest, fixtureDir, codec = "h264") {
|
|
|
223
223
|
]
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
|
+
var MULTI_VOD_MAX_CHANNELS = 9;
|
|
227
|
+
var MULTI_VOD_DEFAULT_CHANNELS = 4;
|
|
228
|
+
function multiChannelProfile(channelId) {
|
|
229
|
+
const slot = (channelId - 1) % 4;
|
|
230
|
+
return {
|
|
231
|
+
startOffsetMs: slot * 9e4,
|
|
232
|
+
skipSecondSegment: slot === 1,
|
|
233
|
+
truncateHalf: slot === 3
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function shanghaiDayStartMs(nowMs = Date.now()) {
|
|
237
|
+
const offsetMs = 8 * 36e5;
|
|
238
|
+
return Math.floor((nowMs + offsetMs) / 864e5) * 864e5 - offsetMs;
|
|
239
|
+
}
|
|
240
|
+
function parseChannelCount(raw) {
|
|
241
|
+
const value = Number(raw);
|
|
242
|
+
if (!Number.isFinite(value) || value < 1) return MULTI_VOD_DEFAULT_CHANNELS;
|
|
243
|
+
return Math.min(MULTI_VOD_MAX_CHANNELS, Math.floor(value));
|
|
244
|
+
}
|
|
245
|
+
function handleVodListMulti(res, manifest, fixtureDir, channels) {
|
|
246
|
+
const entriesSource = (() => {
|
|
247
|
+
const vodListPath = join(fixtureDir, "vod", "vod-list.json");
|
|
248
|
+
if (existsSync2(vodListPath)) {
|
|
249
|
+
try {
|
|
250
|
+
const raw = JSON.parse(readFileSync2(vodListPath, "utf8"));
|
|
251
|
+
if (Array.isArray(raw.entries) && raw.entries.length > 0) return raw.entries;
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (!manifest) return [];
|
|
256
|
+
const size = manifest.fixtures.find((f) => f.codec === "h264" && f.format === "mp4")?.entries[0]?.size ?? 0;
|
|
257
|
+
return [
|
|
258
|
+
{ path: "vod/sample-1.mp4", size },
|
|
259
|
+
{ path: "vod/sample-2.mp4", size }
|
|
260
|
+
];
|
|
261
|
+
})();
|
|
262
|
+
if (entriesSource.length === 0) {
|
|
263
|
+
writeJson(res, 503, { error: "no vod fixtures available (run extend-vod first)" });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const segMs = manifest?.durationSeconds ? manifest.durationSeconds * 1e3 : 0;
|
|
267
|
+
const dayStart = shanghaiDayStartMs();
|
|
268
|
+
const segments = [];
|
|
269
|
+
const channelIds = [];
|
|
270
|
+
for (let channelId = 1; channelId <= channels; channelId += 1) {
|
|
271
|
+
const profile = multiChannelProfile(channelId);
|
|
272
|
+
let entries = entriesSource;
|
|
273
|
+
if (profile.truncateHalf && entries.length >= 2) {
|
|
274
|
+
entries = entries.slice(0, Math.ceil(entries.length / 2));
|
|
275
|
+
}
|
|
276
|
+
channelIds.push(channelId);
|
|
277
|
+
let cursor = dayStart + profile.startOffsetMs;
|
|
278
|
+
entries.forEach((entry, index) => {
|
|
279
|
+
const start = cursor;
|
|
280
|
+
const end = segMs > 0 ? start + segMs : start;
|
|
281
|
+
cursor = end;
|
|
282
|
+
if (profile.skipSecondSegment && index === 1 && entries.length >= 3) return;
|
|
283
|
+
segments.push({
|
|
284
|
+
url: `/streams/${entry.path}`,
|
|
285
|
+
startTimestamp: start,
|
|
286
|
+
endTimestamp: end,
|
|
287
|
+
timeLen: segMs,
|
|
288
|
+
fileSize: entry.size,
|
|
289
|
+
channelID: channelId,
|
|
290
|
+
RecordType: 1
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const response = { segments, channelIds };
|
|
295
|
+
writeJson(res, 200, response);
|
|
296
|
+
}
|
|
226
297
|
|
|
227
298
|
// src/server/live.ts
|
|
228
299
|
import { spawn } from "node:child_process";
|
|
@@ -473,6 +544,16 @@ function createStreamsServer(options) {
|
|
|
473
544
|
handleVodList(res, ctx.manifest, ctx.fixtureDir, "hevc");
|
|
474
545
|
return;
|
|
475
546
|
}
|
|
547
|
+
if (path === "/streams/vod-list-multi" || path === "/streams/vod-list-multi/") {
|
|
548
|
+
const query = (req.url ?? "").split("?", 2)[1] ?? "";
|
|
549
|
+
handleVodListMulti(
|
|
550
|
+
res,
|
|
551
|
+
ctx.manifest,
|
|
552
|
+
ctx.fixtureDir,
|
|
553
|
+
parseChannelCount(new URLSearchParams(query).get("channels"))
|
|
554
|
+
);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
476
557
|
const resolved = resolveRoute(path);
|
|
477
558
|
if (resolved) {
|
|
478
559
|
serveEntry(req, res, fixtureDir, resolved);
|
package/dist/index.js
CHANGED
|
@@ -425,6 +425,77 @@ function handleVodList(res, manifest, fixtureDir, codec = "h264") {
|
|
|
425
425
|
]
|
|
426
426
|
});
|
|
427
427
|
}
|
|
428
|
+
var MULTI_VOD_MAX_CHANNELS = 9;
|
|
429
|
+
var MULTI_VOD_DEFAULT_CHANNELS = 4;
|
|
430
|
+
function multiChannelProfile(channelId) {
|
|
431
|
+
const slot = (channelId - 1) % 4;
|
|
432
|
+
return {
|
|
433
|
+
startOffsetMs: slot * 9e4,
|
|
434
|
+
skipSecondSegment: slot === 1,
|
|
435
|
+
truncateHalf: slot === 3
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function shanghaiDayStartMs(nowMs = Date.now()) {
|
|
439
|
+
const offsetMs = 8 * 36e5;
|
|
440
|
+
return Math.floor((nowMs + offsetMs) / 864e5) * 864e5 - offsetMs;
|
|
441
|
+
}
|
|
442
|
+
function parseChannelCount(raw) {
|
|
443
|
+
const value = Number(raw);
|
|
444
|
+
if (!Number.isFinite(value) || value < 1) return MULTI_VOD_DEFAULT_CHANNELS;
|
|
445
|
+
return Math.min(MULTI_VOD_MAX_CHANNELS, Math.floor(value));
|
|
446
|
+
}
|
|
447
|
+
function handleVodListMulti(res, manifest, fixtureDir, channels) {
|
|
448
|
+
const entriesSource = (() => {
|
|
449
|
+
const vodListPath = join3(fixtureDir, "vod", "vod-list.json");
|
|
450
|
+
if (existsSync2(vodListPath)) {
|
|
451
|
+
try {
|
|
452
|
+
const raw = JSON.parse(readFileSync2(vodListPath, "utf8"));
|
|
453
|
+
if (Array.isArray(raw.entries) && raw.entries.length > 0) return raw.entries;
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (!manifest) return [];
|
|
458
|
+
const size = manifest.fixtures.find((f) => f.codec === "h264" && f.format === "mp4")?.entries[0]?.size ?? 0;
|
|
459
|
+
return [
|
|
460
|
+
{ path: "vod/sample-1.mp4", size },
|
|
461
|
+
{ path: "vod/sample-2.mp4", size }
|
|
462
|
+
];
|
|
463
|
+
})();
|
|
464
|
+
if (entriesSource.length === 0) {
|
|
465
|
+
writeJson(res, 503, { error: "no vod fixtures available (run extend-vod first)" });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const segMs = manifest?.durationSeconds ? manifest.durationSeconds * 1e3 : 0;
|
|
469
|
+
const dayStart = shanghaiDayStartMs();
|
|
470
|
+
const segments = [];
|
|
471
|
+
const channelIds = [];
|
|
472
|
+
for (let channelId = 1; channelId <= channels; channelId += 1) {
|
|
473
|
+
const profile = multiChannelProfile(channelId);
|
|
474
|
+
let entries = entriesSource;
|
|
475
|
+
if (profile.truncateHalf && entries.length >= 2) {
|
|
476
|
+
entries = entries.slice(0, Math.ceil(entries.length / 2));
|
|
477
|
+
}
|
|
478
|
+
channelIds.push(channelId);
|
|
479
|
+
let cursor = dayStart + profile.startOffsetMs;
|
|
480
|
+
entries.forEach((entry, index) => {
|
|
481
|
+
const start = cursor;
|
|
482
|
+
const end = segMs > 0 ? start + segMs : start;
|
|
483
|
+
cursor = end;
|
|
484
|
+
if (profile.skipSecondSegment && index === 1 && entries.length >= 3) return;
|
|
485
|
+
segments.push({
|
|
486
|
+
url: `/streams/${entry.path}`,
|
|
487
|
+
startTimestamp: start,
|
|
488
|
+
endTimestamp: end,
|
|
489
|
+
timeLen: segMs,
|
|
490
|
+
fileSize: entry.size,
|
|
491
|
+
channelID: channelId,
|
|
492
|
+
RecordType: 1
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
const response = { segments, channelIds };
|
|
497
|
+
writeJson(res, 200, response);
|
|
498
|
+
}
|
|
428
499
|
|
|
429
500
|
// src/server/live.ts
|
|
430
501
|
import { spawn } from "node:child_process";
|
|
@@ -653,6 +724,16 @@ function createStreamsServer(options) {
|
|
|
653
724
|
handleVodList(res, ctx.manifest, ctx.fixtureDir, "hevc");
|
|
654
725
|
return;
|
|
655
726
|
}
|
|
727
|
+
if (path === "/streams/vod-list-multi" || path === "/streams/vod-list-multi/") {
|
|
728
|
+
const query = (req.url ?? "").split("?", 2)[1] ?? "";
|
|
729
|
+
handleVodListMulti(
|
|
730
|
+
res,
|
|
731
|
+
ctx.manifest,
|
|
732
|
+
ctx.fixtureDir,
|
|
733
|
+
parseChannelCount(new URLSearchParams(query).get("channels"))
|
|
734
|
+
);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
656
737
|
const resolved = resolveRoute(path);
|
|
657
738
|
if (resolved) {
|
|
658
739
|
serveEntry(req, res, fixtureDir, resolved);
|
package/dist/server/routes.d.ts
CHANGED
|
@@ -16,3 +16,15 @@ export type VodListResponse = {
|
|
|
16
16
|
segments: ReadonlyArray<VodSegmentDescriptor>;
|
|
17
17
|
};
|
|
18
18
|
export declare function handleVodList(res: ServerResponse, manifest: Manifest | null, fixtureDir: string, codec?: "h264" | "hevc"): void;
|
|
19
|
+
export type MultiVodListResponse = VodListResponse & {
|
|
20
|
+
channelIds: number[];
|
|
21
|
+
};
|
|
22
|
+
export declare const MULTI_VOD_MAX_CHANNELS = 9;
|
|
23
|
+
export declare const MULTI_VOD_DEFAULT_CHANNELS = 4;
|
|
24
|
+
export declare function parseChannelCount(raw: string | null): number;
|
|
25
|
+
/**
|
|
26
|
+
* /streams/vod-list-multi?channels=N:返回 N 个通道的录像段列表。
|
|
27
|
+
* 时间基准对齐当天上海时区 00:00(与 demo 时间轴的 24h 视野对齐);
|
|
28
|
+
* 数据源与 /streams/vod-list 相同(vod-list.json 优先,无则回退两段样本)。
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleVodListMulti(res: ServerResponse, manifest: Manifest | null, fixtureDir: string, channels: number): void;
|
package/dist/server/server.js
CHANGED
|
@@ -216,6 +216,77 @@ function handleVodList(res, manifest, fixtureDir, codec = "h264") {
|
|
|
216
216
|
]
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
|
+
var MULTI_VOD_MAX_CHANNELS = 9;
|
|
220
|
+
var MULTI_VOD_DEFAULT_CHANNELS = 4;
|
|
221
|
+
function multiChannelProfile(channelId) {
|
|
222
|
+
const slot = (channelId - 1) % 4;
|
|
223
|
+
return {
|
|
224
|
+
startOffsetMs: slot * 9e4,
|
|
225
|
+
skipSecondSegment: slot === 1,
|
|
226
|
+
truncateHalf: slot === 3
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function shanghaiDayStartMs(nowMs = Date.now()) {
|
|
230
|
+
const offsetMs = 8 * 36e5;
|
|
231
|
+
return Math.floor((nowMs + offsetMs) / 864e5) * 864e5 - offsetMs;
|
|
232
|
+
}
|
|
233
|
+
function parseChannelCount(raw) {
|
|
234
|
+
const value = Number(raw);
|
|
235
|
+
if (!Number.isFinite(value) || value < 1) return MULTI_VOD_DEFAULT_CHANNELS;
|
|
236
|
+
return Math.min(MULTI_VOD_MAX_CHANNELS, Math.floor(value));
|
|
237
|
+
}
|
|
238
|
+
function handleVodListMulti(res, manifest, fixtureDir, channels) {
|
|
239
|
+
const entriesSource = (() => {
|
|
240
|
+
const vodListPath = join(fixtureDir, "vod", "vod-list.json");
|
|
241
|
+
if (existsSync2(vodListPath)) {
|
|
242
|
+
try {
|
|
243
|
+
const raw = JSON.parse(readFileSync2(vodListPath, "utf8"));
|
|
244
|
+
if (Array.isArray(raw.entries) && raw.entries.length > 0) return raw.entries;
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!manifest) return [];
|
|
249
|
+
const size = manifest.fixtures.find((f) => f.codec === "h264" && f.format === "mp4")?.entries[0]?.size ?? 0;
|
|
250
|
+
return [
|
|
251
|
+
{ path: "vod/sample-1.mp4", size },
|
|
252
|
+
{ path: "vod/sample-2.mp4", size }
|
|
253
|
+
];
|
|
254
|
+
})();
|
|
255
|
+
if (entriesSource.length === 0) {
|
|
256
|
+
writeJson(res, 503, { error: "no vod fixtures available (run extend-vod first)" });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const segMs = manifest?.durationSeconds ? manifest.durationSeconds * 1e3 : 0;
|
|
260
|
+
const dayStart = shanghaiDayStartMs();
|
|
261
|
+
const segments = [];
|
|
262
|
+
const channelIds = [];
|
|
263
|
+
for (let channelId = 1; channelId <= channels; channelId += 1) {
|
|
264
|
+
const profile = multiChannelProfile(channelId);
|
|
265
|
+
let entries = entriesSource;
|
|
266
|
+
if (profile.truncateHalf && entries.length >= 2) {
|
|
267
|
+
entries = entries.slice(0, Math.ceil(entries.length / 2));
|
|
268
|
+
}
|
|
269
|
+
channelIds.push(channelId);
|
|
270
|
+
let cursor = dayStart + profile.startOffsetMs;
|
|
271
|
+
entries.forEach((entry, index) => {
|
|
272
|
+
const start = cursor;
|
|
273
|
+
const end = segMs > 0 ? start + segMs : start;
|
|
274
|
+
cursor = end;
|
|
275
|
+
if (profile.skipSecondSegment && index === 1 && entries.length >= 3) return;
|
|
276
|
+
segments.push({
|
|
277
|
+
url: `/streams/${entry.path}`,
|
|
278
|
+
startTimestamp: start,
|
|
279
|
+
endTimestamp: end,
|
|
280
|
+
timeLen: segMs,
|
|
281
|
+
fileSize: entry.size,
|
|
282
|
+
channelID: channelId,
|
|
283
|
+
RecordType: 1
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const response = { segments, channelIds };
|
|
288
|
+
writeJson(res, 200, response);
|
|
289
|
+
}
|
|
219
290
|
|
|
220
291
|
// src/server/live.ts
|
|
221
292
|
import { spawn } from "node:child_process";
|
|
@@ -466,6 +537,16 @@ function createStreamsServer(options) {
|
|
|
466
537
|
handleVodList(res, ctx.manifest, ctx.fixtureDir, "hevc");
|
|
467
538
|
return;
|
|
468
539
|
}
|
|
540
|
+
if (path === "/streams/vod-list-multi" || path === "/streams/vod-list-multi/") {
|
|
541
|
+
const query = (req.url ?? "").split("?", 2)[1] ?? "";
|
|
542
|
+
handleVodListMulti(
|
|
543
|
+
res,
|
|
544
|
+
ctx.manifest,
|
|
545
|
+
ctx.fixtureDir,
|
|
546
|
+
parseChannelCount(new URLSearchParams(query).get("channels"))
|
|
547
|
+
);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
469
550
|
const resolved = resolveRoute(path);
|
|
470
551
|
if (resolved) {
|
|
471
552
|
serveEntry(req, res, fixtureDir, resolved);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flow-player/test-streams",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "ffmpeg-driven local test stream service for Flow Player integration testing: generates and serves H.264/HEVC × HTTP-FLV / HLS / fMP4 / MP4 / MPEG-TS fixtures on 127.0.0.1:4177 (live infinite-loop + 5s VOD fixtures).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"esm",
|