@eluvio/elv-client-js 4.0.77 → 4.0.79
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 +5 -5
- package/dist/ElvClient-node-min.js +9 -9
- package/dist/ElvFrameClient-min.js +2 -2
- package/dist/ElvWalletClient-min.js +7 -7
- package/dist/ElvWalletClient-node-min.js +11 -11
- package/dist/src/FrameClient.js +1 -1
- package/dist/src/HttpClient.js +15 -7
- package/dist/src/UserProfileClient.js +36 -71
- package/dist/src/client/AccessGroups.js +39 -35
- package/dist/src/client/ContentAccess.js +47 -27
- package/dist/src/client/ContentManagement.js +100 -40
- package/dist/src/client/Contracts.js +268 -4
- package/dist/src/client/LiveConf.js +151 -20
- package/dist/src/client/LiveStream.js +384 -231
- package/package.json +1 -1
- package/src/FrameClient.js +2 -0
- package/src/HttpClient.js +6 -0
- package/src/UserProfileClient.js +9 -38
- package/src/client/AccessGroups.js +19 -24
- package/src/client/ContentAccess.js +22 -1
- package/src/client/ContentManagement.js +44 -22
- package/src/client/Contracts.js +157 -2
- package/src/client/LiveConf.js +132 -16
- package/src/client/LiveStream.js +182 -77
- package/testScripts/Test.js +7 -0
- package/testScripts/TestAddTenantContractId.js +87 -0
package/src/client/LiveConf.js
CHANGED
|
@@ -107,6 +107,7 @@ const LiveconfTemplate = {
|
|
|
107
107
|
sync_audio_to_stream_id: -1,
|
|
108
108
|
video_bitrate: null,
|
|
109
109
|
video_seg_duration_ts: null,
|
|
110
|
+
video_time_base: null,
|
|
110
111
|
xc_type: 3
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -160,56 +161,156 @@ class LiveConf {
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
|
|
164
|
+
/*
|
|
165
|
+
* Calculates mez segment durations based on input stream parameters
|
|
166
|
+
*
|
|
167
|
+
* Live input formats have fixed timebase:
|
|
168
|
+
* - MPEG-TS/SRT input stream timebase is 90000
|
|
169
|
+
* - RTMP input stream timebase is 1000 and gets translated to 16000 if not otherwise specified
|
|
170
|
+
*
|
|
171
|
+
* This causes frame duration irregularities for certain frame rates.
|
|
172
|
+
* For example RTMP 60fps has frames of durations 16 and 17. MPEG-TS 59.94fps has frames of
|
|
173
|
+
* durations 1001 and 1002.
|
|
174
|
+
*
|
|
175
|
+
* Live mez segmentation requires that the segment be cut at the specific number of frames, and when
|
|
176
|
+
* the frame durations are irregular we adjust both the video timebase and the video frame duration
|
|
177
|
+
* to make the math possible. This adjustment is also required for live-to-vod conversion.
|
|
178
|
+
*
|
|
179
|
+
* For example for MPEG-TS 59.94fps, the mez segment timebase needs to be 60000
|
|
180
|
+
* (and resulting frame duration is 1001) and for RTMP 60fps the timebase needs to be 15360 (resulting frame
|
|
181
|
+
* duration is 256).
|
|
182
|
+
*
|
|
183
|
+
* @sourceTimescale - adjusted source video stream timescale (eg. MPEGTS 90000, RTMP 16000 )
|
|
184
|
+
* @sampleRate - audio sample rate (commonly 48000 but can be different)
|
|
185
|
+
* @audioCodec - audio codec as a string (eg. "aac")
|
|
186
|
+
* @return - segment encoding parameters
|
|
187
|
+
*/
|
|
188
|
+
calcSegDuration({sourceTimescale, sampleRate, audioCodec}) {
|
|
189
|
+
let seg = {};
|
|
190
|
+
|
|
191
|
+
switch(this.probeKind()) {
|
|
192
|
+
case "rtmp":
|
|
193
|
+
seg = this.calcSegDurationRtmp({sourceTimescale, sampleRate, audioCodec});
|
|
194
|
+
break;
|
|
195
|
+
case "udp":
|
|
196
|
+
case "srt":
|
|
197
|
+
seg = this.calcSegDurationMpegts({sourceTimescale, sampleRate, audioCodec});
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
throw "protocol not supported - " + this.probeKind();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if(audioCodec == "aac") {
|
|
204
|
+
seg.audio = 29.76 * sampleRate;
|
|
205
|
+
} else {
|
|
206
|
+
seg.audio = 29.76 * 48000; // Other codecs are resampled @48000
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return seg;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
calcSegDurationMpegts({sourceTimescale}) {
|
|
164
213
|
let videoStream = this.getStreamDataForCodecType("video");
|
|
165
214
|
let frameRate = videoStream.frame_rate;
|
|
215
|
+
let seg = {};
|
|
216
|
+
|
|
217
|
+
switch(frameRate) {
|
|
218
|
+
case "24":
|
|
219
|
+
seg.video = sourceTimescale * 30;
|
|
220
|
+
seg.keyint = 48;
|
|
221
|
+
seg.duration = "30";
|
|
222
|
+
break;
|
|
223
|
+
case "25":
|
|
224
|
+
seg.video = sourceTimescale * 30;
|
|
225
|
+
seg.keyint = 50;
|
|
226
|
+
seg.duration = "30";
|
|
227
|
+
break;
|
|
228
|
+
case "30":
|
|
229
|
+
seg.video = sourceTimescale * 30;
|
|
230
|
+
seg.keyint = 60;
|
|
231
|
+
seg.duration = "30";
|
|
232
|
+
break;
|
|
233
|
+
case "30000/1001":
|
|
234
|
+
seg.video = sourceTimescale * 30;
|
|
235
|
+
seg.keyint = 60;
|
|
236
|
+
seg.duration = "30.03";
|
|
237
|
+
break;
|
|
238
|
+
case "48":
|
|
239
|
+
seg.video = sourceTimescale * 30;
|
|
240
|
+
seg.keyint = 96;
|
|
241
|
+
seg.duration = "30";
|
|
242
|
+
break;
|
|
243
|
+
case "50":
|
|
244
|
+
seg.video = sourceTimescale * 30;
|
|
245
|
+
seg.keyint = 100;
|
|
246
|
+
seg.duration = "30";
|
|
247
|
+
break;
|
|
248
|
+
case "60":
|
|
249
|
+
seg.video = sourceTimescale * 30;
|
|
250
|
+
seg.keyint = 120;
|
|
251
|
+
seg.duration = "30";
|
|
252
|
+
break;
|
|
253
|
+
case "60000/1001":
|
|
254
|
+
seg.videoTimeBase = 60000;
|
|
255
|
+
seg.video = seg.videoTimeBase * 30.03;
|
|
256
|
+
seg.keyint = 120;
|
|
257
|
+
seg.duration = "30.03";
|
|
258
|
+
break;
|
|
259
|
+
default:
|
|
260
|
+
throw "unsupported frame rate for MPEGTS - " + frameRate;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
return seg;
|
|
264
|
+
}
|
|
166
265
|
|
|
266
|
+
calcSegDurationRtmp({sourceTimescale}) {
|
|
267
|
+
let videoStream = this.getStreamDataForCodecType("video");
|
|
268
|
+
let frameRate = videoStream.frame_rate;
|
|
167
269
|
let seg = {};
|
|
168
|
-
seg.audio = 29.76 * 48000;
|
|
169
270
|
|
|
170
271
|
switch(frameRate) {
|
|
171
272
|
case "24":
|
|
172
|
-
seg.video =
|
|
273
|
+
seg.video = sourceTimescale * 30;
|
|
173
274
|
seg.keyint = 48;
|
|
174
275
|
seg.duration = "30";
|
|
175
276
|
break;
|
|
176
277
|
case "25":
|
|
177
|
-
seg.video =
|
|
278
|
+
seg.video = sourceTimescale * 30;
|
|
178
279
|
seg.keyint = 50;
|
|
179
280
|
seg.duration = "30";
|
|
180
281
|
break;
|
|
181
282
|
case "30":
|
|
182
|
-
seg.video =
|
|
283
|
+
seg.video = sourceTimescale * 30;
|
|
183
284
|
seg.keyint = 60;
|
|
184
285
|
seg.duration = "30";
|
|
185
286
|
break;
|
|
186
287
|
case "30000/1001":
|
|
187
|
-
seg.video = 30.03
|
|
288
|
+
seg.video = sourceTimescale * 30.03;
|
|
188
289
|
seg.keyint = 60;
|
|
189
290
|
seg.duration = "30.03";
|
|
190
291
|
break;
|
|
191
292
|
case "48":
|
|
192
|
-
seg.video =
|
|
293
|
+
seg.video = sourceTimescale * 30;
|
|
193
294
|
seg.keyint = 96;
|
|
194
295
|
seg.duration = "30";
|
|
195
296
|
break;
|
|
196
297
|
case "50":
|
|
197
|
-
seg.video =
|
|
298
|
+
seg.video = sourceTimescale * 30;
|
|
198
299
|
seg.keyint = 100;
|
|
199
300
|
seg.duration = "30";
|
|
200
301
|
break;
|
|
201
302
|
case "60":
|
|
202
|
-
seg.video =
|
|
303
|
+
seg.video = sourceTimescale * 30;
|
|
203
304
|
seg.keyint = 120;
|
|
204
305
|
seg.duration = "30";
|
|
205
306
|
break;
|
|
206
307
|
case "60000/1001":
|
|
207
|
-
seg.video = 30.03
|
|
308
|
+
seg.video = sourceTimescale * 30.03;
|
|
208
309
|
seg.keyint = 120;
|
|
209
310
|
seg.duration = "30.03";
|
|
210
311
|
break;
|
|
211
312
|
default:
|
|
212
|
-
|
|
313
|
+
throw "unsupported frame rate for RTMP - " + frameRate;
|
|
213
314
|
break;
|
|
214
315
|
}
|
|
215
316
|
return seg;
|
|
@@ -220,10 +321,11 @@ class LiveConf {
|
|
|
220
321
|
let videoStream = this.getStreamDataForCodecType("video");
|
|
221
322
|
switch(this.probeKind()) {
|
|
222
323
|
case "udp":
|
|
324
|
+
case "srt":
|
|
223
325
|
sync_id = videoStream.stream_id;
|
|
224
326
|
break;
|
|
225
327
|
case "rtmp":
|
|
226
|
-
sync_id =
|
|
328
|
+
sync_id = videoStream.stream_index;
|
|
227
329
|
break;
|
|
228
330
|
}
|
|
229
331
|
return sync_id;
|
|
@@ -234,14 +336,14 @@ class LiveConf {
|
|
|
234
336
|
const conf = JSON.parse(JSON.stringify(LiveconfTemplate));
|
|
235
337
|
const fileName = this.overwriteOriginUrl || this.probeData.format.filename;
|
|
236
338
|
const audioStream = this.getStreamDataForCodecType("audio");
|
|
339
|
+
|
|
237
340
|
const sampleRate = parseInt(audioStream.sample_rate);
|
|
341
|
+
const audioCodec = audioStream.codec_name;
|
|
238
342
|
const videoStream = this.getStreamDataForCodecType("video");
|
|
239
343
|
let sourceTimescale;
|
|
240
344
|
|
|
241
|
-
console.log("AUDIO", audioStream);
|
|
242
|
-
console.log("VIDEO", videoStream);
|
|
243
|
-
|
|
244
345
|
// Fill in liveconf all formats have in common
|
|
346
|
+
conf.live_recording.probe_info = this.probeData;
|
|
245
347
|
conf.live_recording.fabric_config.ingress_node_api = this.nodeUrl || null;
|
|
246
348
|
conf.live_recording.fabric_config.ingress_node_id = this.nodeId || null;
|
|
247
349
|
conf.live_recording.recording_config.recording_params.description;
|
|
@@ -267,6 +369,11 @@ class LiveConf {
|
|
|
267
369
|
sourceTimescale = 90000;
|
|
268
370
|
conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
|
|
269
371
|
break;
|
|
372
|
+
case "srt":
|
|
373
|
+
sourceTimescale = 90000;
|
|
374
|
+
conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
|
|
375
|
+
conf.live_recording.recording_config.recording_params.live_delay_nano = 4000000000;
|
|
376
|
+
break;
|
|
270
377
|
case "rtmp":
|
|
271
378
|
sourceTimescale = 16000;
|
|
272
379
|
conf.live_recording.recording_config.recording_params.source_timescale = sourceTimescale;
|
|
@@ -279,7 +386,7 @@ class LiveConf {
|
|
|
279
386
|
break;
|
|
280
387
|
}
|
|
281
388
|
|
|
282
|
-
const segDurations = this.calcSegDuration({sourceTimescale});
|
|
389
|
+
const segDurations = this.calcSegDuration({sourceTimescale, sampleRate, audioCodec});
|
|
283
390
|
|
|
284
391
|
// Segment conditioning parameters
|
|
285
392
|
conf.live_recording.recording_config.recording_params.xc_params.seg_duration = segDurations.duration;
|
|
@@ -287,6 +394,15 @@ class LiveConf {
|
|
|
287
394
|
conf.live_recording.recording_config.recording_params.xc_params.video_seg_duration_ts = segDurations.video;
|
|
288
395
|
conf.live_recording.recording_config.recording_params.xc_params.force_keyint = segDurations.keyint;
|
|
289
396
|
|
|
397
|
+
// Optional override output timebase and frame duration (ts)
|
|
398
|
+
if(segDurations.videoTimeBase) {
|
|
399
|
+
conf.live_recording.recording_config.recording_params.xc_params.video_time_base = segDurations.videoTimeBase;
|
|
400
|
+
conf.live_recording.recording_config.recording_params.source_timescale = segDurations.videoTimeBase;
|
|
401
|
+
}
|
|
402
|
+
if(segDurations.videoFrameDurationTs) {
|
|
403
|
+
conf.live_recording.recording_config.recording_params.xc_params.video_frame_duration_ts = segDurations.videoFrameDurationTs;
|
|
404
|
+
}
|
|
405
|
+
|
|
290
406
|
switch(videoStream.height) {
|
|
291
407
|
case 2160:
|
|
292
408
|
conf.live_recording.recording_config.recording_params.ladder_specs.unshift(
|
package/src/client/LiveStream.js
CHANGED
|
@@ -136,9 +136,9 @@ const StreamGenerateOffering = async({
|
|
|
136
136
|
const maxStreamIndex = Math.max(aStreamIndex, vStreamIndex);
|
|
137
137
|
|
|
138
138
|
for(let i = 0; i <= maxStreamIndex; i++) {
|
|
139
|
-
if
|
|
139
|
+
if(i === aStreamIndex) {
|
|
140
140
|
sourceStreams.push(sourceAudioStream);
|
|
141
|
-
} else if
|
|
141
|
+
} else if(i === vStreamIndex) {
|
|
142
142
|
sourceStreams.push(sourceVideoStream);
|
|
143
143
|
} else {
|
|
144
144
|
sourceStreams.push(DUMMY_STREAM);
|
|
@@ -242,12 +242,12 @@ const StreamGenerateOffering = async({
|
|
|
242
242
|
abrProfile
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
if
|
|
245
|
+
if(createResponse.warnings.length > 0) {
|
|
246
246
|
console.log("WARNINGS:");
|
|
247
247
|
console.log(JSON.stringify(createResponse.warnings, null, 2));
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
if
|
|
250
|
+
if(createResponse.errors.length > 0) {
|
|
251
251
|
console.log("ERRORS:");
|
|
252
252
|
console.log(JSON.stringify(createResponse.errors, null, 2));
|
|
253
253
|
}
|
|
@@ -339,6 +339,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
339
339
|
]
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
+
status.reference_url = mainMeta.live_recording_config.reference_url;
|
|
343
|
+
|
|
342
344
|
if(mainMeta.live_recording_config == undefined || mainMeta.live_recording_config.url == undefined) {
|
|
343
345
|
status.state = "unconfigured";
|
|
344
346
|
return status;
|
|
@@ -367,7 +369,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
367
369
|
status.url = mainMeta.live_recording.recording_config.recording_params.origin_url;
|
|
368
370
|
|
|
369
371
|
let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
370
|
-
if(edgeWriteToken
|
|
372
|
+
if(!edgeWriteToken) {
|
|
371
373
|
status.state = "inactive";
|
|
372
374
|
return status;
|
|
373
375
|
}
|
|
@@ -688,8 +690,6 @@ exports.StreamCreate = async function({name, start=false}) {
|
|
|
688
690
|
*/
|
|
689
691
|
exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
690
692
|
try {
|
|
691
|
-
console.log("Stream ", op, ": ", name);
|
|
692
|
-
|
|
693
693
|
let status = await this.StreamStatus({name});
|
|
694
694
|
if(status.state != "stopped") {
|
|
695
695
|
if(op === "start") {
|
|
@@ -699,8 +699,6 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
699
699
|
}
|
|
700
700
|
|
|
701
701
|
if(status.state == "running" || status.state == "starting" || status.state == "stalled") {
|
|
702
|
-
console.log("STOPPING");
|
|
703
|
-
|
|
704
702
|
try {
|
|
705
703
|
await this.CallBitcodeMethod({
|
|
706
704
|
libraryId: status.library_id,
|
|
@@ -769,8 +767,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
769
767
|
};
|
|
770
768
|
|
|
771
769
|
/**
|
|
772
|
-
*
|
|
773
|
-
* Not implemented fully
|
|
770
|
+
* Close the edge write token and make the stream object inactive.
|
|
774
771
|
*
|
|
775
772
|
* @methodGroup Live Stream
|
|
776
773
|
* @namedParams
|
|
@@ -780,8 +777,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
780
777
|
*/
|
|
781
778
|
exports.StreamStopSession = async function({name}) {
|
|
782
779
|
try {
|
|
783
|
-
|
|
784
|
-
|
|
780
|
+
this.Log(`Terminating stream session for: ${name}`);
|
|
785
781
|
let conf = await this.LoadConf({name});
|
|
786
782
|
|
|
787
783
|
let {objectId} = conf;
|
|
@@ -801,86 +797,81 @@ exports.StreamStopSession = async function({name}) {
|
|
|
801
797
|
|
|
802
798
|
this.SetNodes({fabricURIs: [fabURI]});
|
|
803
799
|
|
|
804
|
-
|
|
800
|
+
const metaEdgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
805
801
|
|
|
806
|
-
if(
|
|
802
|
+
if(!metaEdgeWriteToken) {
|
|
807
803
|
return {
|
|
808
804
|
state: "inactive",
|
|
809
|
-
error: "
|
|
805
|
+
error: "The stream is not active"
|
|
810
806
|
};
|
|
811
807
|
}
|
|
812
|
-
let edgeMeta = await this.ContentObjectMetadata({
|
|
813
|
-
libraryId,
|
|
814
|
-
objectId,
|
|
815
|
-
writeToken: edgeWriteToken
|
|
816
|
-
});
|
|
817
808
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
libraryId: status.library_id,
|
|
825
|
-
objectId: status.object_id,
|
|
826
|
-
writeToken: status.edge_write_token,
|
|
827
|
-
method: "/live/stop/" + status.tlro,
|
|
828
|
-
constant: false
|
|
829
|
-
});
|
|
830
|
-
} catch(error) {
|
|
831
|
-
// The /call/lro/stop API returns empty response
|
|
832
|
-
// console.log("LRO Stop (failed): ", error);
|
|
833
|
-
}
|
|
809
|
+
try {
|
|
810
|
+
const streamMetadata = await this.ContentObjectMetadata({
|
|
811
|
+
libraryId,
|
|
812
|
+
objectId,
|
|
813
|
+
writeToken: metaEdgeWriteToken
|
|
814
|
+
});
|
|
834
815
|
|
|
835
|
-
|
|
836
|
-
let tries = 10;
|
|
837
|
-
while (status.state != "stopped" && tries-- > 0) {
|
|
838
|
-
console.log("Wait to terminate - ", status.state);
|
|
839
|
-
await Sleep(1000);
|
|
840
|
-
status = await this.StreamStatus({name});
|
|
841
|
-
}
|
|
842
|
-
console.log("Status after stop - ", status.state);
|
|
816
|
+
const status = await this.StreamStatus({name});
|
|
843
817
|
|
|
844
|
-
if(
|
|
845
|
-
|
|
846
|
-
|
|
818
|
+
if(status.state !== "stopped") {
|
|
819
|
+
return {
|
|
820
|
+
state: status.state,
|
|
821
|
+
error: "The stream must be stopped before terminating"
|
|
822
|
+
}
|
|
847
823
|
}
|
|
824
|
+
|
|
825
|
+
await this.DeleteWriteToken({
|
|
826
|
+
libraryId,
|
|
827
|
+
writeToken: metaEdgeWriteToken
|
|
828
|
+
});
|
|
829
|
+
} catch(error) {
|
|
830
|
+
this.Log(`Unable to retrieve metadata for edge write token ${edgeWriteToken}`);
|
|
848
831
|
}
|
|
849
832
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
833
|
+
const {writeToken} = await this.EditContentObject({
|
|
834
|
+
libraryId: libraryId,
|
|
835
|
+
objectId: objectId
|
|
836
|
+
});
|
|
854
837
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
};
|
|
838
|
+
// Set stop time and inactive state
|
|
839
|
+
const newState = "inactive";
|
|
840
|
+
const stopTime = Math.floor(new Date().getTime() / 1000);
|
|
859
841
|
|
|
860
|
-
|
|
842
|
+
const finalizeMetadata = {
|
|
843
|
+
live_recording: {
|
|
844
|
+
status: {
|
|
845
|
+
edge_write_token: "",
|
|
846
|
+
state: newState,
|
|
847
|
+
recording_stop_time: stopTime
|
|
848
|
+
},
|
|
849
|
+
fabric_config: {
|
|
850
|
+
edge_write_token: ""
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
recording_stop_time: stopTime
|
|
854
|
+
};
|
|
861
855
|
|
|
862
|
-
await this.
|
|
856
|
+
await this.MergeMetadata({
|
|
863
857
|
libraryId,
|
|
864
858
|
objectId,
|
|
865
|
-
writeToken
|
|
866
|
-
metadata:
|
|
859
|
+
writeToken,
|
|
860
|
+
metadata: finalizeMetadata
|
|
867
861
|
});
|
|
868
862
|
|
|
869
863
|
let fin = await this.FinalizeContentObject({
|
|
870
864
|
libraryId,
|
|
871
865
|
objectId,
|
|
872
|
-
writeToken
|
|
873
|
-
commitMessage:
|
|
874
|
-
publish: false // Don't publish this version because it is not currently useful
|
|
866
|
+
writeToken,
|
|
867
|
+
commitMessage: `Deactivate live stream - stop time ${stopTime}`
|
|
875
868
|
});
|
|
876
869
|
|
|
877
870
|
return {
|
|
878
871
|
fin,
|
|
879
872
|
name,
|
|
880
|
-
|
|
881
|
-
state: "terminated"
|
|
873
|
+
state: newState
|
|
882
874
|
};
|
|
883
|
-
|
|
884
875
|
} catch(error) {
|
|
885
876
|
console.error(error);
|
|
886
877
|
}
|
|
@@ -1202,7 +1193,7 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
|
|
|
1202
1193
|
playout: "/qfab/" + targetHash + "/rep/playout" // TO FIX - should be a link
|
|
1203
1194
|
};
|
|
1204
1195
|
|
|
1205
|
-
for
|
|
1196
|
+
for(let i = 0; i < insertions.length; i ++) {
|
|
1206
1197
|
if(insertions[i].insertion_time <= currentTime) {
|
|
1207
1198
|
// Bad insertion - must be later than current time
|
|
1208
1199
|
append(errs, "Bad insertion - time:", insertions[i].insertion_time);
|
|
@@ -1325,9 +1316,10 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1325
1316
|
|
|
1326
1317
|
let userConfig = mainMeta.live_recording_config;
|
|
1327
1318
|
status.user_config = userConfig;
|
|
1319
|
+
console.log("userConfig", userConfig);
|
|
1328
1320
|
|
|
1329
1321
|
// Get node URI from user config
|
|
1330
|
-
const hostName = userConfig.url.replace("udp://", "").replace("rtmp://", "").split(":")[0];
|
|
1322
|
+
const hostName = userConfig.url.replace("udp://", "").replace("rtmp://", "").replace("srt://", "").split(":")[0];
|
|
1331
1323
|
const streamUrl = new URL(userConfig.url);
|
|
1332
1324
|
|
|
1333
1325
|
console.log("Retrieving nodes...");
|
|
@@ -1380,7 +1372,6 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1380
1372
|
}
|
|
1381
1373
|
}
|
|
1382
1374
|
|
|
1383
|
-
console.log("PROBE", probe);
|
|
1384
1375
|
probe.format.filename = streamUrl.href;
|
|
1385
1376
|
|
|
1386
1377
|
// Create live recording config
|
|
@@ -1410,14 +1401,6 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1410
1401
|
metadata: liveRecordingConfig.live_recording
|
|
1411
1402
|
});
|
|
1412
1403
|
|
|
1413
|
-
await this.ReplaceMetadata({
|
|
1414
|
-
libraryId,
|
|
1415
|
-
objectId: conf.objectId,
|
|
1416
|
-
writeToken,
|
|
1417
|
-
metadataSubtree: "probe",
|
|
1418
|
-
metadata: probe
|
|
1419
|
-
});
|
|
1420
|
-
|
|
1421
1404
|
status.fin = await this.FinalizeContentObject({
|
|
1422
1405
|
libraryId,
|
|
1423
1406
|
objectId: conf.objectId,
|
|
@@ -1427,3 +1410,125 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1427
1410
|
|
|
1428
1411
|
return status;
|
|
1429
1412
|
};
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* List the pre-allocated URLs for a site
|
|
1416
|
+
*
|
|
1417
|
+
* @methodGroup Live Stream
|
|
1418
|
+
* @namedParams
|
|
1419
|
+
* @param {string=} - ID of the live stream site object
|
|
1420
|
+
*
|
|
1421
|
+
* @return {Promise<Object>} - The list of stream URLs
|
|
1422
|
+
*/
|
|
1423
|
+
exports.StreamListUrls = async function({siteId}={}) {
|
|
1424
|
+
try {
|
|
1425
|
+
const STATUS_MAP = {
|
|
1426
|
+
UNCONFIGURED: "unconfigured",
|
|
1427
|
+
UNINITIALIZED: "uninitialized",
|
|
1428
|
+
INACTIVE: "inactive",
|
|
1429
|
+
STOPPED: "stopped",
|
|
1430
|
+
STARTING: "starting",
|
|
1431
|
+
RUNNING: "running",
|
|
1432
|
+
STALLED: "stalled",
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
if(!siteId) {
|
|
1436
|
+
const tenantContractId = await this.userProfileClient.TenantContractId();
|
|
1437
|
+
|
|
1438
|
+
if(!tenantContractId) {
|
|
1439
|
+
throw Error("No tenant contract ID configured");
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
siteId = await this.ContentObjectMetadata({
|
|
1443
|
+
libraryId: tenantContractId.replace("iten", "ilib"),
|
|
1444
|
+
objectId: tenantContractId.replace("iten", "iq__"),
|
|
1445
|
+
metadataSubtree: "public/sites/live_streams",
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const streamMetadata = await this.ContentObjectMetadata({
|
|
1450
|
+
libraryId: await this.ContentObjectLibraryId({objectId: siteId}),
|
|
1451
|
+
objectId: siteId,
|
|
1452
|
+
metadataSubtree: "public/asset_metadata/live_streams",
|
|
1453
|
+
resolveLinks: true,
|
|
1454
|
+
resolveIgnoreErrors: true
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
const activeUrlMap = {};
|
|
1458
|
+
await this.utils.LimitedMap(
|
|
1459
|
+
10,
|
|
1460
|
+
Object.keys(streamMetadata || {}),
|
|
1461
|
+
async slug => {
|
|
1462
|
+
const stream = streamMetadata[slug];
|
|
1463
|
+
let versionHash;
|
|
1464
|
+
|
|
1465
|
+
if(
|
|
1466
|
+
stream &&
|
|
1467
|
+
stream.sources &&
|
|
1468
|
+
stream.sources.default &&
|
|
1469
|
+
stream.sources.default["."] &&
|
|
1470
|
+
stream.sources.default["."].container ||
|
|
1471
|
+
((stream["/"] || "").match(/^\/?qfab\/([\w]+)\/?.+/) || [])[1]
|
|
1472
|
+
) {
|
|
1473
|
+
versionHash = (
|
|
1474
|
+
stream.sources.default["."].container ||
|
|
1475
|
+
((stream["/"] || "").match(/^\/?qfab\/([\w]+)\/?.+/) || [])[1]
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
if(versionHash) {
|
|
1480
|
+
const objectId = this.utils.DecodeVersionHash(versionHash).objectId;
|
|
1481
|
+
const libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1482
|
+
|
|
1483
|
+
const status = await this.StreamStatus({
|
|
1484
|
+
name: objectId
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
const streamMeta = await this.ContentObjectMetadata({
|
|
1488
|
+
objectId,
|
|
1489
|
+
libraryId,
|
|
1490
|
+
select: [
|
|
1491
|
+
"live_recording_config/reference_url",
|
|
1492
|
+
// live_recording_config/url is the old path
|
|
1493
|
+
"live_recording_config/url"
|
|
1494
|
+
]
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
const url = streamMeta.live_recording_config.reference_url || streamMeta.live_recording_config.url;
|
|
1498
|
+
const isActive = [STATUS_MAP.STARTING, STATUS_MAP.RUNNING, STATUS_MAP.STALLED, STATUS_MAP.STOPPED].includes(status.state);
|
|
1499
|
+
|
|
1500
|
+
if(url && isActive) {
|
|
1501
|
+
activeUrlMap[url] = true;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
);
|
|
1506
|
+
|
|
1507
|
+
const streamUrlStatus = {};
|
|
1508
|
+
|
|
1509
|
+
const streamUrls = await this.ContentObjectMetadata({
|
|
1510
|
+
libraryId: await this.ContentObjectLibraryId({objectId: siteId}),
|
|
1511
|
+
objectId: siteId,
|
|
1512
|
+
metadataSubtree: "/live_stream_urls",
|
|
1513
|
+
resolveLinks: true,
|
|
1514
|
+
resolveIgnoreErrors: true
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
if(!streamUrls) {
|
|
1518
|
+
throw Error("No pre-allocated URLs configured");
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
Object.keys(streamUrls || {}).forEach(protocol => {
|
|
1522
|
+
streamUrlStatus[protocol] = streamUrls[protocol].map(url => {
|
|
1523
|
+
return {
|
|
1524
|
+
url,
|
|
1525
|
+
active: activeUrlMap[url] || false
|
|
1526
|
+
};
|
|
1527
|
+
})
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
return streamUrlStatus;
|
|
1531
|
+
} catch(error) {
|
|
1532
|
+
console.error(error);
|
|
1533
|
+
}
|
|
1534
|
+
};
|
package/testScripts/Test.js
CHANGED
|
@@ -16,6 +16,13 @@ const Test = async () => {
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
client.SetSigner({signer});
|
|
19
|
+
|
|
20
|
+
const versionHash = "";
|
|
21
|
+
|
|
22
|
+
const url = await client.FabricUrl({
|
|
23
|
+
versionHash
|
|
24
|
+
});
|
|
25
|
+
console.log("url", url)
|
|
19
26
|
} catch(error) {
|
|
20
27
|
console.error(error);
|
|
21
28
|
console.error(JSON.stringify(error, null, 2));
|