@eluvio/elv-client-js 4.2.16 → 4.2.18

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/src/client/NTP.js CHANGED
@@ -253,6 +253,51 @@ exports.DeleteNTPInstance = async function({tenantId, ntpId}) {
253
253
  });
254
254
  };
255
255
 
256
+ /**
257
+ * Generate a report for the specified NTP instance.
258
+ *
259
+ * @methodGroup NTP Instances
260
+ * @namedParams
261
+ * @param {string} tenantId - The ID of the tenant in which this NTP instance was created
262
+ * @param {string} ntpId - The ID of the NTP instance
263
+ * @param {string} password - The code associated with the NTP instance
264
+ * @param {string=} email - Email address associated with the code
265
+ */
266
+ exports.ReportNTPInstance = async function({tenantId, ntpId, password, email}) {
267
+ ValidatePresence("tenantId", tenantId);
268
+ ValidatePresence("ntpId", ntpId);
269
+
270
+ let paramsJSON = [];
271
+
272
+ if (password) {
273
+ paramsJSON.push(`pwd:${password}`);
274
+ }
275
+
276
+ if (email) {
277
+ paramsJSON.push(`eml:${email}`);
278
+ }
279
+
280
+ const res = await this.authClient.MakeKMSCall({
281
+ tenantId,
282
+ methodName: "elv_updateOTPInstance",
283
+ params: [
284
+ tenantId,
285
+ ntpId,
286
+ "report",
287
+ JSON.stringify(paramsJSON),
288
+ Date.now()
289
+ ],
290
+ paramTypes: [
291
+ "string",
292
+ "string",
293
+ "string",
294
+ "string",
295
+ "int"
296
+ ]
297
+ });
298
+ return res || {};
299
+ };
300
+
256
301
  /**
257
302
  * Retrieve info for NTP instances in the specified tenant
258
303
  *
@@ -389,6 +434,32 @@ exports.IssueSignedNTPCode = async function({tenantId, ntpId, email, maxRedempti
389
434
  return result;
390
435
  };
391
436
 
437
+ /**
438
+ * Check the status of the specified ticket/code without redeeming it.
439
+ *
440
+ * @methodGroup Tickets
441
+ * @namedParams
442
+ * @param {string} tenantId - The ID of the tenant from which the ticket was issued
443
+ * @param {string} ntpId - The ID of the NTP instance from which the ticket was issued
444
+ * @param {string} code - Access code
445
+ * @param {string=} email - Email address associated with the code
446
+ *
447
+ * @return {Promise<Object>} - Status information for the ticket
448
+ */
449
+ exports.NTPStatus = async function({tenantId, ntpId, code, email}) {
450
+ ValidatePresence("tenantId", tenantId);
451
+ ValidatePresence("ntpId", ntpId);
452
+ ValidatePresence("code", code);
453
+
454
+ const response = await this.HttpClient.Request({
455
+ method: "POST",
456
+ path: UrlJoin("ks", "otp", "ntp", tenantId, ntpId, "status"),
457
+ body: {"_PASSWORD": code, "_EMAIL": email}
458
+ });
459
+
460
+ return await response.json();
461
+ };
462
+
392
463
  /**
393
464
  * Redeem the specified ticket/code to authorize the client. Must provide either issuer or tenantId and ntpId
394
465
  *
@@ -0,0 +1,54 @@
1
+ const LiveRecordingConfigDefault = {
2
+ drm_type: "clear",
3
+ recording_config: {
4
+ part_ttl: 86400,
5
+ reconnect_timeout: 3600,
6
+ connection_timeout: 3600,
7
+ copy_mpegts: false,
8
+ },
9
+ profile: {
10
+ ladder_specs: {
11
+ audio: [
12
+ {
13
+ bit_rate: 192000,
14
+ channels: 2,
15
+ codecs: "mp4a.40.2"
16
+ },
17
+ {
18
+ bit_rate: 384000,
19
+ channels: 6,
20
+ codecs: "mp4a.40.2"
21
+ }
22
+ ],
23
+ video: [
24
+ {
25
+ bit_rate: 9500000,
26
+ codecs: "avc1.640028,mp4a.40.2",
27
+ height: 1080,
28
+ width: 1920
29
+ },
30
+ {
31
+ bit_rate: 4500000,
32
+ codecs: "avc1.640028,mp4a.40.2",
33
+ height: 720,
34
+ width: 1280
35
+ },
36
+ {
37
+ bit_rate: 2000000,
38
+ codecs: "avc1.640028,mp4a.40.2",
39
+ height: 540,
40
+ width: 960
41
+ },
42
+ {
43
+ bit_rate: 900000,
44
+ codecs: "avc1.640028,mp4a.40.2",
45
+ height: 540,
46
+ width: 960
47
+ }
48
+ ]
49
+ },
50
+ name: "Default"
51
+ }
52
+ }
53
+
54
+ module.exports = LiveRecordingConfigDefault;
@@ -0,0 +1,143 @@
1
+ {
2
+ "name": "Reference Full Profile",
3
+ "drm_type": "clear",
4
+
5
+ "recording_config": {
6
+ "part_ttl": 86400,
7
+ "reconnect_timeout": 3600,
8
+ "connection_timeout": 3600,
9
+ "copy_mpegts": false,
10
+ "input_cfg": {
11
+ "bypass_libav_reader": false,
12
+ "copy_mode": "",
13
+ "copy_packaging": "",
14
+ "custom_read_loop_enabled": false,
15
+ "input_packaging": ""
16
+ }
17
+ },
18
+
19
+ "playout_config": {
20
+ "dvr_enabled": true,
21
+ "dvr_max_duration": 14400,
22
+ "rebroadcast_start_time_sec_epoch": 0,
23
+ "playout_sharding_level": 2,
24
+ "vod_enabled": false,
25
+ "playout_formats": [
26
+ "hls-clear"
27
+ ],
28
+ "ladder_specs": {
29
+ "audio": [
30
+ {
31
+ "bit_rate": 192000,
32
+ "channels": 2,
33
+ "codecs": "mp4a.40.2"
34
+ },
35
+ {
36
+ "bit_rate": 384000,
37
+ "channels": 6,
38
+ "codecs": "mp4a.40.2"
39
+ }
40
+ ],
41
+ "video": [
42
+ {
43
+ "bit_rate": 14000000,
44
+ "codecs": "avc1.640028,mp4a.40.2",
45
+ "height": 2160,
46
+ "width": 3840
47
+ },
48
+ {
49
+ "bit_rate": 9500000,
50
+ "codecs": "avc1.640028,mp4a.40.2",
51
+ "height": 1080,
52
+ "width": 1920
53
+ },
54
+ {
55
+ "bit_rate": 4500000,
56
+ "codecs": "avc1.640028,mp4a.40.2",
57
+ "height": 720,
58
+ "width": 1280
59
+ },
60
+ {
61
+ "bit_rate": 2000000,
62
+ "codecs": "avc1.640028,mp4a.40.2",
63
+ "height": 540,
64
+ "width": 960
65
+ },
66
+ {
67
+ "bit_rate": 900000,
68
+ "codecs": "avc1.640028,mp4a.40.2",
69
+ "height": 540,
70
+ "width": 960
71
+ }
72
+ ]
73
+ },
74
+ "simple_watermark": {
75
+ "font_color": "white@0.5",
76
+ "font_relative_height": 0.05,
77
+ "shadow": true,
78
+ "shadow_color": "black@0.5",
79
+ "template": "",
80
+ "x": "(w-tw)/2",
81
+ "y": "h-(4*lh)"
82
+ },
83
+ "image_watermark": {
84
+ "align_h": "right",
85
+ "align_v": "bottom",
86
+ "image": "",
87
+ "margin_h": "1/20",
88
+ "margin_v": "1/10",
89
+ "target_video_height": 1080,
90
+ "wm_enabled": false
91
+ }
92
+ },
93
+
94
+ "recording_stream_config": {
95
+ "audio": {
96
+ "0": {
97
+ "bitrate": 192000,
98
+ "codec": "aac",
99
+ "playout": true,
100
+ "playout_label": "Audio 1",
101
+ "record": true,
102
+ "recording_bitrate": 192000,
103
+ "recording_channels": 2,
104
+ "lang": ""
105
+ }
106
+ }
107
+ },
108
+
109
+ "recording_params": {
110
+ "description": "",
111
+ "listen": true,
112
+ "live_delay_nano": 6000000000,
113
+ "max_duration_sec": -1,
114
+ "playout_type": "live",
115
+ "source_timescale": null,
116
+ "xc_params": {
117
+ "audio_bitrate": 384000,
118
+ "audio_index": [0, 0, 0, 0, 0, 0, 0, 0],
119
+ "audio_seg_duration_ts": null,
120
+ "connection_timeout": 600,
121
+ "ecodec2": "aac",
122
+ "enc_height": null,
123
+ "enc_width": null,
124
+ "filter_descriptor": "",
125
+ "force_keyint": null,
126
+ "format": "fmp4-segment",
127
+ "listen": true,
128
+ "n_audio": 1,
129
+ "preset": "faster",
130
+ "sample_rate": 48000,
131
+ "seg_duration": null,
132
+ "skip_decoding": false,
133
+ "start_segment_str": "1",
134
+ "stream_id": -1,
135
+ "sync_audio_to_stream_id": -1,
136
+ "video_bitrate": null,
137
+ "video_seg_duration_ts": null,
138
+ "video_time_base": null,
139
+ "video_frame_duration_ts": null,
140
+ "xc_type": 3
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,95 @@
1
+ const { ElvClient } = require("../src/index");
2
+
3
+ const Test = async () => {
4
+ try {
5
+ const client = await ElvClient.FromNetworkName({
6
+ networkName: "demo"
7
+ });
8
+
9
+ const wallet = client.GenerateWallet();
10
+ const signer = wallet.AddAccount({
11
+ privateKey: process.env.PRIVATE_KEY
12
+ });
13
+
14
+ client.SetSigner({signer});
15
+
16
+ CreateLink = ({
17
+ targetHash,
18
+ linkTarget="meta/public/asset_metadata",
19
+ options={},
20
+ autoUpdate=true
21
+ }) => {
22
+ return {
23
+ ...options,
24
+ ".": {
25
+ ...(options["."] || {}),
26
+ ...autoUpdate ? {"auto_update": {"tag": "latest"}} : undefined
27
+ },
28
+ "/": `/qfab/${targetHash}/${linkTarget}`
29
+ };
30
+ };
31
+
32
+ UpdateStreamLink = async ({siteLibraryId, siteId, objectId, slug}) => {
33
+ try {
34
+ const originalLink = await client.ContentObjectMetadata({
35
+ libraryId: siteLibraryId,
36
+ objectId: siteId,
37
+ metadataSubtree: `public/asset_metadata/live_streams/${slug}`,
38
+ });
39
+
40
+ const link = CreateLink({
41
+ targetHash: await client.LatestVersionHash({objectId}),
42
+ options: {order: originalLink.order}
43
+ });
44
+
45
+ await client.ReplaceMetadata({
46
+ libraryId: siteLibraryId,
47
+ objectId: siteId,
48
+ writeToken,
49
+ metadataSubtree: `public/asset_metadata/live_streams/${slug}`,
50
+ metadata: link
51
+ });
52
+ } catch(error) {
53
+ // eslint-disable-next-line no-console
54
+ console.error("Unable to update stream link", error);
55
+ }
56
+ }
57
+
58
+ const {streamMetadata, siteObjectId, siteLibraryId} = await client.StreamGetSiteData({streamOptions: {resolveIncludeSource: false, resolveLinks: false}});
59
+
60
+ const {writeToken} = await client.EditContentObject({
61
+ libraryId: siteLibraryId,
62
+ objectId: siteObjectId
63
+ });
64
+
65
+ for(let streamSlug in streamMetadata) {
66
+ const obj = streamMetadata[streamSlug];
67
+
68
+ const versionHash = obj["/"] ? obj["/"].split("/")[2] : obj["."].source;
69
+ const objId = client.utils.DecodeVersionHash(versionHash).objectId;
70
+
71
+ await UpdateStreamLink({
72
+ siteLibraryId,
73
+ objectId: objId,
74
+ siteId: siteObjectId,
75
+ slug: streamSlug
76
+ });
77
+ }
78
+
79
+ await client.FinalizeContentObject({
80
+ libraryId: siteLibraryId,
81
+ objectId: siteObjectId,
82
+ writeToken,
83
+ commitMessage: "Update stream link",
84
+ awaitCommitConfirmation: true
85
+ });
86
+
87
+ } catch(error) {
88
+ console.error(error);
89
+ console.error(JSON.stringify(error, null, 2));
90
+ }
91
+
92
+ process.exit(0);
93
+ };
94
+
95
+ Test();
@@ -0,0 +1,149 @@
1
+ /**
2
+ * LiveOutputs.js - Manage live outputs
3
+ *
4
+ * Usage:
5
+ * PRIVATE_KEY=<key> node utilities/LiveOutputs.js <command> [options]
6
+ *
7
+ * Commands:
8
+ * list List all outputs with config and state
9
+ * status <output_id> Show config and live state for an output
10
+ * create --stream <id> [--node <id> | --geo <geo>] Create a new output
11
+ * [--passphrase <pass>] [--name <name>]
12
+ * modify <output_id> [--stream <id>] [--enable true|false] Modify an existing output
13
+ * [--passphrase <pass>] [--name <name>]
14
+ * delete <output_id> Delete an output
15
+ *
16
+ * Examples:
17
+ * node utilities/LiveOutputs.js list
18
+ * node utilities/LiveOutputs.js status out005
19
+ * node utilities/LiveOutputs.js create --stream iq__abc123 --node inod123 --name "My Output"
20
+ * node utilities/LiveOutputs.js create --stream iq__abc123 --geo na-west-north
21
+ * node utilities/LiveOutputs.js modify out005 --enable false
22
+ * node utilities/LiveOutputs.js modify out005 --stream iq_def456 --passphrase "new-secret" --name "Renamed"
23
+ * node utilities/LiveOutputs.js delete out005
24
+ */
25
+ const { ElvClient } = require("../src/ElvClient");
26
+
27
+ const OBJECT_ID = "iq__eiDtwuBbAfyJCQqFKS5drdeDToL"; // demov3
28
+
29
+ const Init = async () => {
30
+ const client = await ElvClient.FromNetworkName({networkName: "demov3"});
31
+ const wallet = client.GenerateWallet();
32
+ const signer = wallet.AddAccount({privateKey: process.env.PRIVATE_KEY});
33
+ client.SetSigner({signer});
34
+ client.ToggleLogging(false);
35
+ return client;
36
+ };
37
+
38
+ const List = async () => {
39
+ const client = await Init();
40
+ const outputs = await client.OutputsList({objectId: OBJECT_ID, includeState: true});
41
+ console.log(JSON.stringify(outputs, null, 2));
42
+ };
43
+
44
+ const Status = async (outputId) => {
45
+ const client = await Init();
46
+ const state = await client.OutputsState({objectId: OBJECT_ID, outputId, includeState: true});
47
+ console.log(JSON.stringify(state, null, 2));
48
+ };
49
+
50
+ const Create = async ({streamId, nodeId, geo, passphrase, name}) => {
51
+ const client = await Init();
52
+ const result = await client.OutputsCreate({
53
+ objectId: OBJECT_ID,
54
+ streamObjectId: streamId,
55
+ enabled: true,
56
+ name,
57
+ nodeIds: nodeId ? [nodeId] : undefined,
58
+ geos: geo ? [geo] : [],
59
+ passphrase,
60
+ stripRtp: true
61
+ });
62
+ console.log(JSON.stringify(result, null, 2));
63
+ };
64
+
65
+ const Modify = async (outputId, {streamId, enable, passphrase, name}) => {
66
+ const client = await Init();
67
+
68
+ // Read current output to use as base
69
+ let output = await client.OutputsState({objectId: OBJECT_ID, outputId, includeState: false})
70
+
71
+ if(streamId !== undefined) {
72
+ output.input = {stream: streamId};
73
+ }
74
+ if(enable !== undefined) {
75
+ output.enabled = enable;
76
+ }
77
+ if(passphrase !== undefined) {
78
+ output.srt_pull = output.srt_pull || {};
79
+ output.srt_pull.passphrase = passphrase;
80
+ }
81
+ if(name !== undefined) {
82
+ output.name = name;
83
+ }
84
+
85
+ const result = await client.OutputsModify({objectId: OBJECT_ID, outputId, output});
86
+ console.log(JSON.stringify(result, null, 2));
87
+ };
88
+
89
+ const Delete = async (outputId) => {
90
+ const client = await Init();
91
+ const result = await client.OutputsDelete({objectId: OBJECT_ID, outputId});
92
+ console.log(JSON.stringify(result, null, 2));
93
+ };
94
+
95
+ const Run = async (fn) => {
96
+ try {
97
+ await fn();
98
+ } catch(error) {
99
+ if(error.status) {
100
+ console.error(`${error.status} ${error.statusText} ${error.url || ""}`);
101
+ if(error.body) {
102
+ console.error(JSON.stringify(error.body, null, 2));
103
+ }
104
+ } else {
105
+ console.error(error.message || error);
106
+ }
107
+ process.exit(1);
108
+ }
109
+ };
110
+
111
+ const getArg = (args, flag) => args.includes(flag) ? args[args.indexOf(flag) + 1] : undefined;
112
+
113
+ const [cmd, ...args] = process.argv.slice(2);
114
+
115
+ switch(cmd) {
116
+ case "list":
117
+ Run(List);
118
+ break;
119
+ case "status":
120
+ Run(() => Status(args[0]));
121
+ break;
122
+ case "create":
123
+ Run(() => Create({
124
+ streamId: getArg(args, "--stream"),
125
+ nodeId: getArg(args, "--node"),
126
+ geo: getArg(args, "--geo"),
127
+ passphrase: getArg(args, "--passphrase"),
128
+ name: getArg(args, "--name")
129
+ }));
130
+ break;
131
+ case "modify":
132
+ Run(() => Modify(args[0], {
133
+ streamId: getArg(args, "--stream"),
134
+ enable: args.includes("--enable") ? getArg(args, "--enable") === "true" : undefined,
135
+ passphrase: getArg(args, "--passphrase"),
136
+ name: getArg(args, "--name")
137
+ }));
138
+ break;
139
+ case "delete":
140
+ Run(() => Delete(args[0]));
141
+ break;
142
+ default:
143
+ console.log("Usage: PRIVATE_KEY=<key> node utilities/LiveOutputs.js <command>\n");
144
+ console.log(" list");
145
+ console.log(" status <output_id>");
146
+ console.log(" create --stream <stream_object_id> [--node <node_id> | --geo <geo>] [--passphrase <pass>] [--name <name>]");
147
+ console.log(" modify <output_id> [--stream <stream_object_id>] [--enable true|false] [--passphrase <pass>] [--name <name>]");
148
+ console.log(" delete <output_id>");
149
+ }
@@ -0,0 +1,53 @@
1
+ const Client = require("./lib/concerns/Client");
2
+ const {NewOpt, StdOpt} = require("./lib/options");
3
+ const Library = require("./lib/concerns/Library");
4
+ const Utility = require("./lib/Utility");
5
+
6
+ class StreamCreate extends Utility {
7
+ blueprint() {
8
+ return {
9
+ concerns: [Client],
10
+ options: [
11
+ StdOpt(
12
+ "libraryId",
13
+ {
14
+ demand: true,
15
+ forX: "new stream"
16
+ }
17
+ ),
18
+ NewOpt(
19
+ "url",
20
+ {
21
+ demand: true,
22
+ descTemplate: "URL{X}",
23
+ type: "string"
24
+ }
25
+ )
26
+ ]
27
+ };
28
+ }
29
+
30
+ async body() {
31
+ const logger = this.logger;
32
+ const {libraryId, url} = this.args;
33
+
34
+ const client = await this.concerns.Client.get();
35
+ const response = await client.StreamCreate({
36
+ libraryId,
37
+ url
38
+ });
39
+
40
+ logger.log(`New object ID: ${response.id}`);
41
+ logger.data("object_id", response.id);
42
+ }
43
+
44
+ header() {
45
+ return `Create live stream '${this.args.libraryId}':`;
46
+ }
47
+ }
48
+
49
+ if(require.main === module) {
50
+ Utility.cmdLineInvoke(StreamCreate);
51
+ } else {
52
+ module.exports = StreamCreate;
53
+ }
@@ -77,6 +77,9 @@ const suppressNullLike = x => kindOf(x) === "null" || kindOf(x) === "undefined"
77
77
 
78
78
  const trimSlashes = R.compose(removeLeadingSlash, removeTrailingSlash);
79
79
 
80
+ const slugify = str =>
81
+ (str || "").toLowerCase().trim().replace(/ /g, "-").replace(/[^a-z0-9-]/g, "");
82
+
80
83
  // --------------------------------------------
81
84
  // time formatting
82
85
  // --------------------------------------------
@@ -225,6 +228,7 @@ module.exports = {
225
228
  removeTrailingSlash,
226
229
  seconds,
227
230
  singleEntryMap,
231
+ slugify,
228
232
  spaceAfter,
229
233
  stringOrFileContents,
230
234
  subst,
@@ -235,4 +239,4 @@ module.exports = {
235
239
  unit,
236
240
  valOrThrow,
237
241
  widthForRatioAndHeight
238
- };
242
+ };
@@ -307,6 +307,13 @@ const StartABRMezzanineJobs = async (args) => {
307
307
  };
308
308
  };
309
309
 
310
+ const StreamCreate = async (args) => {
311
+ calls.push("StreamCreate: " + JSON.stringify(args));
312
+ return {
313
+ id: "iq__dummy_new_id"
314
+ };
315
+ };
316
+
310
317
  const UploadFiles = async (args) => {
311
318
  calls.push("UploadFiles: " + JSON.stringify(args));
312
319
  };
@@ -332,6 +339,7 @@ const MockClient = {
332
339
  ReplaceMetadata,
333
340
  SetVisibility,
334
341
  StartABRMezzanineJobs,
342
+ StreamCreate,
335
343
  UploadFiles
336
344
  };
337
345
 
@@ -503,4 +511,4 @@ const writeTokens = {};
503
511
 
504
512
 
505
513
 
506
- module.exports = {removeStubs, stubClient};
514
+ module.exports = {removeStubs, stubClient};
@@ -0,0 +1,39 @@
1
+ const chai = require("chai");
2
+ const chaiAsPromised = require("chai-as-promised");
3
+ const expect = chai.expect;
4
+ chai.use(chaiAsPromised);
5
+
6
+ const {removeStubs, stubClient} = require("../mocks/ElvClient.mock");
7
+ const {argList2Params, removeElvEnvVars} = require("../helpers/params");
8
+
9
+ removeElvEnvVars();
10
+ beforeEach(removeStubs);
11
+
12
+ const StreamCreate = require("../../StreamCreate");
13
+
14
+ describe("StreamCreate", () => {
15
+ it("should complain if --libraryId not supplied", () => {
16
+ expect(() => {
17
+ new StreamCreate(argList2Params("--url", "http://example.com"));
18
+ }).to.throw("Missing required argument: libraryId");
19
+ });
20
+
21
+ it("should complain if --url not supplied", () => {
22
+ expect(() => {
23
+ new StreamCreate(argList2Params("--libraryId", "ilib123"));
24
+ }).to.throw("Missing required argument: url");
25
+ });
26
+
27
+ it("should call StreamCreate() and include object_id in return value", () => {
28
+ const utility = new StreamCreate(argList2Params("--libraryId", "ilib123", "--url", "http://example.com", "--json"));
29
+ const stub = stubClient(utility.concerns.Client);
30
+ stub.resetHistory();
31
+ return utility.run().then( (retVal) => {
32
+ expect(retVal.object_id).to.equal("iq__dummy_new_id");
33
+
34
+ expect(stub.callHistoryMismatches([
35
+ "StreamCreate"
36
+ ]).length).to.equal(0);
37
+ });
38
+ });
39
+ });