@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/package.json +3 -2
- package/src/AuthorizationClient.js +1 -6
- package/src/FrameClient.js +23 -2
- package/src/abr_profiles/abr_profile_live_drm.js +0 -10
- package/src/client/ContentAccess.js +54 -64
- package/src/client/LiveConf.js +150 -65
- package/src/client/LiveStream.js +2613 -654
- package/src/client/NTP.js +71 -0
- package/src/live_recording_config_profiles/live_recording_config_default.js +54 -0
- package/src/live_recording_config_profiles/live_stream_profile_full.json +143 -0
- package/testScripts/StreamUpdateLinks.js +95 -0
- package/utilities/LiveOutputs.js +149 -0
- package/utilities/StreamCreate.js +53 -0
- package/utilities/lib/helpers.js +5 -1
- package/utilities/tests/mocks/ElvClient.mock.js +9 -1
- package/utilities/tests/unit/StreamCreate.test.js +39 -0
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
|
+
}
|
package/utilities/lib/helpers.js
CHANGED
|
@@ -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
|
+
});
|