@flow-player/test-streams 0.7.3 → 0.7.4

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 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);
@@ -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;
@@ -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",
3
+ "version": "0.7.4",
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",