@eluvio/elv-client-js 4.2.33 → 4.2.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ElvClient-min.js +1 -1
- package/dist/ElvClient-node-min.js +1 -1
- package/dist/ElvFrameClient-min.js +1 -1
- package/dist/ElvPermissionsClient-min.js +1 -1
- package/dist/ElvWalletClient-min.js +1 -1
- package/dist/ElvWalletClient-node-min.js +1 -1
- package/dist/src/AuthorizationClient.js +41 -32
- package/dist/src/ElvClient.js +391 -285
- package/dist/src/EthClient.js +3 -2
- package/dist/src/FrameClient.js +1 -1
- package/dist/src/HttpClient.js +59 -52
- package/dist/src/client/ABRPublishing.js +71 -60
- package/dist/src/client/ContentAccess.js +585 -207
- package/dist/src/client/ContentManagement.js +667 -67
- package/dist/src/client/Files.js +1400 -844
- package/dist/src/client/LiveConf.js +66 -45
- package/dist/src/client/LiveStream.js +1649 -1194
- package/package.json +1 -1
- package/src/HttpClient.js +9 -2
- package/src/client/ContentAccess.js +6 -2
- package/src/client/ContentManagement.js +7 -5
- package/src/client/LiveConf.js +19 -13
- package/src/client/LiveStream.js +66 -12
- package/testScripts/Test.js +2 -14
package/package.json
CHANGED
package/src/HttpClient.js
CHANGED
|
@@ -65,10 +65,16 @@ class HttpClient {
|
|
|
65
65
|
allowFailover=true,
|
|
66
66
|
forceFailover=false,
|
|
67
67
|
allowRetry=true,
|
|
68
|
-
uriIndex
|
|
68
|
+
uriIndex,
|
|
69
|
+
nodeUrl
|
|
69
70
|
}) {
|
|
70
71
|
let baseURI = this.BaseURI(uriIndex);
|
|
71
72
|
|
|
73
|
+
if(nodeUrl) {
|
|
74
|
+
baseURI = new URI(nodeUrl);
|
|
75
|
+
allowFailover = false;
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
// If URL contains a write token, it must go to the correct server and can not fail over
|
|
73
79
|
const writeTokenMatch = path.replace(/^\//, "").match(/(qlibs\/ilib[a-zA-Z0-9]+|q|qid)\/(tqw__[a-zA-Z0-9]+)/);
|
|
74
80
|
const writeToken = writeTokenMatch ? writeTokenMatch[2] : undefined;
|
|
@@ -171,7 +177,8 @@ class HttpClient {
|
|
|
171
177
|
headers,
|
|
172
178
|
attempts: attempts + 1,
|
|
173
179
|
uriIndex,
|
|
174
|
-
forceFailover
|
|
180
|
+
forceFailover,
|
|
181
|
+
nodeUrl
|
|
175
182
|
});
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -1902,6 +1902,8 @@ exports.MakeFileServiceRequest = async function({
|
|
|
1902
1902
|
* @param {boolean=} constant=true - If specified, a GET request authenticated with an AccessRequest will be made.
|
|
1903
1903
|
* Otherwise, a POST with an UpdateRequest will be performed
|
|
1904
1904
|
* @param {string=} format=json - The format of the response
|
|
1905
|
+
* @param {string=} nodeUrl - If specified, the call is made directly to this fabric node instead of
|
|
1906
|
+
* the client's currently configured node(s)
|
|
1905
1907
|
*
|
|
1906
1908
|
* @returns {Promise<format>} - The response from the call in the specified format
|
|
1907
1909
|
*/
|
|
@@ -1916,7 +1918,8 @@ exports.CallBitcodeMethod = async function({
|
|
|
1916
1918
|
body={},
|
|
1917
1919
|
headers={},
|
|
1918
1920
|
constant=true,
|
|
1919
|
-
format="json"
|
|
1921
|
+
format="json",
|
|
1922
|
+
nodeUrl
|
|
1920
1923
|
}) {
|
|
1921
1924
|
ValidateParameters({libraryId, objectId, versionHash});
|
|
1922
1925
|
if(!method) { throw "Bitcode method not specified"; }
|
|
@@ -1960,7 +1963,8 @@ exports.CallBitcodeMethod = async function({
|
|
|
1960
1963
|
method: verb,
|
|
1961
1964
|
path,
|
|
1962
1965
|
queryParams,
|
|
1963
|
-
allowFailover: false
|
|
1966
|
+
allowFailover: false,
|
|
1967
|
+
nodeUrl
|
|
1964
1968
|
}),
|
|
1965
1969
|
this.HttpClient.debug,
|
|
1966
1970
|
this.HttpClient.Log.bind(this.HttpClient)
|
|
@@ -813,10 +813,11 @@ exports.CreateNonOwnerCap = async function({objectId, libraryId, publicKey, writ
|
|
|
813
813
|
* @param {object=} options -
|
|
814
814
|
* @param {object=} options.meta - New metadata for the object - will be merged into existing metadata if specified
|
|
815
815
|
* @param {string=} options.type - New type for the object - Object ID, version hash or name of type
|
|
816
|
+
* @param {string=} nodeUrl - Fabric node URL to create the draft on
|
|
816
817
|
*
|
|
817
818
|
* @returns {Promise<object>} - Response containing the object ID and write token of the draft, as well as URL of node handling the draft
|
|
818
819
|
*/
|
|
819
|
-
exports.EditContentObject = async function({libraryId, objectId, options={}}) {
|
|
820
|
+
exports.EditContentObject = async function({libraryId, objectId, options={}, nodeUrl}) {
|
|
820
821
|
ValidateParameters({libraryId, objectId});
|
|
821
822
|
|
|
822
823
|
this.Log(`Opening content draft: ${libraryId} ${objectId}`);
|
|
@@ -842,11 +843,12 @@ exports.EditContentObject = async function({libraryId, objectId, options={}}) {
|
|
|
842
843
|
headers: await this.authClient.AuthorizationHeader({libraryId, objectId, update: true}),
|
|
843
844
|
method: "POST",
|
|
844
845
|
path: path,
|
|
845
|
-
body: options
|
|
846
|
+
body: options,
|
|
847
|
+
nodeUrl
|
|
846
848
|
});
|
|
847
849
|
// extract the url for the node that handled the request
|
|
848
850
|
// TODO: remove/simplify after we start using /nodes API call to get node URLs for write tokens
|
|
849
|
-
const
|
|
851
|
+
const responseNodeUrl = (new URL(rawEditResponse.url)).origin;
|
|
850
852
|
const editResponse = await this.utils.ResponseToJson(
|
|
851
853
|
rawEditResponse,
|
|
852
854
|
this.HttpClient.debug,
|
|
@@ -854,11 +856,11 @@ exports.EditContentObject = async function({libraryId, objectId, options={}}) {
|
|
|
854
856
|
);
|
|
855
857
|
|
|
856
858
|
// Record the node used in creating this write token
|
|
857
|
-
this.RecordWriteToken({writeToken: editResponse.write_token, fabricNodeUrl:
|
|
859
|
+
this.RecordWriteToken({writeToken: editResponse.write_token, fabricNodeUrl: responseNodeUrl});
|
|
858
860
|
|
|
859
861
|
editResponse.writeToken = editResponse.write_token;
|
|
860
862
|
editResponse.objectId = editResponse.id;
|
|
861
|
-
editResponse.nodeUrl =
|
|
863
|
+
editResponse.nodeUrl = responseNodeUrl;
|
|
862
864
|
|
|
863
865
|
return editResponse;
|
|
864
866
|
};
|
package/src/client/LiveConf.js
CHANGED
|
@@ -538,8 +538,12 @@ class LiveConf {
|
|
|
538
538
|
conf.live_recording.recording_config.recording_params.description = `Ingest stream ${fileName}`;
|
|
539
539
|
conf.live_recording.recording_config.recording_params.name = `Ingest stream ${fileName}`;
|
|
540
540
|
conf.live_recording.recording_config.recording_params.xc_params.sample_rate = sampleRate;
|
|
541
|
-
|
|
542
|
-
|
|
541
|
+
|
|
542
|
+
// Encoding dimensions default to the source but are overridden by profile values if present
|
|
543
|
+
conf.live_recording.recording_config.recording_params.xc_params.enc_height =
|
|
544
|
+
customXcParams?.enc_height ?? videoStream.height;
|
|
545
|
+
conf.live_recording.recording_config.recording_params.xc_params.enc_width =
|
|
546
|
+
customXcParams?.enc_width ?? videoStream.width;
|
|
543
547
|
|
|
544
548
|
for(let i =0; i < Object.keys(audioStreams).length; i ++) {
|
|
545
549
|
conf.live_recording.recording_config.recording_params.xc_params.audio_index[i] = parseInt(Object.keys(audioStreams)[i]);
|
|
@@ -585,25 +589,27 @@ class LiveConf {
|
|
|
585
589
|
customXcParams?.video_seg_duration_ts ?? segDurations.video;
|
|
586
590
|
conf.live_recording.recording_config.recording_params.xc_params.force_keyint = segDurations.keyint;
|
|
587
591
|
|
|
588
|
-
//
|
|
589
|
-
if(
|
|
590
|
-
|
|
592
|
+
// These must be recomputed from scratch on every (re)configuration to avoid stale values
|
|
593
|
+
if(R.isNil(customXcParams?.video_time_base)) {
|
|
594
|
+
if(segDurations.videoTimeBase) {
|
|
595
|
+
conf.live_recording.recording_config.recording_params.xc_params.video_time_base = segDurations.videoTimeBase;
|
|
591
596
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
597
|
+
// Note 'source_timescale' needs to be set to the output timebase and is used by playout
|
|
598
|
+
if(R.isNil(customRecordingParams?.source_timescale)) {
|
|
599
|
+
conf.live_recording.recording_config.recording_params.source_timescale = this.calcOutputTimebase(segDurations.videoTimeBase);
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
conf.live_recording.recording_config.recording_params.xc_params.video_time_base = null;
|
|
595
603
|
}
|
|
596
604
|
}
|
|
597
|
-
if(
|
|
598
|
-
conf.live_recording.recording_config.recording_params.xc_params.video_frame_duration_ts =
|
|
605
|
+
if(R.isNil(customXcParams?.video_frame_duration_ts)) {
|
|
606
|
+
conf.live_recording.recording_config.recording_params.xc_params.video_frame_duration_ts =
|
|
607
|
+
segDurations.videoFrameDurationTs || null;
|
|
599
608
|
}
|
|
600
609
|
|
|
601
610
|
const ladder_specs = customSettings.liveRecordingConfigProfile?.playout_config?.ladder_specs;
|
|
602
611
|
const ladderProfile = ladder_specs?.video?.length > 0 ? ladder_specs : DefaultABRLadder;
|
|
603
612
|
|
|
604
|
-
conf.live_recording.recording_config.recording_params.xc_params.enc_height = videoStream.height;
|
|
605
|
-
conf.live_recording.recording_config.recording_params.xc_params.enc_width = videoStream.width;
|
|
606
|
-
|
|
607
613
|
// Reset ladder specs (updating existing stream will carry over old specs
|
|
608
614
|
conf.live_recording.recording_config.recording_params.ladder_specs = [];
|
|
609
615
|
|
package/src/client/LiveStream.js
CHANGED
|
@@ -393,15 +393,42 @@ exports.StreamCreate = async function({
|
|
|
393
393
|
metadata
|
|
394
394
|
});
|
|
395
395
|
|
|
396
|
+
// If the profile is changing, only carry over name and url so stale
|
|
397
|
+
// settings from the old profile are cleared
|
|
398
|
+
const changingProfile = liveRecordingConfig?.name && liveRecordingConfig.name !== oldProfile;
|
|
399
|
+
const baseLiveRecordingConfig = changingProfile ?
|
|
400
|
+
R.pick(["url", "name"], currentLiveRecordingConfig) :
|
|
401
|
+
currentLiveRecordingConfig;
|
|
402
|
+
|
|
396
403
|
// Full overwrite avoids the server-side merge mixing streams from the old and new profile
|
|
397
404
|
await this.ReplaceMetadata({
|
|
398
405
|
libraryId,
|
|
399
406
|
objectId,
|
|
400
407
|
writeToken,
|
|
401
408
|
metadataSubtree: "live_recording_config",
|
|
402
|
-
metadata: R.mergeDeepRight(
|
|
409
|
+
metadata: R.mergeDeepRight(baseLiveRecordingConfig, liveRecordingConfig)
|
|
403
410
|
});
|
|
404
411
|
|
|
412
|
+
if(changingProfile) {
|
|
413
|
+
// Clear live_recording and live_recording_overrides so stale settings from the
|
|
414
|
+
// previous profile don't persist, matching StreamApplyProfile
|
|
415
|
+
await this.ReplaceMetadata({
|
|
416
|
+
libraryId,
|
|
417
|
+
objectId,
|
|
418
|
+
writeToken,
|
|
419
|
+
metadataSubtree: "live_recording",
|
|
420
|
+
metadata: {}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await this.ReplaceMetadata({
|
|
424
|
+
libraryId,
|
|
425
|
+
objectId,
|
|
426
|
+
writeToken,
|
|
427
|
+
metadataSubtree: "live_recording_overrides",
|
|
428
|
+
metadata: {}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
405
432
|
try {
|
|
406
433
|
await this.CreateLinks({
|
|
407
434
|
libraryId,
|
|
@@ -1269,13 +1296,12 @@ exports.StreamStartRecording = async function({name, start=false}) {
|
|
|
1269
1296
|
fabURI = "https://" + fabURI;
|
|
1270
1297
|
}
|
|
1271
1298
|
|
|
1272
|
-
this.SetNodes({fabricURIs: [fabURI]});
|
|
1273
|
-
|
|
1274
1299
|
console.log("Node URI", fabURI, "ID", liveRecording.fabric_config.ingress_node_id);
|
|
1275
1300
|
|
|
1276
1301
|
let response = await this.EditContentObject({
|
|
1277
1302
|
libraryId: libraryId,
|
|
1278
|
-
objectId: objectId
|
|
1303
|
+
objectId: objectId,
|
|
1304
|
+
nodeUrl: fabURI
|
|
1279
1305
|
});
|
|
1280
1306
|
const edgeToken = response.write_token;
|
|
1281
1307
|
console.log("Edge token:", edgeToken);
|
|
@@ -1285,7 +1311,8 @@ exports.StreamStartRecording = async function({name, start=false}) {
|
|
|
1285
1311
|
*/
|
|
1286
1312
|
response = await this.EditContentObject({
|
|
1287
1313
|
libraryId: libraryId,
|
|
1288
|
-
objectId: objectId
|
|
1314
|
+
objectId: objectId,
|
|
1315
|
+
nodeUrl: fabURI
|
|
1289
1316
|
});
|
|
1290
1317
|
let writeToken = response.write_token;
|
|
1291
1318
|
|
|
@@ -1371,8 +1398,6 @@ exports.StreamRestartRecording = async function({name}) {
|
|
|
1371
1398
|
throw new Error("Unable to restart stream - ingress node API is unavailable");
|
|
1372
1399
|
}
|
|
1373
1400
|
|
|
1374
|
-
this.SetNodes({fabricURIs: [fabricApi]});
|
|
1375
|
-
|
|
1376
1401
|
let mainWriteToken;
|
|
1377
1402
|
let newEdgeWriteToken;
|
|
1378
1403
|
let mainObjectFinalizeStarted = false;
|
|
@@ -1380,12 +1405,14 @@ exports.StreamRestartRecording = async function({name}) {
|
|
|
1380
1405
|
try {
|
|
1381
1406
|
({writeToken: mainWriteToken} = await this.EditContentObject({
|
|
1382
1407
|
libraryId,
|
|
1383
|
-
objectId
|
|
1408
|
+
objectId,
|
|
1409
|
+
nodeUrl: fabricApi
|
|
1384
1410
|
}));
|
|
1385
1411
|
|
|
1386
1412
|
({writeToken: newEdgeWriteToken} = await this.EditContentObject({
|
|
1387
1413
|
libraryId,
|
|
1388
|
-
objectId
|
|
1414
|
+
objectId,
|
|
1415
|
+
nodeUrl: fabricApi
|
|
1389
1416
|
}));
|
|
1390
1417
|
|
|
1391
1418
|
await this.MergeMetadata({
|
|
@@ -1518,6 +1545,34 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
1518
1545
|
});
|
|
1519
1546
|
} catch(error) {
|
|
1520
1547
|
console.log("LRO Start (failed): ", error);
|
|
1548
|
+
|
|
1549
|
+
// Check for unsupported input conditions before returning
|
|
1550
|
+
// generic "LRO start failed - must create a stream first" error
|
|
1551
|
+
try {
|
|
1552
|
+
const configMeta = await this.ContentObjectMetadata({
|
|
1553
|
+
libraryId: status.libraryId,
|
|
1554
|
+
objectId: status.objectId,
|
|
1555
|
+
metadataSubtree: "live_recording_config",
|
|
1556
|
+
select: [
|
|
1557
|
+
"probe_info",
|
|
1558
|
+
"input_stream_info"
|
|
1559
|
+
]
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
const probeInfo = configMeta?.probe_info || configMeta?.input_stream_info;
|
|
1563
|
+
const unsupportedAudio = (probeInfo?.streams || [])
|
|
1564
|
+
.find(stream => stream.codec_type === "audio" && stream.sample_rate === 44100);
|
|
1565
|
+
|
|
1566
|
+
if(unsupportedAudio) {
|
|
1567
|
+
return {
|
|
1568
|
+
state: status.state,
|
|
1569
|
+
error: "Unsupported audio sample rate: 44.1 kHz. Only 48 kHz audio is supported."
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
} catch(probeError) {
|
|
1573
|
+
console.error("Unable to check audio sample rate", probeError);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1521
1576
|
return {
|
|
1522
1577
|
state: status.state,
|
|
1523
1578
|
error: "LRO start failed - must create a stream first"
|
|
@@ -1567,8 +1622,6 @@ exports.StreamStopRecording = async function({name}) {
|
|
|
1567
1622
|
fabURI = "https://" + fabURI;
|
|
1568
1623
|
}
|
|
1569
1624
|
|
|
1570
|
-
this.SetNodes({fabricURIs: [fabURI]});
|
|
1571
|
-
|
|
1572
1625
|
const metaEdgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
1573
1626
|
|
|
1574
1627
|
if(!metaEdgeWriteToken) {
|
|
@@ -1606,7 +1659,8 @@ exports.StreamStopRecording = async function({name}) {
|
|
|
1606
1659
|
|
|
1607
1660
|
const {writeToken} = await this.EditContentObject({
|
|
1608
1661
|
libraryId: libraryId,
|
|
1609
|
-
objectId: objectId
|
|
1662
|
+
objectId: objectId,
|
|
1663
|
+
nodeUrl: fabURI
|
|
1610
1664
|
});
|
|
1611
1665
|
|
|
1612
1666
|
// Set stop time and inactive state
|
package/testScripts/Test.js
CHANGED
|
@@ -11,7 +11,7 @@ const Test = async () => {
|
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
13
|
const client = await ElvClient.FromNetworkName({
|
|
14
|
-
networkName: "
|
|
14
|
+
networkName: "demo"
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
const wallet = client.GenerateWallet();
|
|
@@ -21,19 +21,7 @@ const Test = async () => {
|
|
|
21
21
|
|
|
22
22
|
client.SetSigner({signer});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
configUrl.pathname = "/config";
|
|
26
|
-
// if(geo) { configUrl.searchParams.set("elvgeo", geo); } // optional region scoping
|
|
27
|
-
|
|
28
|
-
const fabricInfo = await (await fetch(configUrl.toString())).json();
|
|
29
|
-
// fabricInfo.network.services.* e.g. live_egress, ethereum, authority, etc.
|
|
30
|
-
const liveEgressUrls = fabricInfo?.network?.services?.live_egress;
|
|
31
|
-
console.log('fabricINfo', fabricInfo.network?.services?.tenant_query)
|
|
32
|
-
|
|
33
|
-
// tenant_query nodes are now wired into the client and used automatically by TenantContent:
|
|
34
|
-
console.log('client tenantQueryURIs', client.Nodes().tenantQueryURIs)
|
|
35
|
-
// const result = await client.TenantContent({limit: 5});
|
|
36
|
-
// console.log(JSON.stringify(result, null, 2));
|
|
24
|
+
console.log(await client.ContentLibraries());
|
|
37
25
|
} catch(error) {
|
|
38
26
|
console.error(error);
|
|
39
27
|
console.error(JSON.stringify(error, null, 2));
|