@eluvio/elv-client-js 4.0.83 → 4.0.85

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.
@@ -358,17 +358,17 @@ const StreamGenerateOffering = async({
358
358
  * @return {Promise<Object>} - The status response for the object, as well as logs, warnings and errors from the master initialization
359
359
  */
360
360
  exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
361
- let conf = await this.LoadConf({name});
361
+ let objectId = name;
362
362
  let status = {name: name};
363
363
 
364
364
  try {
365
- let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
365
+ let libraryId = await this.ContentObjectLibraryId({objectId});
366
366
  status.library_id = libraryId;
367
- status.object_id = conf.objectId;
367
+ status.object_id = objectId;
368
368
 
369
369
  let mainMeta = await this.ContentObjectMetadata({
370
- libraryId: libraryId,
371
- objectId: conf.objectId,
370
+ libraryId,
371
+ objectId,
372
372
  select: [
373
373
  "live_recording_config",
374
374
  "live_recording"
@@ -416,7 +416,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
416
416
  status.stream_id = edgeWriteToken; // By convention the stream ID is its write token
417
417
  let edgeMeta = await this.ContentObjectMetadata({
418
418
  libraryId: libraryId,
419
- objectId: conf.objectId,
419
+ objectId: objectId,
420
420
  writeToken: edgeWriteToken,
421
421
  select: [
422
422
  "live_recording"
@@ -442,8 +442,14 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
442
442
  let tlro = period.live_recording_handle;
443
443
  status.tlro = tlro;
444
444
 
445
- let sinceLastFinalize = Math.floor(new Date().getTime() / 1000) -
446
- period.video_finalized_parts_info.last_finalization_time /1000000;
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
+ }
447
453
 
448
454
  let recording_period = {
449
455
  activation_time_epoch_sec: period.recording_start_time_epoch_sec,
@@ -451,15 +457,15 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
451
457
  start_time_text: new Date(period.start_time_epoch_sec * 1000).toLocaleString(),
452
458
  end_time_epoch_sec: period.end_time_epoch_sec,
453
459
  end_time_text: period.end_time_epoch_sec === 0 ? null : new Date(period.end_time_epoch_sec * 1000).toLocaleString(),
454
- video_parts: period.video_finalized_parts_info.n_parts,
455
- video_last_part_finalized_epoch_sec: period.video_finalized_parts_info.last_finalization_time / 1000000,
460
+ video_parts: videoFinalizedParts,
461
+ video_last_part_finalized_epoch_sec: videoLastFinalizationTimeEpochSec,
456
462
  video_since_last_finalize_sec : sinceLastFinalize
457
463
  };
458
464
  status.recording_period = recording_period;
459
465
 
460
466
  status.lro_status_url = await this.FabricUrl({
461
467
  libraryId: libraryId,
462
- objectId: conf.objectId,
468
+ objectId: objectId,
463
469
  writeToken: edgeWriteToken,
464
470
  call: "live/status/" + tlro
465
471
  });
@@ -499,7 +505,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
499
505
  }
500
506
 
501
507
  // Convert LRO 'state' to desired 'state'
502
- if(state === "running" && period.video_finalized_parts_info.last_finalization_time === 0) {
508
+ if(state === "running" && videoLastFinalizationTimeEpochSec <= 0) {
503
509
  state = "starting";
504
510
  } else if(state === "running" && sinceLastFinalize > 32.9) {
505
511
  state = "stalled";
@@ -510,8 +516,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
510
516
 
511
517
  if((state === "running" || state === "stalled" || state === "starting") && stopLro) {
512
518
  lroStopUrl = await this.FabricUrl({
513
- libraryId: libraryId,
514
- objectId: conf.objectId,
519
+ libraryId,
520
+ objectId,
515
521
  writeToken: edgeWriteToken,
516
522
  call: "live/stop/" + tlro
517
523
  });
@@ -532,7 +538,6 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
532
538
 
533
539
  if(state === "running") {
534
540
  let playout_urls = {};
535
- let objectId = conf.objectId;
536
541
  let playout_options = await this.PlayoutOptions({
537
542
  objectId,
538
543
  linkPath: "public/asset_metadata/sources/default"
@@ -593,7 +598,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
593
598
  if(networkInfo.name.includes("demo")) {
594
599
  embed_net = "demo";
595
600
  }
596
- let embed_url = `https://embed.v3.contentfabric.io/?net=${embed_net}&p&ct=h&oid=${conf.objectId}&mt=lv&ath=${token}`;
601
+ let embed_url = `https://embed.v3.contentfabric.io/?net=${embed_net}&p&ct=h&oid=${objectId}&mt=lv&ath=${token}`;
597
602
  playout_urls.embed_url = embed_url;
598
603
 
599
604
  status.playout_urls = playout_urls;
@@ -618,7 +623,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
618
623
  */
619
624
  exports.StreamCreate = async function({name, start=false}) {
620
625
  let status = await this.StreamStatus({name});
621
- if(status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
626
+ if(status.state != "uninitialized" && status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
622
627
  return {
623
628
  state: status.state,
624
629
  error: "stream still active - must terminate first"
@@ -728,7 +733,7 @@ exports.StreamCreate = async function({name, start=false}) {
728
733
  */
729
734
  exports.StreamStartOrStopOrReset = async function({name, op}) {
730
735
  try {
731
- let status = await this.StreamStatus({name});
736
+ let status = await this.StreamStatus({name})
732
737
  if(status.state != "stopped") {
733
738
  if(op === "start") {
734
739
  status.error = "Unable to start stream - state: " + status.state;
@@ -816,9 +821,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
816
821
  exports.StreamStopSession = async function({name}) {
817
822
  try {
818
823
  this.Log(`Terminating stream session for: ${name}`);
819
- let conf = await this.LoadConf({name});
820
-
821
- let {objectId} = conf;
824
+ let objectId = name;
822
825
  let libraryId = await this.ContentObjectLibraryId({objectId});
823
826
 
824
827
  let mainMeta = await this.ContentObjectMetadata({
@@ -887,8 +890,7 @@ exports.StreamStopSession = async function({name}) {
887
890
  fabric_config: {
888
891
  edge_write_token: ""
889
892
  }
890
- },
891
- recording_stop_time: stopTime
893
+ }
892
894
  };
893
895
 
894
896
  await this.MergeMetadata({
@@ -933,9 +935,8 @@ exports.StreamInitialize = async function({name, drm=false, format}) {
933
935
  let typeLiveStream;
934
936
 
935
937
  // 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({
938
+ const tenantContractId = await this.userProfileClient.TenantContractId();
939
+ const {live_stream, title} = await this.ContentObjectMetadata({
939
940
  libraryId: tenantContractId.replace("iten", "ilib"),
940
941
  objectId: tenantContractId.replace("iten", "iq__"),
941
942
  metadataSubtree: "public/content_types",
@@ -964,7 +965,7 @@ exports.StreamInitialize = async function({name, drm=false, format}) {
964
965
  };
965
966
 
966
967
  /**
967
- * Set the Live Stream offering
968
+ * Create a dummy VoD offering and initialize DRM keys.
968
969
  *
969
970
  * @methodGroup Live Stream
970
971
  * @namedParams
@@ -980,7 +981,7 @@ exports.StreamInitialize = async function({name, drm=false, format}) {
980
981
  */
981
982
  exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveStream, drm=false, format}) {
982
983
  let status = await this.StreamStatus({name});
983
- if(status.state != "inactive" && status.state != "stopped") {
984
+ if(status.state != "uninitialized" && status.state != "inactive" && status.state != "stopped") {
984
985
  return {
985
986
  state: status.state,
986
987
  error: "stream still active - must terminate first"
@@ -1162,13 +1163,12 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
1162
1163
  }
1163
1164
  }
1164
1165
 
1165
- let conf = await this.LoadConf({name});
1166
- let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
1167
- let objectId = conf.objectId;
1166
+ let objectId = name;
1167
+ let libraryId = await this.ContentObjectLibraryId({objectId});
1168
1168
 
1169
1169
  let mainMeta = await this.ContentObjectMetadata({
1170
- libraryId: libraryId,
1171
- objectId: conf.objectId
1170
+ libraryId,
1171
+ objectId
1172
1172
  });
1173
1173
 
1174
1174
  let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
@@ -1182,8 +1182,8 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
1182
1182
  let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
1183
1183
 
1184
1184
  let edgeMeta = await this.ContentObjectMetadata({
1185
- libraryId: libraryId,
1186
- objectId: conf.objectId,
1185
+ libraryId,
1186
+ objectId,
1187
1187
  writeToken: edgeWriteToken
1188
1188
  });
1189
1189
 
@@ -1298,73 +1298,52 @@ exports.StreamInsertion = async function({name, insertionTime, sinceStart=false,
1298
1298
  };
1299
1299
 
1300
1300
  /**
1301
- * Load cached stream configuration
1301
+ * Configure the stream based on built-in logic and optional custom settings.
1302
1302
  *
1303
- * @methodGroup Live Stream
1304
- * @namedParams
1305
- * @param {string} name - Object ID or name of the live stream object
1306
- *
1307
- * @return {Promise<Object>} - The configuration of the stream
1308
- */
1309
- exports.LoadConf = async function({name}) {
1310
- if(name.startsWith("iq__")) {
1311
- return {
1312
- name: name,
1313
- objectId: name
1314
- };
1315
- }
1316
-
1317
- // If name is not a QID, load liveconf.json
1318
- let streamsBuf;
1319
- try {
1320
- streamsBuf = fs.readFileSync(
1321
- path.resolve(__dirname, "../liveconf.json")
1322
- );
1323
- } catch(error) {
1324
- console.log("Stream name must be a QID or a label in liveconf.json");
1325
- return {};
1326
- }
1327
- const streams = JSON.parse(streamsBuf);
1328
- const conf = streams[name];
1329
- if(conf === null) {
1330
- console.log("Bad name: ", name);
1331
- return {};
1332
- }
1333
-
1334
- return conf;
1335
- };
1336
-
1337
- /**
1338
- * 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
+ * }
1339
1321
  *
1340
1322
  * @methodGroup Live Stream
1341
1323
  * @namedParams
1342
1324
  * @param {string} name - Object ID or name of the live stream object
1343
1325
  * @param {Object=} customSettings - Additional options to customize configuration settings
1344
- * - audioBitrate
1345
- * - audioIndex
1346
- * - partTtl
1347
- * - channelLayout
1348
- *
1326
+ * @param {Object=} probeMetadata - Metadata for the probe. If not specified, a new probe will be configured
1349
1327
  * @return {Promise<Object>} - The status response for the stream
1350
1328
  *
1351
1329
  */
1352
- exports.StreamConfig = async function({name, customSettings={}}) {
1353
- let conf = await this.LoadConf({name});
1330
+ exports.StreamConfig = async function({name, customSettings={}, probeMetadata}) {
1331
+ let objectId = name;
1354
1332
  let status = {name};
1355
1333
 
1356
- let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
1334
+ let libraryId = await this.ContentObjectLibraryId({objectId});
1357
1335
  status.library_id = libraryId;
1358
- status.object_id = conf.objectId;
1336
+ status.object_id = objectId;
1337
+
1338
+ let probe = probeMetadata;
1359
1339
 
1360
1340
  let mainMeta = await this.ContentObjectMetadata({
1361
1341
  libraryId: libraryId,
1362
- objectId: conf.objectId
1342
+ objectId: objectId
1363
1343
  });
1364
1344
 
1365
1345
  let userConfig = mainMeta.live_recording_config;
1366
1346
  status.user_config = userConfig;
1367
- console.log("userConfig", userConfig);
1368
1347
 
1369
1348
  // Get node URI from user config
1370
1349
  const hostName = userConfig.url.replace("udp://", "").replace("rtmp://", "").replace("srt://", "").split(":")[0];
@@ -1378,80 +1357,85 @@ exports.StreamConfig = async function({name, customSettings={}}) {
1378
1357
  }
1379
1358
  const node = nodes[0];
1380
1359
  status.node = node;
1381
-
1382
1360
  let endpoint = node.endpoints[0];
1383
- this.SetNodes({fabricURIs: [endpoint]});
1384
-
1385
- // Probe the stream
1386
- let probe = {};
1387
- const controller = new AbortController();
1388
- const timeoutId = setTimeout(() => {
1389
- controller.abort();
1390
- }, 60 * 1000); // milliseconds
1391
- try {
1392
1361
 
1393
- let probeUrl = await this.Rep({
1394
- libraryId,
1395
- objectId: conf.objectId,
1396
- rep: "probe"
1397
- });
1362
+ if(!probe) {
1363
+ this.SetNodes({fabricURIs: [endpoint]});
1398
1364
 
1399
- probe = await this.utils.ResponseToJson(
1400
- await HttpClient.Fetch(probeUrl, {
1401
- body: JSON.stringify({
1402
- "filename": streamUrl.href,
1403
- "listen": true
1404
- }),
1405
- method: "POST",
1406
- signal: controller.signal
1407
- })
1408
- );
1365
+ // Probe the stream
1366
+ probe = {};
1367
+ const controller = new AbortController();
1368
+ const timeoutId = setTimeout(() => {
1369
+ controller.abort();
1370
+ }, 60 * 1000); // milliseconds
1371
+ try {
1409
1372
 
1410
- if(probe) { clearTimeout(timeoutId); }
1373
+ let probeUrl = await this.Rep({
1374
+ libraryId,
1375
+ objectId,
1376
+ rep: "probe"
1377
+ });
1411
1378
 
1412
- if(probe.errors) {
1413
- throw probe.errors[0];
1414
- }
1415
- } catch(error) {
1416
- if(error.code === "ETIMEDOUT") {
1417
- throw "Stream probe time out - make sure the stream source is available";
1418
- } else {
1419
- throw error;
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
+ }
1420
1401
  }
1421
- }
1422
1402
 
1423
- probe.format.filename = streamUrl.href;
1403
+ probe.format.filename = streamUrl.href;
1404
+ }
1424
1405
 
1425
1406
  // Create live recording config
1426
1407
  let lc = new LiveConf(probe, node.id, endpoint, false, false, true);
1427
1408
 
1428
- const liveRecordingConfigStr = lc.generateLiveConf({
1429
- audioBitrate: customSettings.audioBitrate,
1430
- audioIndex: customSettings.audioIndex,
1431
- partTtl: customSettings.partTtl,
1432
- channelLayout: customSettings.channelLayout
1409
+ const liveRecordingConfig = lc.generateLiveConf({
1410
+ customSettings
1433
1411
  });
1434
- let liveRecordingConfig = JSON.parse(liveRecordingConfigStr);
1435
- console.log("CONFIG", JSON.stringify(liveRecordingConfig.live_recording));
1436
1412
 
1437
1413
  // Store live recording config into the stream object
1438
1414
  let e = await this.EditContentObject({
1439
1415
  libraryId,
1440
- objectId: conf.objectId
1416
+ objectId: objectId
1441
1417
  });
1442
1418
  let writeToken = e.write_token;
1443
1419
 
1444
1420
  await this.ReplaceMetadata({
1445
1421
  libraryId,
1446
- objectId: conf.objectId,
1422
+ objectId,
1447
1423
  writeToken,
1448
1424
  metadataSubtree: "live_recording",
1449
1425
  metadata: liveRecordingConfig.live_recording
1450
1426
  });
1451
1427
 
1428
+ await this.ReplaceMetadata({
1429
+ libraryId,
1430
+ objectId,
1431
+ writeToken,
1432
+ metadataSubtree: "live_recording_config/probe_info",
1433
+ metadata: probe
1434
+ });
1435
+
1452
1436
  status.fin = await this.FinalizeContentObject({
1453
1437
  libraryId,
1454
- objectId: conf.objectId,
1438
+ objectId,
1455
1439
  writeToken,
1456
1440
  commitMessage: "Apply live stream configuration"
1457
1441
  });
@@ -1499,7 +1483,8 @@ exports.StreamListUrls = async function({siteId}={}) {
1499
1483
  objectId: siteId,
1500
1484
  metadataSubtree: "public/asset_metadata/live_streams",
1501
1485
  resolveLinks: true,
1502
- resolveIgnoreErrors: true
1486
+ resolveIgnoreErrors: true,
1487
+ resolveIncludeSource: true
1503
1488
  });
1504
1489
 
1505
1490
  const activeUrlMap = {};
@@ -1510,18 +1495,8 @@ exports.StreamListUrls = async function({siteId}={}) {
1510
1495
  const stream = streamMetadata[slug];
1511
1496
  let versionHash;
1512
1497
 
1513
- if(
1514
- stream &&
1515
- stream.sources &&
1516
- stream.sources.default &&
1517
- stream.sources.default["."] &&
1518
- stream.sources.default["."].container ||
1519
- ((stream["/"] || "").match(/^\/?qfab\/([\w]+)\/?.+/) || [])[1]
1520
- ) {
1521
- versionHash = (
1522
- stream.sources.default["."].container ||
1523
- ((stream["/"] || "").match(/^\/?qfab\/([\w]+)\/?.+/) || [])[1]
1524
- );
1498
+ if(stream && stream["."] && stream["."].source) {
1499
+ versionHash = stream["."].source;
1525
1500
  }
1526
1501
 
1527
1502
  if(versionHash) {
@@ -1646,7 +1621,7 @@ exports.StreamCopyToVod = async function({
1646
1621
  startTime="",
1647
1622
  endTime=""
1648
1623
  }) {
1649
- const conf = await this.LoadConf({name});
1624
+ const objectId = name;
1650
1625
  const abrProfile = require("../abr_profiles/abr_profile_live_to_vod.js");
1651
1626
 
1652
1627
  const status = await this.StreamStatus({name});
@@ -1672,9 +1647,9 @@ exports.StreamCopyToVod = async function({
1672
1647
  }
1673
1648
 
1674
1649
  try {
1675
- status.live_object_id = conf.objectId;
1650
+ status.live_object_id = objectId;
1676
1651
 
1677
- const liveHash = await this.LatestVersionHash({objectId: conf.objectId, libraryId});
1652
+ const liveHash = await this.LatestVersionHash({objectId, libraryId});
1678
1653
  status.live_hash = liveHash;
1679
1654
 
1680
1655
  if(eventId) {