@eluvio/elv-client-js 4.0.83 → 4.0.84

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "4.0.83",
3
+ "version": "4.0.84",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/index.js",
6
6
  "author": "Kevin Talmadge",
@@ -15,6 +15,7 @@ const LadderTemplate = {
15
15
  media_type: 1,
16
16
  representation: "videovideo_1920x1080_h264@9500000",
17
17
  stream_name: "video",
18
+ stream_index: 0,
18
19
  width: 1920
19
20
  },
20
21
  "720": {
@@ -24,6 +25,7 @@ const LadderTemplate = {
24
25
  media_type: 1,
25
26
  representation: "videovideo_1280x720_h264@4500000",
26
27
  stream_name: "video",
28
+ stream_index: 0,
27
29
  width: 1280
28
30
  },
29
31
  "540": {
@@ -33,6 +35,7 @@ const LadderTemplate = {
33
35
  media_type: 1,
34
36
  representation: "videovideo_960x540_h264@2000000",
35
37
  stream_name: "video",
38
+ stream_index: 0,
36
39
  width: 960
37
40
  },
38
41
  "540_low": {
@@ -42,6 +45,7 @@ const LadderTemplate = {
42
45
  media_type: 1,
43
46
  representation: "videovideo_960x540_h264@900000",
44
47
  stream_name: "video",
48
+ stream_index: 0,
45
49
  width: 960
46
50
  }
47
51
  };
@@ -59,16 +63,7 @@ const LiveconfTemplate = {
59
63
  recording_config: {
60
64
  recording_params: {
61
65
  description: "",
62
- ladder_specs: [
63
- {
64
- bit_rate: 384000,
65
- channels: 2,
66
- codecs: "mp4a.40.2",
67
- media_type: 2,
68
- representation: "audioaudio_aac@384000",
69
- stream_name: "audio"
70
- }
71
- ],
66
+ ladder_specs: [],
72
67
  listen: true,
73
68
  live_delay_nano: 2000000000,
74
69
  max_duration_sec: -1,
@@ -90,6 +85,7 @@ const LiveconfTemplate = {
90
85
  0
91
86
  ],
92
87
  audio_seg_duration_ts: null,
88
+ connection_timeout: 60,
93
89
  ecodec2: "aac",
94
90
  enc_height: null,
95
91
  enc_width: null,
@@ -108,6 +104,7 @@ const LiveconfTemplate = {
108
104
  video_bitrate: null,
109
105
  video_seg_duration_ts: null,
110
106
  video_time_base: null,
107
+ video_frame_duration_ts: null,
111
108
  xc_type: 3
112
109
  }
113
110
  }
@@ -115,6 +112,16 @@ const LiveconfTemplate = {
115
112
  }
116
113
  };
117
114
 
115
+ const LadderSpecAudio = {
116
+ bit_rate: 384000,
117
+ channels: 2,
118
+ codecs: "mp4a.40.2",
119
+ media_type: 2,
120
+ representation: "audioaudio_aac@384000",
121
+ stream_name: "audio",
122
+ stream_index: 0
123
+ };
124
+
118
125
  class LiveConf {
119
126
  constructor(probeData, nodeId, nodeUrl, includeAVSegDurations, overwriteOriginUrl, syncAudioToVideo) {
120
127
  this.probeData = probeData;
@@ -140,6 +147,22 @@ class LiveConf {
140
147
  return stream;
141
148
  }
142
149
 
150
+ // Return all audio streams found in the probe
151
+ // Used by generateAudioStreamsConfig()
152
+ getAudioStreamsFromProbe() {
153
+ let audioStreams = {};
154
+ for(let index = 0; index < this.probeData.streams.length; index++) {
155
+ if(this.probeData.streams[index].codec_type == "audio") {
156
+ audioStreams[index] = {
157
+ recordingBitrate: Math.max(this.probeData.streams[index].bit_rate, 128000),
158
+ recordingChannels: this.probeData.streams[index].channels,
159
+ playoutLabel: `Audio ${index}`
160
+ }
161
+ }
162
+ }
163
+ return audioStreams;
164
+ }
165
+
143
166
  getFrameRate() {
144
167
  let videoStream = this.getStreamDataForCodecType("video");
145
168
  let frameRate = videoStream.r_frame_rate || videoStream.frame_rate;
@@ -223,7 +246,12 @@ class LiveConf {
223
246
  calcSegDurationMpegts({sourceTimescale}) {
224
247
  let videoStream = this.getStreamDataForCodecType("video");
225
248
  let frameRate = videoStream.frame_rate;
226
- let seg = {};
249
+
250
+ // PENDING(SS) - calculate frame duration here
251
+ // let frameRateNum = 0;
252
+ let seg = {
253
+ // videoFrameDurationTs: sourceTimescale / frameRateNum
254
+ };
227
255
 
228
256
  switch(frameRate) {
229
257
  case "24":
@@ -242,21 +270,25 @@ class LiveConf {
242
270
  seg.duration = "30";
243
271
  break;
244
272
  case "30000/1001":
273
+ //seg.videoFrameDurationTs = 3003;
245
274
  seg.video = sourceTimescale * 30;
246
275
  seg.keyint = 60;
247
276
  seg.duration = "30.03";
248
277
  break;
249
278
  case "48":
279
+ //seg.videoFrameDurationTs = 1875;
250
280
  seg.video = sourceTimescale * 30;
251
281
  seg.keyint = 96;
252
282
  seg.duration = "30";
253
283
  break;
254
284
  case "50":
285
+ //seg.videoFrameDurationTs = 1800;
255
286
  seg.video = sourceTimescale * 30;
256
287
  seg.keyint = 100;
257
288
  seg.duration = "30";
258
289
  break;
259
290
  case "60":
291
+ //seg.videoFrameDurationTs = 1500;
260
292
  seg.video = sourceTimescale * 30;
261
293
  seg.keyint = 120;
262
294
  seg.duration = "30";
@@ -281,7 +313,7 @@ class LiveConf {
281
313
 
282
314
  switch(frameRate) {
283
315
  case "24":
284
- seg.videoTimeBase = 1536; // Output timebase: 12288
316
+ seg.videoTimeBase = 768; // Note 1536 produces low output bitrate
285
317
  seg.videoFrameDurationTs = 512;
286
318
  seg.video = this.calcOutputTimebase(seg.videoTimeBase) * 30;
287
319
  seg.keyint = 48;
@@ -350,36 +382,77 @@ class LiveConf {
350
382
  return sync_id;
351
383
  }
352
384
 
353
- generateLiveConf({audioBitrate, audioIndex, partTtl, channelLayout}) {
385
+ /*
386
+ * Generate audio streams recording configuration based on the optional custom settings.
387
+ * If no custom "audio" section is present, record all the acceptable audio streams found in the probe
388
+ */
389
+ generateAudioStreamsConfig({customSettings}) {
390
+
391
+ let audioStreams = {};
392
+ if (customSettings && customSettings.audio) {
393
+ for (let i = 0; i < Object.keys(customSettings.audio).length; i ++) {
394
+ let audioIdx = Object.keys(customSettings.audio)[i];
395
+ let audio = customSettings.audio[audioIdx];
396
+ audioStreams[audioIdx] = {
397
+ recordingBitrate: audio.recording_bitrate || 192000,
398
+ recordingChannels: audio.recording_channels || 2,
399
+ };
400
+ if (audio.playout) {
401
+ audioStreams[audioIdx].playoutLabel = audio.playout_label || `Audio ${audioIdx}`
402
+ }
403
+ }
404
+ }
405
+
406
+ // If no audio streams specified in custom config, set up all the suitable audio streams in the probe
407
+ if (Object.keys(audioStreams).length == 0) {
408
+ audioStreams = this.getAudioStreamsFromProbe();
409
+ }
410
+
411
+ return audioStreams;
412
+ }
413
+
414
+ /*
415
+ * Generate the live recording config as required by QFAB, based on defaults and optional custom settings.
416
+ */
417
+ generateLiveConf({customSettings}) {
354
418
  // gather required data
355
419
  const conf = JSON.parse(JSON.stringify(LiveconfTemplate));
356
420
  const fileName = this.overwriteOriginUrl || this.probeData.format.filename;
357
- const audioStream = this.getStreamDataForCodecType("audio");
421
+ const audioStreams = this.generateAudioStreamsConfig({customSettings});
358
422
 
423
+ // Retrieve one audio stream from the probe to read the sample rate and codec name
424
+ const audioStream = this.getStreamDataForCodecType("audio");
359
425
  const sampleRate = parseInt(audioStream.sample_rate);
360
426
  const audioCodec = audioStream.codec_name;
427
+
361
428
  const videoStream = this.getStreamDataForCodecType("video");
362
429
  let sourceTimescale;
363
430
 
364
431
  // Fill in liveconf all formats have in common
365
- conf.live_recording.probe_info = this.probeData;
366
432
  conf.live_recording.fabric_config.ingress_node_api = this.nodeUrl || null;
367
433
  conf.live_recording.fabric_config.ingress_node_id = this.nodeId || null;
368
434
  conf.live_recording.recording_config.recording_params.description;
369
435
  conf.live_recording.recording_config.recording_params.origin_url = fileName;
370
436
  conf.live_recording.recording_config.recording_params.description = `Ingest stream ${fileName}`;
371
437
  conf.live_recording.recording_config.recording_params.name = `Ingest stream ${fileName}`;
372
- conf.live_recording.recording_config.recording_params.xc_params.audio_index[0] = audioIndex === undefined ? audioStream.stream_index : audioIndex;
373
438
  conf.live_recording.recording_config.recording_params.xc_params.sample_rate = sampleRate;
374
439
  conf.live_recording.recording_config.recording_params.xc_params.enc_height = videoStream.height;
375
440
  conf.live_recording.recording_config.recording_params.xc_params.enc_width = videoStream.width;
376
441
 
442
+ for (let i =0; i < Object.keys(audioStreams).length; i ++) {
443
+ conf.live_recording.recording_config.recording_params.xc_params.audio_index[i] = parseInt(Object.keys(audioStreams)[i]);
444
+ }
445
+
377
446
  if(this.syncAudioToVideo) {
378
447
  conf.live_recording.recording_config.recording_params.xc_params.sync_audio_to_stream_id = this.syncAudioToStreamIdValue();
379
448
  }
380
449
 
381
- if(partTtl) {
382
- conf.live_recording.recording_config.recording_params.part_ttl = partTtl;
450
+ if(customSettings.edge_write_token) {
451
+ conf.live_recording.fabric_config.edge_write_token = customSettings.edge_write_token;
452
+ }
453
+
454
+ if(customSettings.part_ttl) {
455
+ conf.live_recording.recording_config.recording_params.part_ttl = customSettings.part_ttl;
383
456
  }
384
457
 
385
458
  // Fill in specifics for protocol
@@ -472,21 +545,33 @@ class LiveConf {
472
545
  throw new Error("ERROR: Probed stream does not conform to one of the following built in resolution ladders [4096, 2160], [1920, 1080] [1280, 720], [960, 540]");
473
546
  }
474
547
 
475
- if(audioBitrate || channelLayout) {
476
- const audioLadderSpec = conf.live_recording.recording_config.recording_params.ladder_specs.find(spec => spec.stream_name === "audio");
477
548
 
478
- if(audioBitrate) {
479
- conf.live_recording.recording_config.recording_params.xc_params.audio_bitrate = audioBitrate;
480
- audioLadderSpec.bit_rate = audioBitrate;
481
- audioLadderSpec.representation = `audioaudio_aac@${audioBitrate}`;
482
- }
549
+ let globalAudioBitrate = 0;
550
+ let nAudio = 0;
483
551
 
484
- if(channelLayout) {
485
- audioLadderSpec.channels = channelLayout;
552
+ for (let i = 0; i < Object.keys(audioStreams).length; i ++ ) {
553
+ let audioLadderSpec = {...LadderSpecAudio};
554
+ const audioIndex = Object.keys(audioStreams)[i];
555
+ const audio = audioStreams[audioIndex];
556
+ audioLadderSpec.bit_rate = audio.recordingBitrate;
557
+ audioLadderSpec.representation = `audioaudio_aac@${audio.recordingBitrate}`;
558
+ audioLadderSpec.channels = audio.recordingChannels;
559
+ audioLadderSpec.stream_index = parseInt(audioIndex);
560
+ audioLadderSpec.stream_name = `audio_${audioIndex}`;
561
+ audioLadderSpec.stream_label = audio.playoutLabel ? audio.playoutLabel : null;
562
+
563
+ conf.live_recording.recording_config.recording_params.ladder_specs.push(audioLadderSpec);
564
+ if (audio.recordingBitrate > globalAudioBitrate) {
565
+ globalAudioBitrate = audio.recordingBitrate;
486
566
  }
567
+ nAudio ++;
487
568
  }
488
569
 
489
- return JSON.stringify(conf, null, 2);
570
+ // Global recording bitrate for all audio streams
571
+ conf.live_recording.recording_config.recording_params.xc_params.audio_bitrate = globalAudioBitrate;
572
+ conf.live_recording.recording_config.recording_params.xc_params.n_audio = nAudio;
573
+
574
+ return conf;
490
575
  }
491
576
  }
492
577
  exports.LiveConf = LiveConf;