@eluvio/elv-client-js 4.0.81 → 4.0.83
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 +5 -5
- 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/ContentAccess.js +819 -755
- package/dist/src/client/ContentManagement.js +34 -2
- package/dist/src/client/LiveStream.js +1035 -454
- 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/ContentAccess.js +37 -1
- package/src/client/ContentManagement.js +16 -1
- package/src/client/LiveStream.js +441 -14
- package/testScripts/DecodeSignedMessage.js +45 -0
package/package.json
CHANGED
package/src/FrameClient.js
CHANGED
|
@@ -475,11 +475,14 @@ class FrameClient {
|
|
|
475
475
|
"SetPermission",
|
|
476
476
|
"SpaceNodes",
|
|
477
477
|
"StartABRMezzanineJobs",
|
|
478
|
+
"StreamAddWatermark",
|
|
478
479
|
"StreamConfig",
|
|
480
|
+
"StreamCopyToVod",
|
|
479
481
|
"StreamCreate",
|
|
480
482
|
"StreamInitialize",
|
|
481
483
|
"StreamInsertion",
|
|
482
484
|
"StreamListUrls",
|
|
485
|
+
"StreamRemoveWatermark",
|
|
483
486
|
"StreamStatus",
|
|
484
487
|
"StreamStartOrStopOrReset",
|
|
485
488
|
"StreamStopSession",
|
|
@@ -20,6 +20,15 @@ const AbrProfileLiveVod = {
|
|
|
20
20
|
}
|
|
21
21
|
]
|
|
22
22
|
},
|
|
23
|
+
"{\"media_type\":\"audio\",\"channels\":6}": {
|
|
24
|
+
"rung_specs": [
|
|
25
|
+
{
|
|
26
|
+
"bit_rate": 384000,
|
|
27
|
+
"media_type": "audio",
|
|
28
|
+
"pregenerate": true
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
23
32
|
"{\"media_type\":\"video\",\"aspect_ratio_height\":273,\"aspect_ratio_width\":640}": {
|
|
24
33
|
"rung_specs": [
|
|
25
34
|
{
|
|
@@ -1075,7 +1075,7 @@ exports.ContentObjectVersions = async function({libraryId, objectId}) {
|
|
|
1075
1075
|
};
|
|
1076
1076
|
|
|
1077
1077
|
/**
|
|
1078
|
-
* Retrieve the version hash of the latest version of the specified object
|
|
1078
|
+
* Retrieve the version hash of the latest version of the specified object from chain
|
|
1079
1079
|
*
|
|
1080
1080
|
* @methodGroup Content Objects
|
|
1081
1081
|
* @namedParams
|
|
@@ -1122,6 +1122,42 @@ exports.LatestVersionHash = async function({objectId, versionHash}) {
|
|
|
1122
1122
|
return latestHash;
|
|
1123
1123
|
};
|
|
1124
1124
|
|
|
1125
|
+
/**
|
|
1126
|
+
* Retrieve the version hash of the latest version of the specified object via fabric API.
|
|
1127
|
+
* Requires authorization.
|
|
1128
|
+
*
|
|
1129
|
+
* @methodGroup Content Objects
|
|
1130
|
+
* @namedParams
|
|
1131
|
+
* @param {string=} objectId - ID of the object
|
|
1132
|
+
* @param {string=} versionHash - Version hash of the object
|
|
1133
|
+
*
|
|
1134
|
+
* @returns {Promise<string>} - The latest version hash of the object
|
|
1135
|
+
*/
|
|
1136
|
+
exports.LatestVersionHashV2 = async function({objectId, versionHash}) {
|
|
1137
|
+
if(versionHash) { objectId = this.utils.DecodeVersionHash(versionHash).objectId; }
|
|
1138
|
+
|
|
1139
|
+
ValidateObject(objectId);
|
|
1140
|
+
|
|
1141
|
+
let latestHash;
|
|
1142
|
+
try {
|
|
1143
|
+
let path = UrlJoin("q", objectId);
|
|
1144
|
+
|
|
1145
|
+
let q = await this.utils.ResponseToJson(
|
|
1146
|
+
this.HttpClient.Request({
|
|
1147
|
+
headers: await this.authClient.AuthorizationHeader({objectId}),
|
|
1148
|
+
method: "GET",
|
|
1149
|
+
path: path
|
|
1150
|
+
})
|
|
1151
|
+
);
|
|
1152
|
+
latestHash = q.hash;
|
|
1153
|
+
|
|
1154
|
+
} catch(error) {
|
|
1155
|
+
console.log("ERROR", error);
|
|
1156
|
+
throw Error(`Unable to determine latest version hash for ${versionHash || objectId}`);
|
|
1157
|
+
}
|
|
1158
|
+
return latestHash;
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1125
1161
|
/* URL Methods */
|
|
1126
1162
|
|
|
1127
1163
|
/**
|
|
@@ -1044,11 +1044,26 @@ exports.PublishContentVersion = async function({objectId, versionHash, awaitComm
|
|
|
1044
1044
|
|
|
1045
1045
|
if(confirmEvent) {
|
|
1046
1046
|
// Found confirmation
|
|
1047
|
-
this.Log(`Commit confirmed: ${objectHash}`);
|
|
1047
|
+
this.Log(`Commit confirmed on chain: ${objectHash}`);
|
|
1048
1048
|
break;
|
|
1049
1049
|
}
|
|
1050
1050
|
}
|
|
1051
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
// APIv2 ensure the fabric API returns the correct hash
|
|
1054
|
+
if(awaitCommitConfirmation) {
|
|
1055
|
+
const pollingInterval = 500; // ms
|
|
1056
|
+
let tries = 20;
|
|
1057
|
+
while (tries > 0) {
|
|
1058
|
+
const h = await this.LatestVersionHashV2({objectId});
|
|
1059
|
+
if (h == versionHash) {
|
|
1060
|
+
this.Log(`Commit confirmed on fabric node: ${versionHash}`);
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
await new Promise(resolve => setTimeout(resolve, pollingInterval));
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1052
1067
|
};
|
|
1053
1068
|
|
|
1054
1069
|
/**
|
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
|
*
|
|
@@ -453,6 +489,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
453
489
|
await HttpClient.Fetch(status.lro_status_url)
|
|
454
490
|
);
|
|
455
491
|
state = lroStatus.state;
|
|
492
|
+
status.warnings = lroStatus.custom && lroStatus.custom.warnings;
|
|
493
|
+
status.quality = lroStatus.custom && lroStatus.custom.quality;
|
|
456
494
|
} catch(error) {
|
|
457
495
|
console.log("LRO Status (failed): ", error.response.statusCode);
|
|
458
496
|
status.state = "stopped";
|
|
@@ -827,7 +865,7 @@ exports.StreamStopSession = async function({name}) {
|
|
|
827
865
|
writeToken: metaEdgeWriteToken
|
|
828
866
|
});
|
|
829
867
|
} catch(error) {
|
|
830
|
-
this.Log(
|
|
868
|
+
this.Log("Unable to retrieve metadata for edge write token");
|
|
831
869
|
}
|
|
832
870
|
|
|
833
871
|
const {writeToken} = await this.EditContentObject({
|
|
@@ -891,21 +929,28 @@ exports.StreamStopSession = async function({name}) {
|
|
|
891
929
|
* @return {Promise<Object>} - The name, object ID, and state of the stream
|
|
892
930
|
*/
|
|
893
931
|
exports.StreamInitialize = async function({name, drm=false, format}) {
|
|
894
|
-
const contentTypes = await this.ContentTypes();
|
|
895
|
-
|
|
896
932
|
let typeAbrMaster;
|
|
897
933
|
let typeLiveStream;
|
|
898
934
|
|
|
899
|
-
|
|
900
|
-
|
|
935
|
+
// Fetch Title and Live Stream content types from tenant meta
|
|
936
|
+
const tenantContractId = await client.userProfileClient.TenantContractId();
|
|
937
|
+
let liveStreamContentType, titleContentType;
|
|
938
|
+
const {live_stream, title} = await client.ContentObjectMetadata({
|
|
939
|
+
libraryId: tenantContractId.replace("iten", "ilib"),
|
|
940
|
+
objectId: tenantContractId.replace("iten", "iq__"),
|
|
941
|
+
metadataSubtree: "public/content_types",
|
|
942
|
+
select: [
|
|
943
|
+
"live_stream",
|
|
944
|
+
"title"
|
|
945
|
+
]
|
|
946
|
+
});
|
|
901
947
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
948
|
+
if(live_stream) {
|
|
949
|
+
typeLiveStream = live_stream;
|
|
950
|
+
}
|
|
905
951
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}
|
|
952
|
+
if(title) {
|
|
953
|
+
typeAbrMaster = title;
|
|
909
954
|
}
|
|
910
955
|
|
|
911
956
|
if(typeAbrMaster === undefined || typeLiveStream === undefined) {
|
|
@@ -961,9 +1006,10 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
|
|
|
961
1006
|
const vFrameRate = "30000/1001";
|
|
962
1007
|
const vTimeBase = "1/30000"; // "1/16000";
|
|
963
1008
|
|
|
964
|
-
const
|
|
1009
|
+
const abrProfileDefault = require("../abr_profiles/abr_profile_live_drm.js");
|
|
965
1010
|
|
|
966
|
-
let playoutFormats
|
|
1011
|
+
let playoutFormats;
|
|
1012
|
+
let abrProfile = JSON.parse(JSON.stringify(abrProfileDefault));
|
|
967
1013
|
if(format) {
|
|
968
1014
|
drm = true; // Override DRM parameter
|
|
969
1015
|
playoutFormats = {};
|
|
@@ -991,6 +1037,8 @@ exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveS
|
|
|
991
1037
|
}
|
|
992
1038
|
}
|
|
993
1039
|
};
|
|
1040
|
+
} else {
|
|
1041
|
+
playoutFormats = Object.assign({}, abrProfile.playout_formats);
|
|
994
1042
|
}
|
|
995
1043
|
|
|
996
1044
|
abrProfile.playout_formats = playoutFormats;
|
|
@@ -1416,7 +1464,7 @@ exports.StreamConfig = async function({name, customSettings={}}) {
|
|
|
1416
1464
|
*
|
|
1417
1465
|
* @methodGroup Live Stream
|
|
1418
1466
|
* @namedParams
|
|
1419
|
-
* @param {string=} - ID of the live stream site object
|
|
1467
|
+
* @param {string=} siteId - ID of the live stream site object
|
|
1420
1468
|
*
|
|
1421
1469
|
* @return {Promise<Object>} - The list of stream URLs
|
|
1422
1470
|
*/
|
|
@@ -1532,3 +1580,382 @@ exports.StreamListUrls = async function({siteId}={}) {
|
|
|
1532
1580
|
console.error(error);
|
|
1533
1581
|
}
|
|
1534
1582
|
};
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Copy a portion of a live stream recording into a standard VoD object using the zero-copy content fabric API
|
|
1586
|
+
*
|
|
1587
|
+
* Limitations:
|
|
1588
|
+
* - currently requires the target object to be pre-created and have content encryption keys (CAPS)
|
|
1589
|
+
* - for audio and video to be sync'd, the live stream needs to have the beginning of the desired recording period
|
|
1590
|
+
* - 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
|
|
1591
|
+
* - 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
|
|
1592
|
+
* to allow running the live-to-vod command before the beginning of the recording expires.
|
|
1593
|
+
* - startTime and endTime are not currently implemented by this method
|
|
1594
|
+
*
|
|
1595
|
+
*
|
|
1596
|
+
* @methodGroup Live Stream
|
|
1597
|
+
* @namedParams
|
|
1598
|
+
* @param {string} name - Object ID or name of the live stream
|
|
1599
|
+
* @param {string} targetObjectId - Object ID of the target VOD object
|
|
1600
|
+
* @param {string=} eventId -
|
|
1601
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after copy to vod operations
|
|
1602
|
+
* @param {number=} recordingPeriod - Determines which recording period to copy, which are 0-based. -1 copies the current (or last) period
|
|
1603
|
+
*
|
|
1604
|
+
* @return {Promise<Object>} - The status response for the stream
|
|
1605
|
+
*/
|
|
1606
|
+
|
|
1607
|
+
/*
|
|
1608
|
+
Example fabric API flow:
|
|
1609
|
+
|
|
1610
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/live_to_vod/init -d @r1 -H "Authorization: Bearer $TOK"
|
|
1611
|
+
|
|
1612
|
+
{
|
|
1613
|
+
"live_qhash": "hq__5Zk1jSN8vNLUAXjQwMJV8F8J8ESXNvmVKkhaXySmGc1BXnJPG2FvvaXee4CXqvFHuGuU3fqLJc",
|
|
1614
|
+
"start_time": "",
|
|
1615
|
+
"end_time": "",
|
|
1616
|
+
"recording_period": -1,
|
|
1617
|
+
"streams": ["video", "audio"],
|
|
1618
|
+
"variant_key": "default"
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/init -H "Authorization: Bearer $TOK" -d @r2
|
|
1622
|
+
|
|
1623
|
+
{
|
|
1624
|
+
|
|
1625
|
+
"abr_profile": { ... },
|
|
1626
|
+
"offering_key": "default",
|
|
1627
|
+
"prod_master_hash": "tqw__HSQHBt7vYxWfCMPH5yXwKTfhdPcQ4Lcs9WUMUbTtnMbTZPTLo4BfJWPMGpoy1Dpv1wWQVtUtAtAr429TnVs",
|
|
1628
|
+
"variant_key": "default",
|
|
1629
|
+
"keep_other_streams": false
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
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"
|
|
1633
|
+
|
|
1634
|
+
|
|
1635
|
+
https://host-76-74-34-194.contentfabric.io/qlibs/ilib24CtWSJeVt9DiAzym8jB6THE9e7H/q/$QWT/call/media/abr_mezzanine/offerings/default/finalize -d '{}' -H "Authorization: Bearer $TOK"
|
|
1636
|
+
|
|
1637
|
+
*/
|
|
1638
|
+
|
|
1639
|
+
exports.StreamCopyToVod = async function({
|
|
1640
|
+
name,
|
|
1641
|
+
targetObjectId,
|
|
1642
|
+
eventId,
|
|
1643
|
+
streams=null,
|
|
1644
|
+
finalize=true,
|
|
1645
|
+
recordingPeriod=-1,
|
|
1646
|
+
startTime="",
|
|
1647
|
+
endTime=""
|
|
1648
|
+
}) {
|
|
1649
|
+
const conf = await this.LoadConf({name});
|
|
1650
|
+
const abrProfile = require("../abr_profiles/abr_profile_live_to_vod.js");
|
|
1651
|
+
|
|
1652
|
+
const status = await this.StreamStatus({name});
|
|
1653
|
+
const libraryId = status.library_id;
|
|
1654
|
+
|
|
1655
|
+
this.Log(`Copying stream ${name} to target ${targetObjectId}`);
|
|
1656
|
+
|
|
1657
|
+
ValidateObject(targetObjectId);
|
|
1658
|
+
|
|
1659
|
+
const targetLibraryId = await this.ContentObjectLibraryId({objectId: targetObjectId});
|
|
1660
|
+
|
|
1661
|
+
// Validation - ensure target object has content encryption keys
|
|
1662
|
+
const kmsAddress = await this.authClient.KMSAddress({objectId: targetObjectId});
|
|
1663
|
+
const kmsCapId = `eluv.caps.ikms${this.utils.AddressToHash(kmsAddress)}`;
|
|
1664
|
+
const kmsCap = await this.ContentObjectMetadata({
|
|
1665
|
+
libraryId: targetLibraryId,
|
|
1666
|
+
objectId: targetObjectId,
|
|
1667
|
+
metadataSubtree: kmsCapId
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
if(!kmsCap) {
|
|
1671
|
+
throw Error(`No content encryption key set for object ${targetObjectId}`);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
try {
|
|
1675
|
+
status.live_object_id = conf.objectId;
|
|
1676
|
+
|
|
1677
|
+
const liveHash = await this.LatestVersionHash({objectId: conf.objectId, libraryId});
|
|
1678
|
+
status.live_hash = liveHash;
|
|
1679
|
+
|
|
1680
|
+
if(eventId) {
|
|
1681
|
+
// Retrieve start and end times for the event
|
|
1682
|
+
let event = await this.CueInfo({eventId, status});
|
|
1683
|
+
if(event.eventStart && event.eventEnd) {
|
|
1684
|
+
startTime = event.eventStart;
|
|
1685
|
+
endTime = event.eventEnd;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
const {writeToken} = await this.EditContentObject({
|
|
1690
|
+
objectId: targetObjectId,
|
|
1691
|
+
libraryId: targetLibraryId
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
status.target_object_id = targetObjectId;
|
|
1695
|
+
status.target_library_id = targetLibraryId;
|
|
1696
|
+
status.target_write_token = writeToken;
|
|
1697
|
+
|
|
1698
|
+
this.Log("Process live source (takes around 20 sec per hour of content)");
|
|
1699
|
+
|
|
1700
|
+
await this.CallBitcodeMethod({
|
|
1701
|
+
libraryId: targetLibraryId,
|
|
1702
|
+
objectId: targetObjectId,
|
|
1703
|
+
writeToken,
|
|
1704
|
+
method: "/media/live_to_vod/init",
|
|
1705
|
+
body: {
|
|
1706
|
+
"live_qhash": liveHash,
|
|
1707
|
+
"start_time": startTime, // eg. "2023-10-03T02:09:02.00Z",
|
|
1708
|
+
"end_time": endTime, // eg. "2023-10-03T02:15:00.00Z",
|
|
1709
|
+
"streams": streams,
|
|
1710
|
+
"recording_period": recordingPeriod,
|
|
1711
|
+
"variant_key": "default"
|
|
1712
|
+
},
|
|
1713
|
+
constant: false,
|
|
1714
|
+
format: "text"
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1717
|
+
const abrMezInitBody = {
|
|
1718
|
+
abr_profile: abrProfile,
|
|
1719
|
+
"offering_key": "default",
|
|
1720
|
+
"prod_master_hash": writeToken,
|
|
1721
|
+
"variant_key": "default",
|
|
1722
|
+
"keep_other_streams": false
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
await this.CallBitcodeMethod({
|
|
1726
|
+
libraryId: targetLibraryId,
|
|
1727
|
+
objectId: targetObjectId,
|
|
1728
|
+
writeToken,
|
|
1729
|
+
method: "/media/abr_mezzanine/init",
|
|
1730
|
+
body: abrMezInitBody,
|
|
1731
|
+
constant: false,
|
|
1732
|
+
format: "text"
|
|
1733
|
+
});
|
|
1734
|
+
|
|
1735
|
+
try {
|
|
1736
|
+
await this.CallBitcodeMethod({
|
|
1737
|
+
libraryId: targetLibraryId,
|
|
1738
|
+
objectId: targetObjectId,
|
|
1739
|
+
writeToken,
|
|
1740
|
+
method: "/media/live_to_vod/copy",
|
|
1741
|
+
body: {},
|
|
1742
|
+
constant: false,
|
|
1743
|
+
format: "text"
|
|
1744
|
+
});
|
|
1745
|
+
} catch(error) {
|
|
1746
|
+
console.error("Unable to call /media/live_to_vod/copy", error);
|
|
1747
|
+
throw error;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
await this.CallBitcodeMethod({
|
|
1751
|
+
libraryId: targetLibraryId,
|
|
1752
|
+
objectId: targetObjectId,
|
|
1753
|
+
writeToken,
|
|
1754
|
+
method: "/media/abr_mezzanine/offerings/default/finalize",
|
|
1755
|
+
body: abrMezInitBody,
|
|
1756
|
+
constant: false,
|
|
1757
|
+
format: "text"
|
|
1758
|
+
});
|
|
1759
|
+
|
|
1760
|
+
if(finalize) {
|
|
1761
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1762
|
+
libraryId: targetLibraryId,
|
|
1763
|
+
objectId: targetObjectId,
|
|
1764
|
+
writeToken,
|
|
1765
|
+
commitMessage: "Live Stream to VoD"
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1768
|
+
status.target_hash = finalizeResponse.hash;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// Clean up unnecessary status items
|
|
1772
|
+
delete status.playout_urls;
|
|
1773
|
+
delete status.lro_status_url;
|
|
1774
|
+
delete status.recording_period;
|
|
1775
|
+
delete status.recording_period_sequence;
|
|
1776
|
+
delete status.edge_meta_size;
|
|
1777
|
+
delete status.insertions;
|
|
1778
|
+
|
|
1779
|
+
return status;
|
|
1780
|
+
} catch(error) {
|
|
1781
|
+
this.Log(error, true);
|
|
1782
|
+
throw error;
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* Remove a watermark for a live stream
|
|
1788
|
+
*
|
|
1789
|
+
* @methodGroup Live Stream
|
|
1790
|
+
* @namedParams
|
|
1791
|
+
* @param {string} objectId - Object ID of the live stream
|
|
1792
|
+
* @param {Array<string>} types - Specify which type of watermark to remove. Possible values:
|
|
1793
|
+
* - "image"
|
|
1794
|
+
* - "text"
|
|
1795
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after removing watermark
|
|
1796
|
+
*
|
|
1797
|
+
* @return {Promise<Object>} - The finalize response
|
|
1798
|
+
*/
|
|
1799
|
+
exports.StreamRemoveWatermark = async function({
|
|
1800
|
+
objectId,
|
|
1801
|
+
types,
|
|
1802
|
+
finalize=true
|
|
1803
|
+
}) {
|
|
1804
|
+
ValidateObject(objectId);
|
|
1805
|
+
|
|
1806
|
+
const libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1807
|
+
const {writeToken} = await this.EditContentObject({
|
|
1808
|
+
objectId,
|
|
1809
|
+
libraryId
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
this.Log(`Removing watermark types: ${types.join(", ")} ${libraryId} ${objectId}`);
|
|
1813
|
+
|
|
1814
|
+
const edgeWriteToken = await this.ContentObjectMetadata({
|
|
1815
|
+
objectId,
|
|
1816
|
+
libraryId,
|
|
1817
|
+
metadataSubtree: "/live_recording/fabric_config/edge_write_token"
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
const recordingParamsPath = "live_recording/recording_config/recording_params";
|
|
1821
|
+
|
|
1822
|
+
const recordingMetadata = await this.ContentObjectMetadata({
|
|
1823
|
+
libraryId,
|
|
1824
|
+
objectId,
|
|
1825
|
+
writeToken,
|
|
1826
|
+
metadataSubtree: recordingParamsPath,
|
|
1827
|
+
resolveLinks: false
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
if(!recordingMetadata) {
|
|
1831
|
+
throw Error("Stream object must be configured");
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
types.forEach(type => {
|
|
1835
|
+
if(type === "text") {
|
|
1836
|
+
delete recordingMetadata.simple_watermark;
|
|
1837
|
+
} else if(type === "image") {
|
|
1838
|
+
delete recordingMetadata.image_watermark;
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
|
|
1842
|
+
await this.ReplaceMetadata({
|
|
1843
|
+
libraryId,
|
|
1844
|
+
objectId,
|
|
1845
|
+
writeToken,
|
|
1846
|
+
metadataSubtree: recordingParamsPath,
|
|
1847
|
+
metadata: recordingMetadata
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
if(edgeWriteToken) {
|
|
1851
|
+
await this.ReplaceMetadata({
|
|
1852
|
+
libraryId,
|
|
1853
|
+
objectId,
|
|
1854
|
+
writeToken: edgeWriteToken,
|
|
1855
|
+
metadataSubtree: recordingParamsPath,
|
|
1856
|
+
metadata: recordingMetadata
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
if(finalize) {
|
|
1861
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1862
|
+
libraryId,
|
|
1863
|
+
objectId,
|
|
1864
|
+
writeToken,
|
|
1865
|
+
commitMessage: "Watermark removed"
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
return finalizeResponse;
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* Create a watermark for a live stream
|
|
1874
|
+
*
|
|
1875
|
+
* @methodGroup Live Stream
|
|
1876
|
+
* @namedParams
|
|
1877
|
+
* @param {string} objectId - Object ID of the live stream
|
|
1878
|
+
* @param {Object} simpleWatermark - Text watermark
|
|
1879
|
+
* @param {Object} imageWatermark - Image watermark
|
|
1880
|
+
* @param {boolean=} finalize - If enabled, target object will be finalized after adding watermark
|
|
1881
|
+
*
|
|
1882
|
+
* @return {Promise<Object>} - The finalize response
|
|
1883
|
+
*/
|
|
1884
|
+
exports.StreamAddWatermark = async function({
|
|
1885
|
+
objectId,
|
|
1886
|
+
simpleWatermark,
|
|
1887
|
+
imageWatermark,
|
|
1888
|
+
finalize=true
|
|
1889
|
+
}) {
|
|
1890
|
+
ValidateObject(objectId);
|
|
1891
|
+
|
|
1892
|
+
const libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1893
|
+
const {writeToken} = await this.EditContentObject({
|
|
1894
|
+
objectId,
|
|
1895
|
+
libraryId
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
const edgeWriteToken = await this.ContentObjectMetadata({
|
|
1899
|
+
objectId,
|
|
1900
|
+
libraryId,
|
|
1901
|
+
metadataSubtree: "/live_recording/fabric_config/edge_write_token"
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
this.Log(`Adding watermarking type: ${imageWatermark ? "image" : "text"} ${libraryId} ${objectId}`);
|
|
1905
|
+
|
|
1906
|
+
const recordingParamsPath = "live_recording/recording_config/recording_params";
|
|
1907
|
+
|
|
1908
|
+
const recordingMetadata = await this.ContentObjectMetadata({
|
|
1909
|
+
libraryId,
|
|
1910
|
+
objectId,
|
|
1911
|
+
writeToken,
|
|
1912
|
+
metadataSubtree: recordingParamsPath,
|
|
1913
|
+
resolveLinks: false
|
|
1914
|
+
});
|
|
1915
|
+
|
|
1916
|
+
if(!recordingMetadata) {
|
|
1917
|
+
throw Error("Stream object must be configured");
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
if(simpleWatermark) {
|
|
1921
|
+
recordingMetadata.simple_watermark = simpleWatermark;
|
|
1922
|
+
} else if(imageWatermark) {
|
|
1923
|
+
recordingMetadata.image_watermark = imageWatermark;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
await this.ReplaceMetadata({
|
|
1927
|
+
libraryId,
|
|
1928
|
+
objectId,
|
|
1929
|
+
writeToken,
|
|
1930
|
+
metadataSubtree: recordingParamsPath,
|
|
1931
|
+
metadata: recordingMetadata
|
|
1932
|
+
});
|
|
1933
|
+
|
|
1934
|
+
if(edgeWriteToken) {
|
|
1935
|
+
await this.ReplaceMetadata({
|
|
1936
|
+
libraryId,
|
|
1937
|
+
objectId,
|
|
1938
|
+
writeToken: edgeWriteToken,
|
|
1939
|
+
metadataSubtree: recordingParamsPath,
|
|
1940
|
+
metadata: recordingMetadata
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const response = {
|
|
1945
|
+
"imageWatermark": recordingMetadata.image_watermark,
|
|
1946
|
+
"textWatermark": recordingMetadata.simple_watermark
|
|
1947
|
+
};
|
|
1948
|
+
|
|
1949
|
+
if(finalize) {
|
|
1950
|
+
const finalizeResponse = await this.FinalizeContentObject({
|
|
1951
|
+
libraryId,
|
|
1952
|
+
objectId,
|
|
1953
|
+
writeToken,
|
|
1954
|
+
commitMessage: "Watermark set"
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
response.hash = finalizeResponse.hash;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
return response;
|
|
1961
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { ElvClient } = require("../src/ElvClient");
|
|
2
|
+
|
|
3
|
+
const yargs = require("yargs");
|
|
4
|
+
const argv = yargs
|
|
5
|
+
.option("msg", {
|
|
6
|
+
description: "message to be decoded",
|
|
7
|
+
type: "string"
|
|
8
|
+
})
|
|
9
|
+
.option("config-url", {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "URL pointing to the Fabric configuration. i.e. https://main.net955210.contentfabric.io/config"
|
|
12
|
+
})
|
|
13
|
+
.demandOption(
|
|
14
|
+
["msg"],
|
|
15
|
+
"\nUsage: PRIVATE_KEY=<private-key> node DecodeSignedMessage.js --msg <message>\n"
|
|
16
|
+
)
|
|
17
|
+
.strict().argv;
|
|
18
|
+
const ClientConfiguration = (!argv["config-url"]) ? (require("../TestConfiguration.json")) : {"config-url": argv["config-url"]};
|
|
19
|
+
|
|
20
|
+
const DecodeSignedMessage = async ({msg}) => {
|
|
21
|
+
try {
|
|
22
|
+
const client = await ElvClient.FromConfigurationUrl({
|
|
23
|
+
configUrl: ClientConfiguration["config-url"],
|
|
24
|
+
});
|
|
25
|
+
const wallet = client.GenerateWallet();
|
|
26
|
+
const signer = wallet.AddAccount({
|
|
27
|
+
privateKey: process.env.PRIVATE_KEY
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
client.SetSigner({signer});
|
|
31
|
+
|
|
32
|
+
const decodedMessage = await client.DecodeSignedMessageJSON({
|
|
33
|
+
signedMessage: msg,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
console.log(JSON.stringify(decodedMessage));
|
|
37
|
+
} catch(error) {
|
|
38
|
+
console.error(error);
|
|
39
|
+
console.error(JSON.stringify(error, null, 2));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.exit(0);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
DecodeSignedMessage({msg: argv.msg});
|