@eluvio/elv-client-js 4.2.15 → 4.2.17
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/ElvClient-min.js +1 -1
- package/dist/ElvClient-node-min.js +1 -1
- package/dist/ElvFrameClient-min.js +1 -1
- package/dist/ElvPermissionsClient-min.js +1 -1
- package/dist/ElvWalletClient-min.js +1 -1
- package/dist/ElvWalletClient-node-min.js +1 -1
- package/dist/src/AuthorizationClient.js +2 -1
- package/dist/src/ContentObjectAudit.js +2 -1
- package/dist/src/ContentObjectVerification.js +281 -0
- package/dist/src/ElvClient.js +8 -9
- package/dist/src/FrameClient.js +1 -1
- package/dist/src/HttpClient.js +83 -47
- package/dist/src/NetworkUrls.js +8 -0
- package/dist/src/abr_profiles/abr_profile_live_drm.js +0 -10
- package/dist/src/client/ContentAccess.js +76 -85
- package/dist/src/client/LiveConf.js +170 -84
- package/dist/src/client/LiveStream.js +5205 -2118
- package/dist/src/live_recording_config_profiles/live_recording_config_default.js +45 -0
- package/package.json +3 -2
- package/src/AuthorizationClient.js +2 -1
- package/src/ContentObjectAudit.js +4 -1
- package/src/ElvClient.js +8 -15
- package/src/FrameClient.js +23 -2
- package/src/HttpClient.js +17 -1
- package/src/NetworkUrls.js +9 -0
- package/src/abr_profiles/abr_profile_live_drm.js +0 -10
- package/src/client/ContentAccess.js +8 -23
- package/src/client/LiveConf.js +149 -65
- package/src/client/LiveStream.js +2592 -654
- package/src/live_recording_config_profiles/live_recording_config_default.js +54 -0
- package/src/live_recording_config_profiles/live_stream_profile_full.json +143 -0
- package/testScripts/StreamUpdateLinks.js +95 -0
- package/utilities/ChannelCreate.js +1 -1
- package/utilities/LibraryDownloadMp4.js +54 -8
- package/utilities/LibraryDownloadMp4Parallel.js +544 -0
- package/utilities/LiveOutputs.js +149 -0
- package/utilities/StreamCreate.js +53 -0
- package/utilities/lib/concerns/Client.js +5 -0
- package/utilities/lib/helpers.js +5 -1
- package/utilities/tests/mocks/ElvClient.mock.js +9 -1
- package/utilities/tests/unit/StreamCreate.test.js +39 -0
package/src/client/LiveConf.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint no-console: 0 */
|
|
2
|
+
const R = require("ramda");
|
|
2
3
|
|
|
3
4
|
const DefaultABRLadder = {
|
|
4
|
-
"video"
|
|
5
|
+
"video": [
|
|
5
6
|
{
|
|
6
7
|
bit_rate: 14000000,
|
|
7
8
|
codecs: "avc1.640028,mp4a.40.2",
|
|
@@ -33,7 +34,7 @@ const DefaultABRLadder = {
|
|
|
33
34
|
width: 960
|
|
34
35
|
}
|
|
35
36
|
],
|
|
36
|
-
"audio"
|
|
37
|
+
"audio": [
|
|
37
38
|
{
|
|
38
39
|
bit_rate: 192000,
|
|
39
40
|
channels: 2,
|
|
@@ -55,8 +56,9 @@ const LiveconfTemplate = {
|
|
|
55
56
|
},
|
|
56
57
|
playout_config: {
|
|
57
58
|
dvr_enabled: true,
|
|
58
|
-
dvr_max_duration:
|
|
59
|
+
dvr_max_duration: 14400,
|
|
59
60
|
rebroadcast_start_time_sec_epoch: 0,
|
|
61
|
+
playout_sharding_level: 2,
|
|
60
62
|
vod_enabled: false
|
|
61
63
|
},
|
|
62
64
|
recording_config: {
|
|
@@ -64,11 +66,11 @@ const LiveconfTemplate = {
|
|
|
64
66
|
description: "",
|
|
65
67
|
ladder_specs: [],
|
|
66
68
|
listen: true,
|
|
67
|
-
live_delay_nano:
|
|
69
|
+
live_delay_nano: 6000000000,
|
|
68
70
|
max_duration_sec: -1,
|
|
69
71
|
name: "",
|
|
70
72
|
origin_url: "",
|
|
71
|
-
part_ttl:
|
|
73
|
+
part_ttl: 86400,
|
|
72
74
|
playout_type: "live",
|
|
73
75
|
source_timescale: null,
|
|
74
76
|
reconnect_timeout: 600,
|
|
@@ -113,18 +115,49 @@ const LiveconfTemplate = {
|
|
|
113
115
|
};
|
|
114
116
|
|
|
115
117
|
class LiveConf {
|
|
116
|
-
constructor(
|
|
118
|
+
constructor({
|
|
119
|
+
url,
|
|
120
|
+
probeData,
|
|
121
|
+
nodeId,
|
|
122
|
+
nodeUrl,
|
|
123
|
+
includeAVSegDurations,
|
|
124
|
+
overwriteOriginUrl,
|
|
125
|
+
syncAudioToVideo,
|
|
126
|
+
liveRecordingMeta
|
|
127
|
+
}) {
|
|
128
|
+
this.url = url;
|
|
117
129
|
this.probeData = probeData;
|
|
118
130
|
this.nodeId = nodeId;
|
|
119
131
|
this.nodeUrl = nodeUrl;
|
|
120
132
|
this.includeAVSegDurations = includeAVSegDurations;
|
|
121
133
|
this.overwriteOriginUrl = overwriteOriginUrl;
|
|
122
134
|
this.syncAudioToVideo = syncAudioToVideo;
|
|
135
|
+
this.currentLiveRecordingMeta = liveRecordingMeta;
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
getFormat() {
|
|
139
|
+
if (this.probeData.format.format_name) {
|
|
140
|
+
return this.probeData.format.format_name;
|
|
141
|
+
}
|
|
142
|
+
const fileNameSplit = this.probeData.format?.filename?.split(":");
|
|
143
|
+
if (fileNameSplit.length > 1) {
|
|
144
|
+
const protoScheme = fileNameSplit[0];
|
|
145
|
+
switch(protoScheme) {
|
|
146
|
+
case "rtmp":
|
|
147
|
+
return "flv";
|
|
148
|
+
case "udp":
|
|
149
|
+
case "rtp":
|
|
150
|
+
case "srt":
|
|
151
|
+
return "mpegts";
|
|
152
|
+
default:
|
|
153
|
+
return "format_unknown";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getProtocol() {
|
|
159
|
+
const protoScheme = this.url.split(":")[0];
|
|
160
|
+
return protoScheme;
|
|
128
161
|
}
|
|
129
162
|
|
|
130
163
|
getStreamDataForCodecType(codecType) {
|
|
@@ -139,16 +172,18 @@ class LiveConf {
|
|
|
139
172
|
|
|
140
173
|
// Return all audio streams found in the probe
|
|
141
174
|
// Used by generateAudioStreamsConfig()
|
|
142
|
-
getAudioStreamsFromProbe() {
|
|
143
|
-
|
|
175
|
+
getAudioStreamsFromProbe({ladderProfile}) {
|
|
176
|
+
const audioStreams = {};
|
|
144
177
|
const audioStreamData = this.probeData.streams.filter((value) => value.codec_type === "audio");
|
|
145
178
|
|
|
146
179
|
for(let index = 0; index < audioStreamData.length; index++) {
|
|
147
180
|
const currentStreamIndex = audioStreamData[index].stream_index;
|
|
148
181
|
const currentStreamData = audioStreamData[index];
|
|
149
182
|
|
|
183
|
+
const profileAudioForType = ladderProfile?.audio?.find(a => a.channels === currentStreamData.channels);
|
|
184
|
+
|
|
150
185
|
audioStreams[currentStreamIndex] = {
|
|
151
|
-
recordingBitrate: Math.max(currentStreamData.bit_rate,
|
|
186
|
+
recordingBitrate: profileAudioForType?.bit_rate ?? Math.max(currentStreamData.bit_rate ?? 0, 12800),
|
|
152
187
|
recordingChannels: currentStreamData.channels,
|
|
153
188
|
playoutLabel: `Audio ${index + 1}`
|
|
154
189
|
};
|
|
@@ -205,16 +240,15 @@ class LiveConf {
|
|
|
205
240
|
calcSegDuration({sourceTimescale, sampleRate, audioCodec}) {
|
|
206
241
|
let seg = {};
|
|
207
242
|
|
|
208
|
-
switch(this.
|
|
209
|
-
case "
|
|
243
|
+
switch(this.getFormat()) {
|
|
244
|
+
case "flv":
|
|
210
245
|
seg = this.calcSegDurationRtmp({sourceTimescale, sampleRate, audioCodec});
|
|
211
246
|
break;
|
|
212
|
-
case "
|
|
213
|
-
case "srt":
|
|
247
|
+
case "mpegts":
|
|
214
248
|
seg = this.calcSegDurationMpegts({sourceTimescale, sampleRate, audioCodec});
|
|
215
249
|
break;
|
|
216
250
|
default:
|
|
217
|
-
throw "protocol not supported - " + this.
|
|
251
|
+
throw "protocol not supported - " + this.getFormat();
|
|
218
252
|
}
|
|
219
253
|
|
|
220
254
|
if(audioCodec == "aac") {
|
|
@@ -362,32 +396,79 @@ class LiveConf {
|
|
|
362
396
|
syncAudioToStreamIdValue() {
|
|
363
397
|
let sync_id = -1;
|
|
364
398
|
let videoStream = this.getStreamDataForCodecType("video");
|
|
365
|
-
switch(this.
|
|
366
|
-
case "
|
|
367
|
-
case "srt":
|
|
399
|
+
switch(this.getFormat()) {
|
|
400
|
+
case "mpegts":
|
|
368
401
|
sync_id = videoStream.stream_id;
|
|
369
402
|
break;
|
|
370
|
-
case "
|
|
403
|
+
case "flv":
|
|
371
404
|
sync_id = videoStream.stream_index;
|
|
372
405
|
break;
|
|
373
406
|
}
|
|
374
407
|
return sync_id;
|
|
375
408
|
}
|
|
376
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Map custom live recording profile to the expected config structure
|
|
412
|
+
* @param {Object} customProfile - User's custom recording profile
|
|
413
|
+
* @return {Object} - Mapped config in live_recording format
|
|
414
|
+
*/
|
|
415
|
+
MapCustomProfileToLiveConfig({customProfile}) {
|
|
416
|
+
if(!customProfile) return {};
|
|
417
|
+
|
|
418
|
+
const CompactDeep = (obj) => {
|
|
419
|
+
if(obj === null || typeof obj !== "object" || Array.isArray(obj)) {
|
|
420
|
+
return obj;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return R.pipe(
|
|
424
|
+
R.reject(R.isNil),
|
|
425
|
+
R.map(val => typeof val === "object" ? CompactDeep(val) : val)
|
|
426
|
+
)(obj);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const {recording_config, recording_params, ...rest} = customProfile;
|
|
430
|
+
|
|
431
|
+
return CompactDeep({
|
|
432
|
+
live_recording: {
|
|
433
|
+
...rest,
|
|
434
|
+
recording_config: {
|
|
435
|
+
recording_params: {
|
|
436
|
+
...recording_params,
|
|
437
|
+
part_ttl: recording_config?.part_ttl,
|
|
438
|
+
reconnect_timeout: recording_config?.reconnect_timeout,
|
|
439
|
+
xc_params: {
|
|
440
|
+
...(recording_params?.xc_params || {}),
|
|
441
|
+
connection_timeout: recording_config?.connection_timeout,
|
|
442
|
+
copy_mpegts: recording_config?.copy_mpegts,
|
|
443
|
+
input_cfg: recording_config?.input_cfg
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
377
451
|
/*
|
|
378
452
|
* Generate audio streams recording configuration based on the optional custom settings.
|
|
379
453
|
* If no custom "audio" section is present, record all the acceptable audio streams found in the probe
|
|
380
454
|
*/
|
|
381
|
-
generateAudioStreamsConfig({
|
|
455
|
+
generateAudioStreamsConfig({liveRecordingConfigProfile}) {
|
|
456
|
+
const ladderProfile = liveRecordingConfigProfile?.playout_config?.ladder_specs || DefaultABRLadder;
|
|
457
|
+
const audioSettings = liveRecordingConfigProfile?.recording_stream_config?.audio;
|
|
382
458
|
|
|
383
459
|
let audioStreams = {};
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
460
|
+
|
|
461
|
+
if(audioSettings && Object.keys(audioSettings).length > 0) {
|
|
462
|
+
for(let i = 0; i < Object.keys(audioSettings).length; i ++) {
|
|
463
|
+
const audioIdx = Object.keys(audioSettings)[i];
|
|
464
|
+
const audio = audioSettings[audioIdx];
|
|
465
|
+
const profileAudioForType = ladderProfile?.audio.find(a => a.channels === (audio.channels ?? 2));
|
|
466
|
+
|
|
388
467
|
audioStreams[audioIdx] = {
|
|
389
|
-
recordingBitrate: audio.recording_bitrate
|
|
468
|
+
recordingBitrate: ladderProfile?.audio ? profileAudioForType.bit_rate : audio.recording_bitrate ?? 192000,
|
|
390
469
|
recordingChannels: audio.recording_channels || 2,
|
|
470
|
+
lang: audio.lang,
|
|
471
|
+
isDefault: audio.default
|
|
391
472
|
};
|
|
392
473
|
if(audio.playout) {
|
|
393
474
|
audioStreams[audioIdx].playoutLabel = audio.playout_label || `Audio ${i + 1}`;
|
|
@@ -396,21 +477,34 @@ class LiveConf {
|
|
|
396
477
|
}
|
|
397
478
|
|
|
398
479
|
// If no audio streams specified in custom config, set up all the suitable audio streams in the probe
|
|
399
|
-
if(!
|
|
400
|
-
audioStreams = this.getAudioStreamsFromProbe();
|
|
480
|
+
if(!audioSettings || Object.keys(audioSettings).length === 0) {
|
|
481
|
+
audioStreams = this.getAudioStreamsFromProbe({ladderProfile});
|
|
401
482
|
}
|
|
402
483
|
|
|
403
484
|
return audioStreams;
|
|
404
485
|
}
|
|
405
486
|
|
|
406
487
|
/*
|
|
407
|
-
* Generate the live recording config as required by QFAB, based on defaults and optional custom settings.
|
|
488
|
+
* Generate the live recording config as required by QFAB, based on defaults, existing settings and optional custom settings.
|
|
408
489
|
*/
|
|
409
490
|
generateLiveConf({customSettings}) {
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
491
|
+
// Saved config overrides defaults and is preserved on reconfiguration
|
|
492
|
+
let conf = R.clone(LiveconfTemplate);
|
|
493
|
+
|
|
494
|
+
if(this.currentLiveRecordingMeta) {
|
|
495
|
+
conf = R.mergeDeepRight(conf,
|
|
496
|
+
{live_recording: this.currentLiveRecordingMeta});
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if(customSettings.liveRecordingConfigProfile) {
|
|
500
|
+
conf = R.mergeDeepRight(conf,
|
|
501
|
+
this.MapCustomProfileToLiveConfig({
|
|
502
|
+
customProfile: customSettings.liveRecordingConfigProfile
|
|
503
|
+
}));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const fileName = this.url;
|
|
507
|
+
const audioStreams = this.generateAudioStreamsConfig({liveRecordingConfigProfile: customSettings.liveRecordingConfigProfile});
|
|
414
508
|
|
|
415
509
|
// Retrieve one audio stream from the probe to read the sample rate and codec name
|
|
416
510
|
const audioStream = this.getStreamDataForCodecType("audio");
|
|
@@ -440,15 +534,10 @@ class LiveConf {
|
|
|
440
534
|
}
|
|
441
535
|
|
|
442
536
|
// Fill in specifics for protocol
|
|
443
|
-
switch(this.
|
|
444
|
-
case "
|
|
445
|
-
sourceTimescale = 90000;
|
|
446
|
-
conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
|
|
447
|
-
break;
|
|
448
|
-
case "srt":
|
|
537
|
+
switch(this.getFormat()) {
|
|
538
|
+
case "mpegts":
|
|
449
539
|
sourceTimescale = 90000;
|
|
450
540
|
conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
|
|
451
|
-
conf.live_recording.recording_config.recording_params.live_delay_nano = 4000000000;
|
|
452
541
|
break;
|
|
453
542
|
case "rtmp":
|
|
454
543
|
sourceTimescale = 16000;
|
|
@@ -458,7 +547,15 @@ class LiveConf {
|
|
|
458
547
|
console.log("HLS detected. Not yet implemented");
|
|
459
548
|
break;
|
|
460
549
|
default:
|
|
461
|
-
console.log("Unsupported media", this.
|
|
550
|
+
console.log("Unsupported media", this.getFormat());
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
switch(this.getProtocol()) {
|
|
555
|
+
case "srt":
|
|
556
|
+
if(!customSettings.liveRecordingConfigProfile?.recording_params?.live_delay_nano) {
|
|
557
|
+
conf.live_recording.recording_config.recording_params.live_delay_nano = 6000000000;
|
|
558
|
+
}
|
|
462
559
|
break;
|
|
463
560
|
}
|
|
464
561
|
|
|
@@ -481,11 +578,15 @@ class LiveConf {
|
|
|
481
578
|
conf.live_recording.recording_config.recording_params.xc_params.video_frame_duration_ts = segDurations.videoFrameDurationTs;
|
|
482
579
|
}
|
|
483
580
|
|
|
484
|
-
const
|
|
581
|
+
const ladder_specs = customSettings.liveRecordingConfigProfile?.playout_config?.ladder_specs;
|
|
582
|
+
const ladderProfile = ladder_specs?.video?.length > 0 ? ladder_specs : DefaultABRLadder;
|
|
485
583
|
|
|
486
584
|
conf.live_recording.recording_config.recording_params.xc_params.enc_height = videoStream.height;
|
|
487
585
|
conf.live_recording.recording_config.recording_params.xc_params.enc_width = videoStream.width;
|
|
488
586
|
|
|
587
|
+
// Reset ladder specs (updating existing stream will carry over old specs
|
|
588
|
+
conf.live_recording.recording_config.recording_params.ladder_specs = [];
|
|
589
|
+
|
|
489
590
|
// Determine video recording bitrate and ABR ladder
|
|
490
591
|
let topLadderRate = 0;
|
|
491
592
|
for(let i = 0; i < ladderProfile.video.length; i ++) {
|
|
@@ -521,6 +622,7 @@ class LiveConf {
|
|
|
521
622
|
break;
|
|
522
623
|
}
|
|
523
624
|
}
|
|
625
|
+
|
|
524
626
|
if(Object.keys(audioLadderSpec).length === 0) {
|
|
525
627
|
// If no channels layout match, just use the first element in the ladder
|
|
526
628
|
audioLadderSpec = {...ladderProfile.audio[0]};
|
|
@@ -532,8 +634,10 @@ class LiveConf {
|
|
|
532
634
|
audioLadderSpec.stream_name = `audio_${audioIndex}`;
|
|
533
635
|
audioLadderSpec.stream_label = audio.playoutLabel ? audio.playoutLabel : null;
|
|
534
636
|
audioLadderSpec.media_type = 2;
|
|
637
|
+
audioLadderSpec.lang = audio.lang ?? "";
|
|
535
638
|
|
|
536
|
-
|
|
639
|
+
|
|
640
|
+
if(Object.keys(audioStreams).length === 1 || audio.isDefault) {
|
|
537
641
|
audioLadderSpec.default = true;
|
|
538
642
|
}
|
|
539
643
|
|
|
@@ -541,33 +645,13 @@ class LiveConf {
|
|
|
541
645
|
if(audio.recordingBitrate > globalAudioBitrate) {
|
|
542
646
|
globalAudioBitrate = audio.recordingBitrate;
|
|
543
647
|
}
|
|
544
|
-
nAudio
|
|
648
|
+
nAudio++;
|
|
545
649
|
}
|
|
546
650
|
|
|
547
651
|
// Global recording bitrate for all audio streams
|
|
548
652
|
conf.live_recording.recording_config.recording_params.xc_params.audio_bitrate = globalAudioBitrate;
|
|
549
653
|
conf.live_recording.recording_config.recording_params.xc_params.n_audio = nAudio;
|
|
550
654
|
|
|
551
|
-
// Iterate through custom settings (which will override any existing setting)
|
|
552
|
-
function SetByPath({obj, path, value}) {
|
|
553
|
-
const keys = path.split(".");
|
|
554
|
-
let temp = obj;
|
|
555
|
-
for(let i = 0; i < keys.length - 1; i++) {
|
|
556
|
-
if(!temp[keys[i]]) {
|
|
557
|
-
temp[keys[i]] = {};
|
|
558
|
-
}
|
|
559
|
-
temp = temp[keys[i]];
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
temp[keys[keys.length - 1]] = value;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const {metaPathValues} = customSettings;
|
|
566
|
-
|
|
567
|
-
for(let [path, value] of Object.entries(metaPathValues || {})) {
|
|
568
|
-
SetByPath({obj: conf, path, value});
|
|
569
|
-
}
|
|
570
|
-
|
|
571
655
|
return conf;
|
|
572
656
|
}
|
|
573
657
|
}
|