@eluvio/elv-client-js 4.0.82 → 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/dist/ElvClient-min.js +11 -11
- package/dist/ElvClient-node-min.js +10 -10
- package/dist/ElvFrameClient-min.js +1 -1
- package/dist/ElvWalletClient-min.js +11 -11
- package/dist/ElvWalletClient-node-min.js +10 -10
- package/dist/src/FrameClient.js +1 -1
- package/dist/src/abr_profiles/abr_profile_live_to_vod.js +7 -0
- package/dist/src/client/LiveConf.js +118 -31
- package/dist/src/client/LiveStream.js +1053 -528
- package/package.json +1 -1
- package/src/FrameClient.js +3 -0
- package/src/abr_profiles/abr_profile_live_to_vod.js +9 -0
- package/src/client/LiveConf.js +113 -28
- package/src/client/LiveStream.js +555 -153
- package/testScripts/DecodeSignedMessage.js +45 -0
package/src/client/LiveStream.js
CHANGED
|
@@ -9,6 +9,7 @@ const path = require("path");
|
|
|
9
9
|
const fs = require("fs");
|
|
10
10
|
const HttpClient = require("../HttpClient");
|
|
11
11
|
const Fraction = require("fraction.js");
|
|
12
|
+
const {ValidateObject, ValidatePresence} = require("../Validation");
|
|
12
13
|
|
|
13
14
|
const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
|
|
14
15
|
const tok = await client.authClient.AuthorizationToken({libraryId, objectId,
|
|
@@ -22,6 +23,41 @@ const Sleep = (ms) => {
|
|
|
22
23
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
const CueInfo = async ({eventId, status}) => {
|
|
27
|
+
let cues;
|
|
28
|
+
try {
|
|
29
|
+
const lroStatusResponse = await this.utils.ResponseToJson(
|
|
30
|
+
await HttpClient.Fetch(status.lro_status_url)
|
|
31
|
+
);
|
|
32
|
+
console.log("lroStatusResponse", lroStatusResponse)
|
|
33
|
+
cues = lroStatusResponse.custom.cues;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.log("LRO status failed", error);
|
|
36
|
+
return {error: "failed to retrieve status", eventId};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let eventStart, eventEnd;
|
|
40
|
+
for (const value of Object.values(cues)) {
|
|
41
|
+
for (const event of Object.values(value.descriptors)) {
|
|
42
|
+
if (event.id == eventId) {
|
|
43
|
+
switch (event.type_id) {
|
|
44
|
+
case 32:
|
|
45
|
+
case 16:
|
|
46
|
+
eventStart = value.insertion_time;
|
|
47
|
+
break;
|
|
48
|
+
case 33:
|
|
49
|
+
case 17:
|
|
50
|
+
eventEnd = value.insertion_time;
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {eventStart, eventEnd, eventId};
|
|
59
|
+
}
|
|
60
|
+
|
|
25
61
|
/**
|
|
26
62
|
* Set the offering for the live stream
|
|
27
63
|
*
|
|
@@ -322,17 +358,17 @@ const StreamGenerateOffering = async({
|
|
|
322
358
|
* @return {Promise<Object>} - The status response for the object, as well as logs, warnings and errors from the master initialization
|
|
323
359
|
*/
|
|
324
360
|
exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
325
|
-
let
|
|
361
|
+
let objectId = name;
|
|
326
362
|
let status = {name: name};
|
|
327
363
|
|
|
328
364
|
try {
|
|
329
|
-
let libraryId = await this.ContentObjectLibraryId({objectId
|
|
365
|
+
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
330
366
|
status.library_id = libraryId;
|
|
331
|
-
status.object_id =
|
|
367
|
+
status.object_id = objectId;
|
|
332
368
|
|
|
333
369
|
let mainMeta = await this.ContentObjectMetadata({
|
|
334
|
-
libraryId
|
|
335
|
-
objectId
|
|
370
|
+
libraryId,
|
|
371
|
+
objectId,
|
|
336
372
|
select: [
|
|
337
373
|
"live_recording_config",
|
|
338
374
|
"live_recording"
|
|
@@ -380,7 +416,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
380
416
|
status.stream_id = edgeWriteToken; // By convention the stream ID is its write token
|
|
381
417
|
let edgeMeta = await this.ContentObjectMetadata({
|
|
382
418
|
libraryId: libraryId,
|
|
383
|
-
objectId:
|
|
419
|
+
objectId: objectId,
|
|
384
420
|
writeToken: edgeWriteToken,
|
|
385
421
|
select: [
|
|
386
422
|
"live_recording"
|
|
@@ -406,8 +442,14 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
406
442
|
let tlro = period.live_recording_handle;
|
|
407
443
|
status.tlro = tlro;
|
|
408
444
|
|
|
409
|
-
let
|
|
410
|
-
|
|
445
|
+
let videoLastFinalizationTimeEpochSec = -1;
|
|
446
|
+
let videoFinalizedParts = 0;
|
|
447
|
+
let sinceLastFinalize = -1;
|
|
448
|
+
if (period.finalized_parts_info && period.finalized_parts_info.video && period.finalized_parts_info.video.last_finalization_time) {
|
|
449
|
+
videoLastFinalizationTimeEpochSec = period.finalized_parts_info.video.last_finalization_time / 1000000;
|
|
450
|
+
videoFinalizedParts = period.finalized_parts_info.video.n_parts;
|
|
451
|
+
sinceLastFinalize = Math.floor(new Date().getTime() / 1000) - videoLastFinalizationTimeEpochSec;
|
|
452
|
+
}
|
|
411
453
|
|
|
412
454
|
let recording_period = {
|
|
413
455
|
activation_time_epoch_sec: period.recording_start_time_epoch_sec,
|
|
@@ -415,15 +457,15 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
415
457
|
start_time_text: new Date(period.start_time_epoch_sec * 1000).toLocaleString(),
|
|
416
458
|
end_time_epoch_sec: period.end_time_epoch_sec,
|
|
417
459
|
end_time_text: period.end_time_epoch_sec === 0 ? null : new Date(period.end_time_epoch_sec * 1000).toLocaleString(),
|
|
418
|
-
video_parts:
|
|
419
|
-
video_last_part_finalized_epoch_sec:
|
|
460
|
+
video_parts: videoFinalizedParts,
|
|
461
|
+
video_last_part_finalized_epoch_sec: videoLastFinalizationTimeEpochSec,
|
|
420
462
|
video_since_last_finalize_sec : sinceLastFinalize
|
|
421
463
|
};
|
|
422
464
|
status.recording_period = recording_period;
|
|
423
465
|
|
|
424
466
|
status.lro_status_url = await this.FabricUrl({
|
|
425
467
|
libraryId: libraryId,
|
|
426
|
-
objectId:
|
|
468
|
+
objectId: objectId,
|
|
427
469
|
writeToken: edgeWriteToken,
|
|
428
470
|
call: "live/status/" + tlro
|
|
429
471
|
});
|
|
@@ -453,6 +495,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
453
495
|
await HttpClient.Fetch(status.lro_status_url)
|
|
454
496
|
);
|
|
455
497
|
state = lroStatus.state;
|
|
498
|
+
status.warnings = lroStatus.custom && lroStatus.custom.warnings;
|
|
499
|
+
status.quality = lroStatus.custom && lroStatus.custom.quality;
|
|
456
500
|
} catch(error) {
|
|
457
501
|
console.log("LRO Status (failed): ", error.response.statusCode);
|
|
458
502
|
status.state = "stopped";
|
|
@@ -461,7 +505,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
461
505
|
}
|
|
462
506
|
|
|
463
507
|
// Convert LRO 'state' to desired 'state'
|
|
464
|
-
if(state === "running" &&
|
|
508
|
+
if(state === "running" && videoLastFinalizationTimeEpochSec <= 0) {
|
|
465
509
|
state = "starting";
|
|
466
510
|
} else if(state === "running" && sinceLastFinalize > 32.9) {
|
|
467
511
|
state = "stalled";
|
|
@@ -472,8 +516,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
472
516
|
|
|
473
517
|
if((state === "running" || state === "stalled" || state === "starting") && stopLro) {
|
|
474
518
|
lroStopUrl = await this.FabricUrl({
|
|
475
|
-
libraryId
|
|
476
|
-
objectId
|
|
519
|
+
libraryId,
|
|
520
|
+
objectId,
|
|
477
521
|
writeToken: edgeWriteToken,
|
|
478
522
|
call: "live/stop/" + tlro
|
|
479
523
|
});
|
|
@@ -494,7 +538,6 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
494
538
|
|
|
495
539
|
if(state === "running") {
|
|
496
540
|
let playout_urls = {};
|
|
497
|
-
let objectId = conf.objectId;
|
|
498
541
|
let playout_options = await this.PlayoutOptions({
|
|
499
542
|
objectId,
|
|
500
543
|
linkPath: "public/asset_metadata/sources/default"
|
|
@@ -555,7 +598,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
555
598
|
if(networkInfo.name.includes("demo")) {
|
|
556
599
|
embed_net = "demo";
|
|
557
600
|
}
|
|
558
|
-
let embed_url = `https://embed.v3.contentfabric.io/?net=${embed_net}&p&ct=h&oid=${
|
|
601
|
+
let embed_url = `https://embed.v3.contentfabric.io/?net=${embed_net}&p&ct=h&oid=${objectId}&mt=lv&ath=${token}`;
|
|
559
602
|
playout_urls.embed_url = embed_url;
|
|
560
603
|
|
|
561
604
|
status.playout_urls = playout_urls;
|
|
@@ -580,7 +623,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
580
623
|
*/
|
|
581
624
|
exports.StreamCreate = async function({name, start=false}) {
|
|
582
625
|
let status = await this.StreamStatus({name});
|
|
583
|
-
if(status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
|
|
626
|
+
if(status.state != "uninitialized" && status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
|
|
584
627
|
return {
|
|
585
628
|
state: status.state,
|
|
586
629
|
error: "stream still active - must terminate first"
|
|
@@ -690,7 +733,7 @@ exports.StreamCreate = async function({name, start=false}) {
|
|
|
690
733
|
*/
|
|
691
734
|
exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
692
735
|
try {
|
|
693
|
-
let status = await this.StreamStatus({name})
|
|
736
|
+
let status = await this.StreamStatus({name})
|
|
694
737
|
if(status.state != "stopped") {
|
|
695
738
|
if(op === "start") {
|
|
696
739
|
status.error = "Unable to start stream - state: " + status.state;
|
|
@@ -778,9 +821,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
778
821
|
exports.StreamStopSession = async function({name}) {
|
|
779
822
|
try {
|
|
780
823
|
this.Log(`Terminating stream session for: ${name}`);
|
|
781
|
-
let
|
|
782
|
-
|
|
783
|
-
let {objectId} = conf;
|
|
824
|
+
let objectId = name;
|
|
784
825
|
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
785
826
|
|
|
786
827
|
let mainMeta = await this.ContentObjectMetadata({
|
|
@@ -827,7 +868,7 @@ exports.StreamStopSession = async function({name}) {
|
|
|
827
868
|
writeToken: metaEdgeWriteToken
|
|
828
869
|
});
|
|
829
870
|
} catch(error) {
|
|
830
|
-
this.Log(
|
|
871
|
+
this.Log("Unable to retrieve metadata for edge write token");
|
|
831
872
|
}
|
|
832
873
|
|
|
833
874
|
const {writeToken} = await this.EditContentObject({
|
|
@@ -849,8 +890,7 @@ exports.StreamStopSession = async function({name}) {
|
|
|
849
890
|
fabric_config: {
|
|
850
891
|
edge_write_token: ""
|
|
851
892
|
}
|
|
852
|
-
}
|
|
853
|
-
recording_stop_time: stopTime
|
|
893
|
+
}
|
|
854
894
|
};
|
|
855
895
|
|
|
856
896
|
await this.MergeMetadata({
|
|
@@ -891,21 +931,27 @@ exports.StreamStopSession = async function({name}) {
|
|
|
891
931
|
* @return {Promise<Object>} - The name, object ID, and state of the stream
|
|
892
932
|
*/
|
|
893
933
|
exports.StreamInitialize = async function({name, drm=false, format}) {
|
|
894
|
-
const contentTypes = await this.ContentTypes();
|
|
895
|
-
|
|
896
934
|
let typeAbrMaster;
|
|
897
935
|
let typeLiveStream;
|
|
898
936
|
|
|
899
|
-
|
|
900
|
-
|
|
937
|
+
// Fetch Title and Live Stream content types from tenant meta
|
|
938
|
+
const tenantContractId = await this.userProfileClient.TenantContractId();
|
|
939
|
+
const {live_stream, title} = await this.ContentObjectMetadata({
|
|
940
|
+
libraryId: tenantContractId.replace("iten", "ilib"),
|
|
941
|
+
objectId: tenantContractId.replace("iten", "iq__"),
|
|
942
|
+
metadataSubtree: "public/content_types",
|
|
943
|
+
select: [
|
|
944
|
+
"live_stream",
|
|
945
|
+
"title"
|
|
946
|
+
]
|
|
947
|
+
});
|
|
901
948
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
949
|
+
if(live_stream) {
|
|
950
|
+
typeLiveStream = live_stream;
|
|
951
|
+
}
|
|
905
952
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
953
|
+
if(title) {
|
|
954
|
+
typeAbrMaster = title;
|
|
909
955
|
}
|
|
910
956
|
|
|
911
957
|
if(typeAbrMaster === undefined || typeLiveStream === undefined) {
|
|
@@ -919,7 +965,7 @@ exports.StreamInitialize = async function({name, drm=false, format}) {
|
|
|
919
965
|
};
|
|
920
966
|
|
|
921
967
|
/**
|
|
922
|
-
*
|
|
968
|
+
* Create a dummy VoD offering and initialize DRM keys.
|
|
923
969
|
*
|
|
924
970
|
* @methodGroup Live Stream
|
|
925
971
|
* @namedParams
|
|
@@ -935,7 +981,7 @@ exports.StreamInitialize = async function({name, drm=false, format}) {
|
|
|
935
981
|
*/
|
|
936
982
|
exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveStream, drm=false, format}) {
|
|
937
983
|
let status = await this.StreamStatus({name});
|
|
938
|
-
if(status.state != "inactive" && status.state != "stopped") {
|
|
984
|
+
if(status.state != "uninitialized" && status.state != "inactive" && status.state != "stopped") {
|
|
939
985
|
return {
|
|
940
986
|
state: status.state,
|
|
941
987
|
error: "stream still active - must terminate first"
|
|
@@ -961,9 +1007,10 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
|
|
|
961
1007
|
const vFrameRate = "30000/1001";
|
|
962
1008
|
const vTimeBase = "1/30000"; // "1/16000";
|
|
963
1009
|
|
|
964
|
-
const
|
|
1010
|
+
const abrProfileDefault = require("../abr_profiles/abr_profile_live_drm.js");
|
|
965
1011
|
|
|
966
|
-
let playoutFormats
|
|
1012
|
+
let playoutFormats;
|
|
1013
|
+
let abrProfile = JSON.parse(JSON.stringify(abrProfileDefault));
|
|
967
1014
|
if(format) {
|
|
968
1015
|
drm = true; // Override DRM parameter
|
|
969
1016
|
playoutFormats = {};
|
|
@@ -991,6 +1038,8 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
|
|
|
991
1038
|
}
|
|
992
1039
|
}
|
|
993
1040
|
};
|
|
1041
|
+
} else {
|
|
1042
|
+
playoutFormats = Object.assign({}, abrProfile.playout_formats);
|
|
994
1043
|
}
|
|
995
1044
|
|
|
996
1045
|
abrProfile.playout_formats = playoutFormats;
|
|
@@ -1114,13 +1163,12 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
|
|
|
1114
1163
|
}
|
|
1115
1164
|
}
|
|
1116
1165
|
|
|
1117
|
-
let
|
|
1118
|
-
let libraryId = await this.ContentObjectLibraryId({objectId
|
|
1119
|
-
let objectId = conf.objectId;
|
|
1166
|
+
let objectId = name;
|
|
1167
|
+
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1120
1168
|
|
|
1121
1169
|
let mainMeta = await this.ContentObjectMetadata({
|
|
1122
|
-
libraryId
|
|
1123
|
-
objectId
|
|
1170
|
+
libraryId,
|
|
1171
|
+
objectId
|
|
1124
1172
|
});
|
|
1125
1173
|
|
|
1126
1174
|
let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
@@ -1134,8 +1182,8 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
|
|
|
1134
1182
|
let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
1135
1183
|
|
|
1136
1184
|
let edgeMeta = await this.ContentObjectMetadata({
|
|
1137
|
-
libraryId
|
|
1138
|
-
objectId
|
|
1185
|
+
libraryId,
|
|
1186
|
+
objectId,
|
|
1139
1187
|
writeToken: edgeWriteToken
|
|
1140
1188
|
});
|
|
1141
1189
|
|
|
@@ -1250,73 +1298,52 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
|
|
|
1250
1298
|
};
|
|
1251
1299
|
|
|
1252
1300
|
/**
|
|
1253
|
-
*
|
|
1301
|
+
* Configure the stream based on built-in logic and optional custom settings.
|
|
1254
1302
|
*
|
|
1255
|
-
*
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
path.resolve(__dirname, "../liveconf.json")
|
|
1274
|
-
);
|
|
1275
|
-
} catch(error) {
|
|
1276
|
-
console.log("Stream name must be a QID or a label in liveconf.json");
|
|
1277
|
-
return {};
|
|
1278
|
-
}
|
|
1279
|
-
const streams = JSON.parse(streamsBuf);
|
|
1280
|
-
const conf = streams[name];
|
|
1281
|
-
if(conf === null) {
|
|
1282
|
-
console.log("Bad name: ", name);
|
|
1283
|
-
return {};
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
return conf;
|
|
1287
|
-
};
|
|
1288
|
-
|
|
1289
|
-
/**
|
|
1290
|
-
* Configure the stream
|
|
1303
|
+
* Custom settings format:
|
|
1304
|
+
* {
|
|
1305
|
+
* "audio" {
|
|
1306
|
+
* "1" : { // This is the stream index
|
|
1307
|
+
* "tags" : "language: english",
|
|
1308
|
+
* "codec" : "aac",
|
|
1309
|
+
* "bitrate": 204000,
|
|
1310
|
+
* "record": true,
|
|
1311
|
+
* "recording_bitrate" : 192000,
|
|
1312
|
+
* "recording_channels" : 2,
|
|
1313
|
+
* "playout": bool
|
|
1314
|
+
* "playout_label": "English (Stereo)"
|
|
1315
|
+
* },
|
|
1316
|
+
* "3": {
|
|
1317
|
+
* ...
|
|
1318
|
+
* }
|
|
1319
|
+
* }
|
|
1320
|
+
* }
|
|
1291
1321
|
*
|
|
1292
1322
|
* @methodGroup Live Stream
|
|
1293
1323
|
* @namedParams
|
|
1294
1324
|
* @param {string} name - Object ID or name of the live stream object
|
|
1295
1325
|
* @param {Object=} customSettings - Additional options to customize configuration settings
|
|
1296
|
-
* -
|
|
1297
|
-
* - audioIndex
|
|
1298
|
-
* - partTtl
|
|
1299
|
-
* - channelLayout
|
|
1300
|
-
*
|
|
1326
|
+
* @param {Object=} probeMetadata - Metadata for the probe. If not specified, a new probe will be configured
|
|
1301
1327
|
* @return {Promise<Object>} - The status response for the stream
|
|
1302
1328
|
*
|
|
1303
1329
|
*/
|
|
1304
|
-
exports.StreamConfig = async function({name, customSettings={}}) {
|
|
1305
|
-
let
|
|
1330
|
+
exports.StreamConfig = async function({name, customSettings={}, probeMetadata}) {
|
|
1331
|
+
let objectId = name;
|
|
1306
1332
|
let status = {name};
|
|
1307
1333
|
|
|
1308
|
-
let libraryId = await this.ContentObjectLibraryId({objectId
|
|
1334
|
+
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1309
1335
|
status.library_id = libraryId;
|
|
1310
|
-
status.object_id =
|
|
1336
|
+
status.object_id = objectId;
|
|
1337
|
+
|
|
1338
|
+
let probe = probeMetadata;
|
|
1311
1339
|
|
|
1312
1340
|
let mainMeta = await this.ContentObjectMetadata({
|
|
1313
1341
|
libraryId: libraryId,
|
|
1314
|
-
objectId:
|
|
1342
|
+
objectId: objectId
|
|
1315
1343
|
});
|
|
1316
1344
|
|
|
1317
1345
|
let userConfig = mainMeta.live_recording_config;
|
|
1318
1346
|
status.user_config = userConfig;
|
|
1319
|
-
console.log("userConfig", userConfig);
|
|
1320
1347
|
|
|
1321
1348
|
// Get node URI from user config
|
|
1322
1349
|
const hostName = userConfig.url.replace("udp://", "").replace("rtmp://", "").replace("srt://", "").split(":")[0];
|
|
@@ -1330,80 +1357,85 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1330
1357
|
}
|
|
1331
1358
|
const node = nodes[0];
|
|
1332
1359
|
status.node = node;
|
|
1333
|
-
|
|
1334
1360
|
let endpoint = node.endpoints[0];
|
|
1335
|
-
this.SetNodes({fabricURIs: [endpoint]});
|
|
1336
|
-
|
|
1337
|
-
// Probe the stream
|
|
1338
|
-
let probe = {};
|
|
1339
|
-
const controller = new AbortController();
|
|
1340
|
-
const timeoutId = setTimeout(() => {
|
|
1341
|
-
controller.abort();
|
|
1342
|
-
}, 60 * 1000); // milliseconds
|
|
1343
|
-
try {
|
|
1344
1361
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
objectId: conf.objectId,
|
|
1348
|
-
rep: "probe"
|
|
1349
|
-
});
|
|
1362
|
+
if(!probe) {
|
|
1363
|
+
this.SetNodes({fabricURIs: [endpoint]});
|
|
1350
1364
|
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
signal: controller.signal
|
|
1359
|
-
})
|
|
1360
|
-
);
|
|
1365
|
+
// Probe the stream
|
|
1366
|
+
probe = {};
|
|
1367
|
+
const controller = new AbortController();
|
|
1368
|
+
const timeoutId = setTimeout(() => {
|
|
1369
|
+
controller.abort();
|
|
1370
|
+
}, 60 * 1000); // milliseconds
|
|
1371
|
+
try {
|
|
1361
1372
|
|
|
1362
|
-
|
|
1373
|
+
let probeUrl = await this.Rep({
|
|
1374
|
+
libraryId,
|
|
1375
|
+
objectId,
|
|
1376
|
+
rep: "probe"
|
|
1377
|
+
});
|
|
1363
1378
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1379
|
+
probe = await this.utils.ResponseToJson(
|
|
1380
|
+
await HttpClient.Fetch(probeUrl, {
|
|
1381
|
+
body: JSON.stringify({
|
|
1382
|
+
"filename": streamUrl.href,
|
|
1383
|
+
"listen": true
|
|
1384
|
+
}),
|
|
1385
|
+
method: "POST",
|
|
1386
|
+
signal: controller.signal
|
|
1387
|
+
})
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
if(probe) { clearTimeout(timeoutId); }
|
|
1391
|
+
|
|
1392
|
+
if(probe.errors) {
|
|
1393
|
+
throw probe.errors[0];
|
|
1394
|
+
}
|
|
1395
|
+
} catch(error) {
|
|
1396
|
+
if(error.code === "ETIMEDOUT") {
|
|
1397
|
+
throw "Stream probe time out - make sure the stream source is available";
|
|
1398
|
+
} else {
|
|
1399
|
+
throw error;
|
|
1400
|
+
}
|
|
1372
1401
|
}
|
|
1373
|
-
}
|
|
1374
1402
|
|
|
1375
|
-
|
|
1403
|
+
probe.format.filename = streamUrl.href;
|
|
1404
|
+
}
|
|
1376
1405
|
|
|
1377
1406
|
// Create live recording config
|
|
1378
1407
|
let lc = new LiveConf(probe, node.id, endpoint, false, false, true);
|
|
1379
1408
|
|
|
1380
|
-
const
|
|
1381
|
-
|
|
1382
|
-
audioIndex: customSettings.audioIndex,
|
|
1383
|
-
partTtl: customSettings.partTtl,
|
|
1384
|
-
channelLayout: customSettings.channelLayout
|
|
1409
|
+
const liveRecordingConfig = lc.generateLiveConf({
|
|
1410
|
+
customSettings
|
|
1385
1411
|
});
|
|
1386
|
-
let liveRecordingConfig = JSON.parse(liveRecordingConfigStr);
|
|
1387
|
-
console.log("CONFIG", JSON.stringify(liveRecordingConfig.live_recording));
|
|
1388
1412
|
|
|
1389
1413
|
// Store live recording config into the stream object
|
|
1390
1414
|
let e = await this.EditContentObject({
|
|
1391
1415
|
libraryId,
|
|
1392
|
-
objectId:
|
|
1416
|
+
objectId: objectId
|
|
1393
1417
|
});
|
|
1394
1418
|
let writeToken = e.write_token;
|
|
1395
1419
|
|
|
1396
1420
|
await this.ReplaceMetadata({
|
|
1397
1421
|
libraryId,
|
|
1398
|
-
objectId
|
|
1422
|
+
objectId,
|
|
1399
1423
|
writeToken,
|
|
1400
1424
|
metadataSubtree: "live_recording",
|
|
1401
1425
|
metadata: liveRecordingConfig.live_recording
|
|
1402
1426
|
});
|
|
1403
1427
|
|
|
1428
|
+
await this.ReplaceMetadata({
|
|
1429
|
+
libraryId,
|
|
1430
|
+
objectId,
|
|
1431
|
+
writeToken,
|
|
1432
|
+
metadataSubtree: "live_recording_config/probe_info",
|
|
1433
|
+
metadata: probe
|
|
1434
|
+
});
|
|
1435
|
+
|
|
1404
1436
|
status.fin = await this.FinalizeContentObject({
|
|
1405
1437
|
libraryId,
|
|
1406
|
-
objectId
|
|
1438
|
+
objectId,
|
|
1407
1439
|
writeToken,
|
|
1408
1440
|
commitMessage: "Apply live stream configuration"
|
|
1409
1441
|
});
|
|
@@ -1416,7 +1448,7 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1416
1448
|
*
|
|
1417
1449
|
* @methodGroup Live Stream
|
|
1418
1450
|
* @namedParams
|
|
1419
|
-
* @param {string=} - ID of the live stream site object
|
|
1451
|
+
* @param {string=} siteId - ID of the live stream site object
|
|
1420
1452
|
*
|
|
1421
1453
|
* @return {Promise<Object>} - The list of stream URLs
|
|
1422
1454
|
*/
|
|
@@ -1451,7 +1483,8 @@ exports.StreamListUrls = async function({siteId}={}) {
|
|
|
1451
1483
|
objectId: siteId,
|
|
1452
1484
|
metadataSubtree: "public/asset_metadata/live_streams",
|
|
1453
1485
|
resolveLinks: true,
|
|
1454
|
-
resolveIgnoreErrors: true
|
|
1486
|
+
resolveIgnoreErrors: true,
|
|
1487
|
+
resolveIncludeSource: true
|
|
1455
1488
|
});
|
|
1456
1489
|
|
|
1457
1490
|
const activeUrlMap = {};
|
|
@@ -1462,18 +1495,8 @@ exports.StreamListUrls = async function({siteId}={}) {
|
|
|
1462
1495
|
const stream = streamMetadata[slug];
|
|
1463
1496
|
let versionHash;
|
|
1464
1497
|
|
|
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
|
-
);
|
|
1498
|
+
if(stream && stream["."] && stream["."].source) {
|
|
1499
|
+
versionHash = stream["."].source;
|
|
1477
1500
|
}
|
|
1478
1501
|
|
|
1479
1502
|
if(versionHash) {
|
|
@@ -1532,3 +1555,382 @@ exports.StreamListUrls = async function({siteId}={}) {
|
|
|
1532
1555
|
console.error(error);
|
|
1533
1556
|
}
|
|
1534
1557
|
};
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Copy a portion of a live stream recording into a standard VoD object using the zero-copy content fabric API
|
|
1561
|
+
*
|
|
1562
|
+
* Limitations:
|
|
1563
|
+
* - currently requires the target object to be pre-created and have content encryption keys (CAPS)
|
|
1564
|
+
* - for audio and video to be sync'd, the live stream needs to have the beginning of the desired recording period
|
|
1565
|
+
* - for an event stream, make sure the TTL is long enough to allow running the live-to-vod command before the beginning of the recording expires
|
|
1566
|
+
* - for 24/7 streams, make sure to reset the stream before the desired recording (as to create a new recording period) and have the TTL long enough
|
|
1567
|
+
* to allow running the live-to-vod command before the beginning of the recording expires.
|
|
1568
|
+
* - startTime and endTime are not currently implemented by this method
|
|
1569
|
+
*
|
|
1570
|
+
*
|
|
1571
|
+
* @methodGroup Live Stream
|
|
1572
|
+
* @namedParams
|
|
1573
|
+
* @param {string} name - Object ID or name of the live stream
|
|
1574
|
+
* @param {string} targetObjectId - Object ID of the target VOD object
|
|
1575
|
+
* @param {string=} eventId -
|
|
1576
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after copy to vod operations
|
|
1577
|
+
* @param {number=} recordingPeriod - Determines which recording period to copy, which are 0-based. -1 copies the current (or last) period
|
|
1578
|
+
*
|
|
1579
|
+
* @return {Promise<Object>} - The status response for the stream
|
|
1580
|
+
*/
|
|
1581
|
+
|
|
1582
|
+
/*
|
|
1583
|
+
Example fabric API flow:
|
|
1584
|
+
|
|
1585
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/live_to_vod/init -d @r1 -H "Authorization: Bearer $TOK"
|
|
1586
|
+
|
|
1587
|
+
{
|
|
1588
|
+
"live_qhash": "hq__5Zk1jSN8vNLUAXjQwMJV8F8J8ESXNvmVKkhaXySmGc1BXnJPG2FvvaXee4CXqvFHuGuU3fqLJc",
|
|
1589
|
+
"start_time": "",
|
|
1590
|
+
"end_time": "",
|
|
1591
|
+
"recording_period": -1,
|
|
1592
|
+
"streams": ["video", "audio"],
|
|
1593
|
+
"variant_key": "default"
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/init -H "Authorization: Bearer $TOK" -d @r2
|
|
1597
|
+
|
|
1598
|
+
{
|
|
1599
|
+
|
|
1600
|
+
"abr_profile": { ... },
|
|
1601
|
+
"offering_key": "default",
|
|
1602
|
+
"prod_master_hash": "tqw__HSQHBt7vYxWfCMPH5yXwKTfhdPcQ4Lcs9WUMUbTtnMbTZPTLo4BfJWPMGpoy1Dpv1wWQVtUtAtAr429TnVs",
|
|
1603
|
+
"variant_key": "default",
|
|
1604
|
+
"keep_other_streams": false
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/live_to_vod/copy -d '{"variant_key":"","offering_key":""}' -H "Authorization: Bearer $TOK"
|
|
1608
|
+
|
|
1609
|
+
|
|
1610
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/offerings/default/finalize -d '{}' -H "Authorization: Bearer $TOK"
|
|
1611
|
+
|
|
1612
|
+
*/
|
|
1613
|
+
|
|
1614
|
+
exports.StreamCopyToVod = async function({
|
|
1615
|
+
name,
|
|
1616
|
+
targetObjectId,
|
|
1617
|
+
eventId,
|
|
1618
|
+
streams=null,
|
|
1619
|
+
finalize=true,
|
|
1620
|
+
recordingPeriod=-1,
|
|
1621
|
+
startTime="",
|
|
1622
|
+
endTime=""
|
|
1623
|
+
}) {
|
|
1624
|
+
const objectId = name;
|
|
1625
|
+
const abrProfile = require("../abr_profiles/abr_profile_live_to_vod.js");
|
|
1626
|
+
|
|
1627
|
+
const status = await this.StreamStatus({name});
|
|
1628
|
+
const libraryId = status.library_id;
|
|
1629
|
+
|
|
1630
|
+
this.Log(`Copying stream ${name} to target ${targetObjectId}`);
|
|
1631
|
+
|
|
1632
|
+
ValidateObject(targetObjectId);
|
|
1633
|
+
|
|
1634
|
+
const targetLibraryId = await this.ContentObjectLibraryId({objectId: targetObjectId});
|
|
1635
|
+
|
|
1636
|
+
// Validation - ensure target object has content encryption keys
|
|
1637
|
+
const kmsAddress = await this.authClient.KMSAddress({objectId: targetObjectId});
|
|
1638
|
+
const kmsCapId = `eluv.caps.ikms${this.utils.AddressToHash(kmsAddress)}`;
|
|
1639
|
+
const kmsCap = await this.ContentObjectMetadata({
|
|
1640
|
+
libraryId: targetLibraryId,
|
|
1641
|
+
objectId: targetObjectId,
|
|
1642
|
+
metadataSubtree: kmsCapId
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
if(!kmsCap) {
|
|
1646
|
+
throw Error(`No content encryption key set for object ${targetObjectId}`);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
try {
|
|
1650
|
+
status.live_object_id = objectId;
|
|
1651
|
+
|
|
1652
|
+
const liveHash = await this.LatestVersionHash({objectId, libraryId});
|
|
1653
|
+
status.live_hash = liveHash;
|
|
1654
|
+
|
|
1655
|
+
if(eventId) {
|
|
1656
|
+
// Retrieve start and end times for the event
|
|
1657
|
+
let event = await this.CueInfo({eventId, status});
|
|
1658
|
+
if(event.eventStart && event.eventEnd) {
|
|
1659
|
+
startTime = event.eventStart;
|
|
1660
|
+
endTime = event.eventEnd;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
const {writeToken} = await this.EditContentObject({
|
|
1665
|
+
objectId: targetObjectId,
|
|
1666
|
+
libraryId: targetLibraryId
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
status.target_object_id = targetObjectId;
|
|
1670
|
+
status.target_library_id = targetLibraryId;
|
|
1671
|
+
status.target_write_token = writeToken;
|
|
1672
|
+
|
|
1673
|
+
this.Log("Process live source (takes around 20 sec per hour of content)");
|
|
1674
|
+
|
|
1675
|
+
await this.CallBitcodeMethod({
|
|
1676
|
+
libraryId: targetLibraryId,
|
|
1677
|
+
objectId: targetObjectId,
|
|
1678
|
+
writeToken,
|
|
1679
|
+
method: "/media/live_to_vod/init",
|
|
1680
|
+
body: {
|
|
1681
|
+
"live_qhash": liveHash,
|
|
1682
|
+
"start_time": startTime, // eg. "2023-10-03T02:09:02.00Z",
|
|
1683
|
+
"end_time": endTime, // eg. "2023-10-03T02:15:00.00Z",
|
|
1684
|
+
"streams": streams,
|
|
1685
|
+
"recording_period": recordingPeriod,
|
|
1686
|
+
"variant_key": "default"
|
|
1687
|
+
},
|
|
1688
|
+
constant: false,
|
|
1689
|
+
format: "text"
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
const abrMezInitBody = {
|
|
1693
|
+
abr_profile: abrProfile,
|
|
1694
|
+
"offering_key": "default",
|
|
1695
|
+
"prod_master_hash": writeToken,
|
|
1696
|
+
"variant_key": "default",
|
|
1697
|
+
"keep_other_streams": false
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
await this.CallBitcodeMethod({
|
|
1701
|
+
libraryId: targetLibraryId,
|
|
1702
|
+
objectId: targetObjectId,
|
|
1703
|
+
writeToken,
|
|
1704
|
+
method: "/media/abr_mezzanine/init",
|
|
1705
|
+
body: abrMezInitBody,
|
|
1706
|
+
constant: false,
|
|
1707
|
+
format: "text"
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
try {
|
|
1711
|
+
await this.CallBitcodeMethod({
|
|
1712
|
+
libraryId: targetLibraryId,
|
|
1713
|
+
objectId: targetObjectId,
|
|
1714
|
+
writeToken,
|
|
1715
|
+
method: "/media/live_to_vod/copy",
|
|
1716
|
+
body: {},
|
|
1717
|
+
constant: false,
|
|
1718
|
+
format: "text"
|
|
1719
|
+
});
|
|
1720
|
+
} catch(error) {
|
|
1721
|
+
console.error("Unable to call /media/live_to_vod/copy", error);
|
|
1722
|
+
throw error;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
await this.CallBitcodeMethod({
|
|
1726
|
+
libraryId: targetLibraryId,
|
|
1727
|
+
objectId: targetObjectId,
|
|
1728
|
+
writeToken,
|
|
1729
|
+
method: "/media/abr_mezzanine/offerings/default/finalize",
|
|
1730
|
+
body: abrMezInitBody,
|
|
1731
|
+
constant: false,
|
|
1732
|
+
format: "text"
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1735
|
+
if(finalize) {
|
|
1736
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1737
|
+
libraryId: targetLibraryId,
|
|
1738
|
+
objectId: targetObjectId,
|
|
1739
|
+
writeToken,
|
|
1740
|
+
commitMessage: "Live Stream to VoD"
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
status.target_hash = finalizeResponse.hash;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// Clean up unnecessary status items
|
|
1747
|
+
delete status.playout_urls;
|
|
1748
|
+
delete status.lro_status_url;
|
|
1749
|
+
delete status.recording_period;
|
|
1750
|
+
delete status.recording_period_sequence;
|
|
1751
|
+
delete status.edge_meta_size;
|
|
1752
|
+
delete status.insertions;
|
|
1753
|
+
|
|
1754
|
+
return status;
|
|
1755
|
+
} catch(error) {
|
|
1756
|
+
this.Log(error, true);
|
|
1757
|
+
throw error;
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1761
|
+
/**
|
|
1762
|
+
* Remove a watermark for a live stream
|
|
1763
|
+
*
|
|
1764
|
+
* @methodGroup Live Stream
|
|
1765
|
+
* @namedParams
|
|
1766
|
+
* @param {string} objectId - Object ID of the live stream
|
|
1767
|
+
* @param {Array<string>} types - Specify which type of watermark to remove. Possible values:
|
|
1768
|
+
* - "image"
|
|
1769
|
+
* - "text"
|
|
1770
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after removing watermark
|
|
1771
|
+
*
|
|
1772
|
+
* @return {Promise<Object>} - The finalize response
|
|
1773
|
+
*/
|
|
1774
|
+
exports.StreamRemoveWatermark = async function({
|
|
1775
|
+
objectId,
|
|
1776
|
+
types,
|
|
1777
|
+
finalize=true
|
|
1778
|
+
}) {
|
|
1779
|
+
ValidateObject(objectId);
|
|
1780
|
+
|
|
1781
|
+
const libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1782
|
+
const {writeToken} = await this.EditContentObject({
|
|
1783
|
+
objectId,
|
|
1784
|
+
libraryId
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
this.Log(`Removing watermark types: ${types.join(", ")} ${libraryId} ${objectId}`);
|
|
1788
|
+
|
|
1789
|
+
const edgeWriteToken = await this.ContentObjectMetadata({
|
|
1790
|
+
objectId,
|
|
1791
|
+
libraryId,
|
|
1792
|
+
metadataSubtree: "/live_recording/fabric_config/edge_write_token"
|
|
1793
|
+
});
|
|
1794
|
+
|
|
1795
|
+
const recordingParamsPath = "live_recording/recording_config/recording_params";
|
|
1796
|
+
|
|
1797
|
+
const recordingMetadata = await this.ContentObjectMetadata({
|
|
1798
|
+
libraryId,
|
|
1799
|
+
objectId,
|
|
1800
|
+
writeToken,
|
|
1801
|
+
metadataSubtree: recordingParamsPath,
|
|
1802
|
+
resolveLinks: false
|
|
1803
|
+
});
|
|
1804
|
+
|
|
1805
|
+
if(!recordingMetadata) {
|
|
1806
|
+
throw Error("Stream object must be configured");
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
types.forEach(type => {
|
|
1810
|
+
if(type === "text") {
|
|
1811
|
+
delete recordingMetadata.simple_watermark;
|
|
1812
|
+
} else if(type === "image") {
|
|
1813
|
+
delete recordingMetadata.image_watermark;
|
|
1814
|
+
}
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
await this.ReplaceMetadata({
|
|
1818
|
+
libraryId,
|
|
1819
|
+
objectId,
|
|
1820
|
+
writeToken,
|
|
1821
|
+
metadataSubtree: recordingParamsPath,
|
|
1822
|
+
metadata: recordingMetadata
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
if(edgeWriteToken) {
|
|
1826
|
+
await this.ReplaceMetadata({
|
|
1827
|
+
libraryId,
|
|
1828
|
+
objectId,
|
|
1829
|
+
writeToken: edgeWriteToken,
|
|
1830
|
+
metadataSubtree: recordingParamsPath,
|
|
1831
|
+
metadata: recordingMetadata
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if(finalize) {
|
|
1836
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1837
|
+
libraryId,
|
|
1838
|
+
objectId,
|
|
1839
|
+
writeToken,
|
|
1840
|
+
commitMessage: "Watermark removed"
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
return finalizeResponse;
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
/**
|
|
1848
|
+
* Create a watermark for a live stream
|
|
1849
|
+
*
|
|
1850
|
+
* @methodGroup Live Stream
|
|
1851
|
+
* @namedParams
|
|
1852
|
+
* @param {string} objectId - Object ID of the live stream
|
|
1853
|
+
* @param {Object} simpleWatermark - Text watermark
|
|
1854
|
+
* @param {Object} imageWatermark - Image watermark
|
|
1855
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after adding watermark
|
|
1856
|
+
*
|
|
1857
|
+
* @return {Promise<Object>} - The finalize response
|
|
1858
|
+
*/
|
|
1859
|
+
exports.StreamAddWatermark = async function({
|
|
1860
|
+
objectId,
|
|
1861
|
+
simpleWatermark,
|
|
1862
|
+
imageWatermark,
|
|
1863
|
+
finalize=true
|
|
1864
|
+
}) {
|
|
1865
|
+
ValidateObject(objectId);
|
|
1866
|
+
|
|
1867
|
+
const libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1868
|
+
const {writeToken} = await this.EditContentObject({
|
|
1869
|
+
objectId,
|
|
1870
|
+
libraryId
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
const edgeWriteToken = await this.ContentObjectMetadata({
|
|
1874
|
+
objectId,
|
|
1875
|
+
libraryId,
|
|
1876
|
+
metadataSubtree: "/live_recording/fabric_config/edge_write_token"
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
this.Log(`Adding watermarking type: ${imageWatermark ? "image" : "text"} ${libraryId} ${objectId}`);
|
|
1880
|
+
|
|
1881
|
+
const recordingParamsPath = "live_recording/recording_config/recording_params";
|
|
1882
|
+
|
|
1883
|
+
const recordingMetadata = await this.ContentObjectMetadata({
|
|
1884
|
+
libraryId,
|
|
1885
|
+
objectId,
|
|
1886
|
+
writeToken,
|
|
1887
|
+
metadataSubtree: recordingParamsPath,
|
|
1888
|
+
resolveLinks: false
|
|
1889
|
+
});
|
|
1890
|
+
|
|
1891
|
+
if(!recordingMetadata) {
|
|
1892
|
+
throw Error("Stream object must be configured");
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
if(simpleWatermark) {
|
|
1896
|
+
recordingMetadata.simple_watermark = simpleWatermark;
|
|
1897
|
+
} else if(imageWatermark) {
|
|
1898
|
+
recordingMetadata.image_watermark = imageWatermark;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
await this.ReplaceMetadata({
|
|
1902
|
+
libraryId,
|
|
1903
|
+
objectId,
|
|
1904
|
+
writeToken,
|
|
1905
|
+
metadataSubtree: recordingParamsPath,
|
|
1906
|
+
metadata: recordingMetadata
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
if(edgeWriteToken) {
|
|
1910
|
+
await this.ReplaceMetadata({
|
|
1911
|
+
libraryId,
|
|
1912
|
+
objectId,
|
|
1913
|
+
writeToken: edgeWriteToken,
|
|
1914
|
+
metadataSubtree: recordingParamsPath,
|
|
1915
|
+
metadata: recordingMetadata
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
const response = {
|
|
1920
|
+
"imageWatermark": recordingMetadata.image_watermark,
|
|
1921
|
+
"textWatermark": recordingMetadata.simple_watermark
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
if(finalize) {
|
|
1925
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1926
|
+
libraryId,
|
|
1927
|
+
objectId,
|
|
1928
|
+
writeToken,
|
|
1929
|
+
commitMessage: "Watermark set"
|
|
1930
|
+
});
|
|
1931
|
+
|
|
1932
|
+
response.hash = finalizeResponse.hash;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
return response;
|
|
1936
|
+
};
|