@eluvio/elv-client-js 4.0.76 → 4.0.77
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 +18 -10
- package/dist/ElvClient-node-min.js +18 -10
- package/dist/ElvFrameClient-min.js +9 -9
- package/dist/ElvPermissionsClient-min.js +10 -10
- package/dist/ElvWalletClient-min.js +18 -10
- package/dist/ElvWalletClient-node-min.js +18 -10
- package/dist/src/AuthorizationClient.js +18 -12
- package/dist/src/Crypto.js +2 -2
- package/dist/src/ElvClient.js +111 -76
- package/dist/src/EthClient.js +2 -2
- package/dist/src/FrameClient.js +4 -4
- package/dist/src/PermissionsClient.js +2 -2
- package/dist/src/Utils.js +6 -5
- package/dist/src/abr_profiles/abr_profile_live_drm.js +1621 -0
- package/dist/src/abr_profiles/abr_profile_live_to_vod.js +1599 -0
- package/dist/src/client/ABRPublishing.js +2 -2
- package/dist/src/client/AccessGroups.js +19 -20
- package/dist/src/client/ContentAccess.js +2 -2
- package/dist/src/client/ContentManagement.js +3 -3
- package/dist/src/client/Contracts.js +235 -203
- package/dist/src/client/Files.js +2 -2
- package/dist/src/client/LiveConf.js +1 -1
- package/dist/src/client/LiveStream.js +1157 -1153
- package/dist/src/client/NFT.js +2 -2
- package/dist/src/contracts/v3b/BaseAccessControlGroup.js +1704 -0
- package/dist/src/walletClient/ClientMethods.js +423 -280
- package/dist/src/walletClient/Profile.js +2 -2
- package/dist/src/walletClient/Utils.js +7 -3
- package/dist/src/walletClient/index.js +124 -44
- package/package.json +2 -1
- package/src/FrameClient.js +3 -0
- package/src/abr_profiles/abr_profile_live_drm.js +1907 -0
- package/src/abr_profiles/abr_profile_live_to_vod.js +1885 -0
- package/src/client/LiveConf.js +1 -1
- package/src/client/LiveStream.js +809 -936
package/src/client/LiveStream.js
CHANGED
|
@@ -6,16 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
const {LiveConf} = require("./LiveConf");
|
|
8
8
|
const path = require("path");
|
|
9
|
-
|
|
10
9
|
const fs = require("fs");
|
|
11
|
-
|
|
12
10
|
const HttpClient = require("../HttpClient");
|
|
13
|
-
|
|
14
|
-
// const {
|
|
15
|
-
// ValidateLibrary,
|
|
16
|
-
// ValidateVersion,
|
|
17
|
-
// ValidateParameters
|
|
18
|
-
// } = require("../Validation");
|
|
11
|
+
const Fraction = require("fraction.js");
|
|
19
12
|
|
|
20
13
|
const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
|
|
21
14
|
const tok = await client.authClient.AuthorizationToken({libraryId, objectId,
|
|
@@ -25,18 +18,298 @@ const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
|
|
|
25
18
|
return tok;
|
|
26
19
|
};
|
|
27
20
|
|
|
28
|
-
|
|
21
|
+
const Sleep = (ms) => {
|
|
29
22
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
30
|
-
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Set the offering for the live stream
|
|
27
|
+
*
|
|
28
|
+
* @methodGroup Live Stream
|
|
29
|
+
* @namedParams
|
|
30
|
+
* @param {Object} client - The client object
|
|
31
|
+
* @param {string} libraryId - ID of the library for the new live stream object
|
|
32
|
+
* @param {string} objectId - ID of the new live stream object
|
|
33
|
+
* @param {string=} typeAbrMaster - Content type hash
|
|
34
|
+
* @param {string=} typeLiveStream - Content type hash
|
|
35
|
+
* @param {string} streamUrl - Live source URL
|
|
36
|
+
* @param {object} abrProfile - ABR Profile for the offering
|
|
37
|
+
* @param {number} aBitRate - Audio bitrate
|
|
38
|
+
* @param {number} aChannels - Audio channels
|
|
39
|
+
* @param {number} aSampleRate - Audio sample rate
|
|
40
|
+
* @param {number} aStreamIndex - Audio stream index
|
|
41
|
+
* @param {string} aTimeBase - Audio time base as a fraction, e.g. "1/48000" (usually equal to 1/aSampleRate)
|
|
42
|
+
* @param {string} aChannelLayout - Channel layout, e.g. "stereo"
|
|
43
|
+
* @param {number} vBitRate - Video bitrate
|
|
44
|
+
* @param {number} vHeight - Video height
|
|
45
|
+
* @param {number} vStreamIndex - Video stream index
|
|
46
|
+
* @param {number} vWidth - Video width
|
|
47
|
+
* @param {string} vDisplayAspectRatio - Display aspect ratio as a fraction, e.g. "16/9"
|
|
48
|
+
* @param {string} vFrameRate - Frame rate as an integer, e.g. "30"
|
|
49
|
+
* @param {string} vTimeBase - Time base as a fraction, e.g. "1/30000"
|
|
50
|
+
*
|
|
51
|
+
* @return {Promise<string>} - Final hash of the live stream object
|
|
52
|
+
*/
|
|
53
|
+
const StreamGenerateOffering = async({
|
|
54
|
+
client,
|
|
55
|
+
libraryId,
|
|
56
|
+
objectId,
|
|
57
|
+
typeAbrMaster,
|
|
58
|
+
typeLiveStream,
|
|
59
|
+
streamUrl,
|
|
60
|
+
abrProfile,
|
|
61
|
+
aBitRate,
|
|
62
|
+
aChannels,
|
|
63
|
+
aSampleRate,
|
|
64
|
+
aStreamIndex,
|
|
65
|
+
aTimeBase,
|
|
66
|
+
aChannelLayout,
|
|
67
|
+
vBitRate,
|
|
68
|
+
vHeight,
|
|
69
|
+
vStreamIndex,
|
|
70
|
+
vWidth,
|
|
71
|
+
vDisplayAspectRatio,
|
|
72
|
+
vFrameRate,
|
|
73
|
+
vTimeBase
|
|
74
|
+
}) => {
|
|
75
|
+
// compute duration_ts
|
|
76
|
+
const DUMMY_DURATION = 1001; // should result in integer duration_ts values for both audio and video
|
|
77
|
+
const aDurationTs = Fraction(aTimeBase).inverse().mul(DUMMY_DURATION).valueOf();
|
|
78
|
+
const vDurationTs = Fraction(vTimeBase).inverse().mul(DUMMY_DURATION).valueOf();
|
|
79
|
+
|
|
80
|
+
// construct /production_master/sources/STREAM_URL/streams
|
|
81
|
+
|
|
82
|
+
const sourceAudioStream = {
|
|
83
|
+
"bit_rate": aBitRate,
|
|
84
|
+
"channel_layout": aChannelLayout,
|
|
85
|
+
"channels": aChannels,
|
|
86
|
+
"codec_name": "aac",
|
|
87
|
+
"duration": DUMMY_DURATION,
|
|
88
|
+
"duration_ts": aDurationTs,
|
|
89
|
+
"frame_count": 0,
|
|
90
|
+
"language": "",
|
|
91
|
+
"max_bit_rate": aBitRate,
|
|
92
|
+
"sample_rate": aSampleRate,
|
|
93
|
+
"start_pts": 0,
|
|
94
|
+
"start_time": 0,
|
|
95
|
+
"time_base": aTimeBase,
|
|
96
|
+
"type": "StreamAudio"
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const sourceVideoStream = {
|
|
100
|
+
"bit_rate": vBitRate,
|
|
101
|
+
"codec_name": "h264",
|
|
102
|
+
"display_aspect_ratio": vDisplayAspectRatio,
|
|
103
|
+
"duration": DUMMY_DURATION,
|
|
104
|
+
"duration_ts": vDurationTs,
|
|
105
|
+
"field_order": "progressive",
|
|
106
|
+
"frame_count": 0,
|
|
107
|
+
"frame_rate": vFrameRate,
|
|
108
|
+
"hdr": null,
|
|
109
|
+
"height": vHeight,
|
|
110
|
+
"language": "",
|
|
111
|
+
"max_bit_rate": vBitRate,
|
|
112
|
+
"sample_aspect_ratio": "1",
|
|
113
|
+
"start_pts": 0,
|
|
114
|
+
"start_time": 0,
|
|
115
|
+
"time_base": vTimeBase,
|
|
116
|
+
"type": "StreamVideo",
|
|
117
|
+
"width": vWidth
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// placeholder stream to use if [aStreamIndex, vStreamIndex].sort() is not [0,1]
|
|
121
|
+
const DUMMY_STREAM = {
|
|
122
|
+
"bit_rate": 0,
|
|
123
|
+
"codec_name": "",
|
|
124
|
+
"duration": DUMMY_DURATION,
|
|
125
|
+
"duration_ts": 2500 * DUMMY_DURATION,
|
|
126
|
+
"frame_count": 1,
|
|
127
|
+
"language": "",
|
|
128
|
+
"max_bit_rate": 0,
|
|
129
|
+
"start_pts": 0,
|
|
130
|
+
"start_time": 0,
|
|
131
|
+
"time_base": "1/2500",
|
|
132
|
+
"type": "StreamData"
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const sourceStreams = [];
|
|
136
|
+
const maxStreamIndex = Math.max(aStreamIndex, vStreamIndex);
|
|
137
|
+
|
|
138
|
+
for(let i = 0; i <= maxStreamIndex; i++) {
|
|
139
|
+
if (i === aStreamIndex) {
|
|
140
|
+
sourceStreams.push(sourceAudioStream);
|
|
141
|
+
} else if (i === vStreamIndex) {
|
|
142
|
+
sourceStreams.push(sourceVideoStream);
|
|
143
|
+
} else {
|
|
144
|
+
sourceStreams.push(DUMMY_STREAM);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// construct /production_master/sources
|
|
149
|
+
const sources = {
|
|
150
|
+
[streamUrl]: {
|
|
151
|
+
"container_format": {
|
|
152
|
+
"duration": DUMMY_DURATION,
|
|
153
|
+
"filename": streamUrl,
|
|
154
|
+
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
|
|
155
|
+
"start_time": 0
|
|
156
|
+
},
|
|
157
|
+
"streams": sourceStreams
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// construct /production_master/variants
|
|
162
|
+
const variants = {
|
|
163
|
+
"default": {
|
|
164
|
+
"streams": {
|
|
165
|
+
"audio": {
|
|
166
|
+
"default_for_media_type": false,
|
|
167
|
+
"label": "",
|
|
168
|
+
"language": "",
|
|
169
|
+
"mapping_info": "",
|
|
170
|
+
"sources": [
|
|
171
|
+
{
|
|
172
|
+
"files_api_path": streamUrl,
|
|
173
|
+
"stream_index": aStreamIndex
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"video": {
|
|
178
|
+
"default_for_media_type": false,
|
|
179
|
+
"label": "",
|
|
180
|
+
"language": "",
|
|
181
|
+
"mapping_info": "",
|
|
182
|
+
"sources": [
|
|
183
|
+
{
|
|
184
|
+
"files_api_path": streamUrl,
|
|
185
|
+
"stream_index": vStreamIndex
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// construct /production_master
|
|
194
|
+
const production_master = {sources, variants};
|
|
195
|
+
|
|
196
|
+
// get existing metadata
|
|
197
|
+
console.log("Retrieving current metadata...");
|
|
198
|
+
let metadata = await client.ContentObjectMetadata({
|
|
199
|
+
libraryId,
|
|
200
|
+
objectId
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// add /production_master to metadata
|
|
204
|
+
metadata.production_master = production_master;
|
|
205
|
+
|
|
206
|
+
// write back to object
|
|
207
|
+
console.log("Getting write token...");
|
|
208
|
+
let editResponse = await client.EditContentObject({
|
|
209
|
+
libraryId,
|
|
210
|
+
objectId,
|
|
211
|
+
options: {
|
|
212
|
+
type: typeAbrMaster
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
let writeToken = editResponse.write_token;
|
|
216
|
+
console.log(`New write token: ${writeToken}`);
|
|
217
|
+
|
|
218
|
+
console.log("Writing back metadata with /production_master added...");
|
|
219
|
+
await client.ReplaceMetadata({
|
|
220
|
+
libraryId,
|
|
221
|
+
metadata,
|
|
222
|
+
objectId,
|
|
223
|
+
writeToken
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
console.log("Finalizing...");
|
|
227
|
+
let finalizeResponse = await client.FinalizeContentObject({
|
|
228
|
+
libraryId,
|
|
229
|
+
objectId,
|
|
230
|
+
writeToken
|
|
231
|
+
});
|
|
232
|
+
let masterVersionHash = finalizeResponse.hash;
|
|
233
|
+
console.log(`Finalized, new version hash: ${masterVersionHash}`);
|
|
234
|
+
|
|
235
|
+
// Generate offering
|
|
236
|
+
const createResponse = await client.CreateABRMezzanine({
|
|
237
|
+
libraryId,
|
|
238
|
+
objectId,
|
|
239
|
+
masterVersionHash,
|
|
240
|
+
variant: "default",
|
|
241
|
+
offeringKey: "default",
|
|
242
|
+
abrProfile
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (createResponse.warnings.length > 0) {
|
|
246
|
+
console.log("WARNINGS:");
|
|
247
|
+
console.log(JSON.stringify(createResponse.warnings, null, 2));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (createResponse.errors.length > 0) {
|
|
251
|
+
console.log("ERRORS:");
|
|
252
|
+
console.log(JSON.stringify(createResponse.errors, null, 2));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let versionHash = createResponse.hash;
|
|
256
|
+
console.log(`New version hash: ${versionHash}`);
|
|
257
|
+
|
|
258
|
+
// get new metadata
|
|
259
|
+
console.log("Retrieving revised metadata with offering...");
|
|
260
|
+
metadata = await client.ContentObjectMetadata({
|
|
261
|
+
libraryId,
|
|
262
|
+
versionHash
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
console.log("Moving /abr_mezzanine/offerings to /offerings and removing /abr_mezzanine...");
|
|
266
|
+
metadata.offerings = metadata.abr_mezzanine.offerings;
|
|
267
|
+
delete metadata.abr_mezzanine;
|
|
268
|
+
|
|
269
|
+
// add items to media_struct needed to use options.json handler
|
|
270
|
+
metadata.offerings.default.media_struct.duration_rat = `${DUMMY_DURATION}`;
|
|
271
|
+
|
|
272
|
+
// write back to object
|
|
273
|
+
console.log("Getting write token...");
|
|
274
|
+
editResponse = await client.EditContentObject({
|
|
275
|
+
libraryId,
|
|
276
|
+
objectId,
|
|
277
|
+
options: {
|
|
278
|
+
type: typeLiveStream
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
writeToken = editResponse.write_token;
|
|
282
|
+
console.log(`New write token: ${writeToken}`);
|
|
283
|
+
|
|
284
|
+
console.log("Writing back metadata with /offerings...");
|
|
285
|
+
await client.ReplaceMetadata({
|
|
286
|
+
libraryId,
|
|
287
|
+
metadata,
|
|
288
|
+
objectId,
|
|
289
|
+
writeToken
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
console.log("Finalizing...");
|
|
293
|
+
finalizeResponse = await client.FinalizeContentObject({
|
|
294
|
+
libraryId,
|
|
295
|
+
objectId,
|
|
296
|
+
writeToken
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const finalHash = finalizeResponse.hash;
|
|
300
|
+
console.log(`Finalized, new version hash: ${finalHash}`);
|
|
301
|
+
|
|
302
|
+
return finalHash;
|
|
303
|
+
};
|
|
31
304
|
|
|
32
305
|
/**
|
|
33
306
|
* Retrieve the status of the current live stream session
|
|
34
307
|
*
|
|
35
308
|
* @methodGroup Live Stream
|
|
36
309
|
* @namedParams
|
|
37
|
-
* @param {string} name -
|
|
38
|
-
* @param {boolean} stopLro -
|
|
39
|
-
* @param {boolean} showParams -
|
|
310
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
311
|
+
* @param {boolean=} stopLro - If specified, will stop LRO
|
|
312
|
+
* @param {boolean=} showParams - If specified, will return recording_params with status
|
|
40
313
|
* States:
|
|
41
314
|
* unconfigured - no live_recording_config
|
|
42
315
|
* uninitialized - no live_recording config generated
|
|
@@ -46,15 +319,13 @@ function sleep(ms) {
|
|
|
46
319
|
* running - stream is running and producing output
|
|
47
320
|
* stalled - LRO running but no source data (so not producing output)
|
|
48
321
|
*
|
|
49
|
-
* @return {Object} - The status response for the object, as well as logs, warnings and errors from the master initialization
|
|
322
|
+
* @return {Promise<Object>} - The status response for the object, as well as logs, warnings and errors from the master initialization
|
|
50
323
|
*/
|
|
51
324
|
exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
52
325
|
let conf = await this.LoadConf({name});
|
|
53
|
-
|
|
54
326
|
let status = {name: name};
|
|
55
327
|
|
|
56
328
|
try {
|
|
57
|
-
|
|
58
329
|
let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
|
|
59
330
|
status.library_id = libraryId;
|
|
60
331
|
status.object_id = conf.objectId;
|
|
@@ -114,6 +385,8 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
114
385
|
]
|
|
115
386
|
});
|
|
116
387
|
|
|
388
|
+
status.edge_meta_size = JSON.stringify(edgeMeta).length;
|
|
389
|
+
|
|
117
390
|
// If a stream has never been started return state 'inactive'
|
|
118
391
|
if(edgeMeta.live_recording === undefined ||
|
|
119
392
|
edgeMeta.live_recording.recordings === undefined ||
|
|
@@ -168,7 +441,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
168
441
|
}
|
|
169
442
|
|
|
170
443
|
if(showParams) {
|
|
171
|
-
status.
|
|
444
|
+
status.recording_params = edgeMeta.live_recording.recording_config.recording_params;
|
|
172
445
|
}
|
|
173
446
|
|
|
174
447
|
let state = "stopped";
|
|
@@ -207,10 +480,12 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
207
480
|
await this.utils.ResponseToJson(
|
|
208
481
|
await HttpClient.Fetch(lroStopUrl)
|
|
209
482
|
);
|
|
483
|
+
|
|
210
484
|
console.log("LRO Stop: ", lroStatus.body);
|
|
211
485
|
} catch(error) {
|
|
212
486
|
console.log("LRO Stop (failed): ", error.response.statusCode);
|
|
213
487
|
}
|
|
488
|
+
|
|
214
489
|
state = "stopped";
|
|
215
490
|
status.state = state;
|
|
216
491
|
}
|
|
@@ -290,36 +565,18 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
|
290
565
|
return status;
|
|
291
566
|
};
|
|
292
567
|
|
|
293
|
-
// async StatusPrep({name}) {
|
|
294
|
-
//
|
|
295
|
-
// let conf = await this.LoadConf({name});
|
|
296
|
-
//
|
|
297
|
-
// try {
|
|
298
|
-
//
|
|
299
|
-
// // Set static token - avoid individual auth for separate channels/streams
|
|
300
|
-
// let token = await MakeTxLessToken({client: this.client, libraryId: conf.libraryId});
|
|
301
|
-
// this.client.SetStaticToken({token});
|
|
302
|
-
//
|
|
303
|
-
// } catch(error) {
|
|
304
|
-
// console.log("StatusPrep failed: ", error);
|
|
305
|
-
// return null;
|
|
306
|
-
// }
|
|
307
|
-
//
|
|
308
|
-
// }
|
|
309
|
-
|
|
310
568
|
/**
|
|
311
569
|
* Create a new edge write token
|
|
312
570
|
*
|
|
313
571
|
* @methodGroup Live Stream
|
|
314
572
|
* @namedParams
|
|
315
|
-
* @param {string} name -
|
|
316
|
-
* @param {boolean} start -
|
|
573
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
574
|
+
* @param {boolean=} start - If specified, will start the stream after creation
|
|
317
575
|
*
|
|
318
|
-
* @return {Object} - The status response for the object
|
|
576
|
+
* @return {Promise<Object>} - The status response for the object
|
|
319
577
|
*
|
|
320
578
|
*/
|
|
321
|
-
exports.StreamCreate = async function({name, start
|
|
322
|
-
|
|
579
|
+
exports.StreamCreate = async function({name, start=false}) {
|
|
323
580
|
let status = await this.StreamStatus({name});
|
|
324
581
|
if(status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
|
|
325
582
|
return {
|
|
@@ -392,6 +649,7 @@ exports.StreamCreate = async function({name, start = false}) {
|
|
|
392
649
|
writeToken: writeToken,
|
|
393
650
|
commitMessage: "Create stream edge write token " + edgeToken
|
|
394
651
|
});
|
|
652
|
+
|
|
395
653
|
const objectHash = response.hash;
|
|
396
654
|
this.Log("Finalized object: ", objectHash);
|
|
397
655
|
|
|
@@ -417,20 +675,21 @@ exports.StreamCreate = async function({name, start = false}) {
|
|
|
417
675
|
*
|
|
418
676
|
* @methodGroup Live Stream
|
|
419
677
|
* @namedParams
|
|
420
|
-
* @param {string} name -
|
|
421
|
-
* @param {string
|
|
678
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
679
|
+
* @param {string} op - The operation to perform. Possible values:
|
|
422
680
|
* 'start'
|
|
423
681
|
* 'reset' - Stops current LRO recording and starts a new one. Does
|
|
424
682
|
* not create a new edge write token (just creates a new recording
|
|
425
683
|
* period in the existing edge write token)
|
|
426
684
|
* - 'stop'
|
|
427
685
|
*
|
|
428
|
-
* @return {Object} - The status response for the stream
|
|
686
|
+
* @return {Promise<Object>} - The status response for the stream
|
|
429
687
|
*
|
|
430
688
|
*/
|
|
431
689
|
exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
432
690
|
try {
|
|
433
691
|
console.log("Stream ", op, ": ", name);
|
|
692
|
+
|
|
434
693
|
let status = await this.StreamStatus({name});
|
|
435
694
|
if(status.state != "stopped") {
|
|
436
695
|
if(op === "start") {
|
|
@@ -441,6 +700,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
441
700
|
|
|
442
701
|
if(status.state == "running" || status.state == "starting" || status.state == "stalled") {
|
|
443
702
|
console.log("STOPPING");
|
|
703
|
+
|
|
444
704
|
try {
|
|
445
705
|
await this.CallBitcodeMethod({
|
|
446
706
|
libraryId: status.library_id,
|
|
@@ -458,13 +718,14 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
458
718
|
let tries = 10;
|
|
459
719
|
while(status.state != "stopped" && tries-- > 0) {
|
|
460
720
|
console.log("Wait to terminate - ", status.state);
|
|
461
|
-
await
|
|
721
|
+
await Sleep(1000);
|
|
462
722
|
status = await this.StreamStatus({name});
|
|
463
723
|
}
|
|
464
|
-
|
|
724
|
+
|
|
725
|
+
console.log("Status after stop - ", status.state);
|
|
465
726
|
|
|
466
727
|
if(tries <= 0) {
|
|
467
|
-
console.log("Failed to
|
|
728
|
+
console.log("Failed to stop");
|
|
468
729
|
return status;
|
|
469
730
|
}
|
|
470
731
|
}
|
|
@@ -495,7 +756,7 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
495
756
|
let tries = 10;
|
|
496
757
|
while(status.state != "starting" && tries-- > 0) {
|
|
497
758
|
console.log("Wait to start - ", status.state);
|
|
498
|
-
await
|
|
759
|
+
await Sleep(1000);
|
|
499
760
|
status = await this.StreamStatus({name});
|
|
500
761
|
}
|
|
501
762
|
|
|
@@ -507,416 +768,505 @@ exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
|
507
768
|
}
|
|
508
769
|
};
|
|
509
770
|
|
|
510
|
-
|
|
771
|
+
/**
|
|
511
772
|
* Stop the live stream session and close the edge write token.
|
|
512
773
|
* Not implemented fully
|
|
774
|
+
*
|
|
775
|
+
* @methodGroup Live Stream
|
|
776
|
+
* @namedParams
|
|
777
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
778
|
+
*
|
|
779
|
+
* @return {Promise<Object>} - The finalize response for the stream object
|
|
780
|
+
*/
|
|
781
|
+
exports.StreamStopSession = async function({name}) {
|
|
782
|
+
try {
|
|
783
|
+
console.log("TERMINATE: ", name);
|
|
784
|
+
|
|
785
|
+
let conf = await this.LoadConf({name});
|
|
786
|
+
|
|
787
|
+
let {objectId} = conf;
|
|
788
|
+
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
789
|
+
|
|
790
|
+
let mainMeta = await this.ContentObjectMetadata({
|
|
791
|
+
libraryId,
|
|
792
|
+
objectId
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
796
|
+
// Support both hostname and URL ingress_node_api
|
|
797
|
+
if(!fabURI.startsWith("http")) {
|
|
798
|
+
// Assume https
|
|
799
|
+
fabURI = "https://" + fabURI;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
this.SetNodes({fabricURIs: [fabURI]});
|
|
803
|
+
|
|
804
|
+
let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
805
|
+
|
|
806
|
+
if(edgeWriteToken === undefined || edgeWriteToken === "") {
|
|
807
|
+
return {
|
|
808
|
+
state: "inactive",
|
|
809
|
+
error: "no active streams - must create a stream first"
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
let edgeMeta = await this.ContentObjectMetadata({
|
|
813
|
+
libraryId,
|
|
814
|
+
objectId,
|
|
815
|
+
writeToken: edgeWriteToken
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// Stop the LRO if running
|
|
819
|
+
let status = await this.StreamStatus({name});
|
|
820
|
+
if(status.state != "terminated") {
|
|
821
|
+
console.log("STOPPING");
|
|
822
|
+
try {
|
|
823
|
+
await this.CallBitcodeMethod({
|
|
824
|
+
libraryId: status.library_id,
|
|
825
|
+
objectId: status.object_id,
|
|
826
|
+
writeToken: status.edge_write_token,
|
|
827
|
+
method: "/live/stop/" + status.tlro,
|
|
828
|
+
constant: false
|
|
829
|
+
});
|
|
830
|
+
} catch(error) {
|
|
831
|
+
// The /call/lro/stop API returns empty response
|
|
832
|
+
// console.log("LRO Stop (failed): ", error);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Wait until LRO is terminated
|
|
836
|
+
let tries = 10;
|
|
837
|
+
while (status.state != "stopped" && tries-- > 0) {
|
|
838
|
+
console.log("Wait to terminate - ", status.state);
|
|
839
|
+
await Sleep(1000);
|
|
840
|
+
status = await this.StreamStatus({name});
|
|
841
|
+
}
|
|
842
|
+
console.log("Status after stop - ", status.state);
|
|
843
|
+
|
|
844
|
+
if(tries <= 0) {
|
|
845
|
+
console.log("Failed to stop");
|
|
846
|
+
return status;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Set stop time
|
|
851
|
+
edgeMeta.recording_stop_time = Math.floor(new Date().getTime() / 1000);
|
|
852
|
+
console.log("recording_start_time: ", edgeMeta.recording_start_time);
|
|
853
|
+
console.log("recording_stop_time: ", edgeMeta.recording_stop_time);
|
|
854
|
+
|
|
855
|
+
edgeMeta.live_recording.status = {
|
|
856
|
+
state: "terminated",
|
|
857
|
+
recording_stop_time: edgeMeta.recording_stop_time
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
edgeMeta.live_recording.fabric_config.edge_write_token = "";
|
|
861
|
+
|
|
862
|
+
await this.ReplaceMetadata({
|
|
863
|
+
libraryId,
|
|
864
|
+
objectId,
|
|
865
|
+
writeToken: edgeWriteToken,
|
|
866
|
+
metadata: edgeMeta
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
let fin = await this.FinalizeContentObject({
|
|
870
|
+
libraryId,
|
|
871
|
+
objectId,
|
|
872
|
+
writeToken: edgeWriteToken,
|
|
873
|
+
commitMessage: "Finalize live stream - stop time " + edgeMeta.recording_stop_time,
|
|
874
|
+
publish: false // Don't publish this version because it is not currently useful
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
fin,
|
|
879
|
+
name,
|
|
880
|
+
edge_write_token: edgeWriteToken,
|
|
881
|
+
state: "terminated"
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
} catch(error) {
|
|
885
|
+
console.error(error);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Initialize the stream object
|
|
891
|
+
*
|
|
892
|
+
* @methodGroup Live Stream
|
|
893
|
+
* @namedParams
|
|
894
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
895
|
+
* @param {boolean=} drm - If specified, playout will be DRM protected
|
|
896
|
+
* @param {string=} format - Specify the list of playout formats and DRM to support,
|
|
897
|
+
comma-separated (hls-clear, hls-aes128, hls-sample-aes,
|
|
898
|
+
hls-fairplay)
|
|
899
|
+
*
|
|
900
|
+
* @return {Promise<Object>} - The name, object ID, and state of the stream
|
|
513
901
|
*/
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
// libraryId: libraryId,
|
|
527
|
-
// objectId: objectId
|
|
528
|
-
// });
|
|
529
|
-
//
|
|
530
|
-
// let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
531
|
-
// // Support both hostname and URL ingress_node_api
|
|
532
|
-
// if(!fabURI.startsWith("http")) {
|
|
533
|
-
// // Assume https
|
|
534
|
-
// fabURI = "https://" + fabURI;
|
|
535
|
-
// }
|
|
536
|
-
//
|
|
537
|
-
// this.client.SetNodes({fabricURIs: [fabURI]});
|
|
538
|
-
//
|
|
539
|
-
// let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
540
|
-
//
|
|
541
|
-
// if(edgeWriteToken === undefined || edgeWriteToken === "") {
|
|
542
|
-
// return {
|
|
543
|
-
// state: "inactive",
|
|
544
|
-
// error: "no active streams - must create a stream first"
|
|
545
|
-
// };
|
|
546
|
-
// }
|
|
547
|
-
// let edgeMeta = await this.client.ContentObjectMetadata({
|
|
548
|
-
// libraryId: libraryId,
|
|
549
|
-
// objectId: objectId,
|
|
550
|
-
// writeToken: edgeWriteToken
|
|
551
|
-
// });
|
|
552
|
-
//
|
|
553
|
-
// // Stop the LRO if running
|
|
554
|
-
// let status = await this.Status({name});
|
|
555
|
-
// if(status.state != "terminated") {
|
|
556
|
-
// console.log("STOPPING");
|
|
557
|
-
// try {
|
|
558
|
-
// await this.client.CallBitcodeMethod({
|
|
559
|
-
// libraryId: status.library_id,
|
|
560
|
-
// objectId: status.object_id,
|
|
561
|
-
// writeToken: status.edge_write_token,
|
|
562
|
-
// method: "/live/stop/" + status.tlro,
|
|
563
|
-
// constant: false
|
|
564
|
-
// });
|
|
565
|
-
// } catch(error) {
|
|
566
|
-
// // The /call/lro/stop API returns empty response
|
|
567
|
-
// // console.log("LRO Stop (failed): ", error);
|
|
568
|
-
// }
|
|
569
|
-
//
|
|
570
|
-
// // Wait until LRO is terminated
|
|
571
|
-
// let tries = 10;
|
|
572
|
-
// while (status.state != "terminated" && tries-- > 0) {
|
|
573
|
-
// console.log("Wait to terminate - ", status.state);
|
|
574
|
-
// await sleep(1000);
|
|
575
|
-
// status = await this.Status({name});
|
|
576
|
-
// }
|
|
577
|
-
// console.log("Status after terminate - ", status.state);
|
|
578
|
-
//
|
|
579
|
-
// if(tries <= 0) {
|
|
580
|
-
// console.log("Failed to terminate");
|
|
581
|
-
// return status;
|
|
582
|
-
// }
|
|
583
|
-
// }
|
|
584
|
-
//
|
|
585
|
-
// // Set stop time
|
|
586
|
-
// edgeMeta.recording_stop_time = Math.floor(new Date().getTime() / 1000);
|
|
587
|
-
// console.log("recording_start_time: ", edgeMeta.recording_start_time);
|
|
588
|
-
// console.log("recording_stop_time: ", edgeMeta.recording_stop_time);
|
|
589
|
-
//
|
|
590
|
-
// edgeMeta.live_recording.status = {
|
|
591
|
-
// state: "terminated",
|
|
592
|
-
// recording_stop_time: edgeMeta.recording_stop_time
|
|
593
|
-
// };
|
|
594
|
-
//
|
|
595
|
-
// edgeMeta.live_recording.fabric_config.edge_write_token = "";
|
|
596
|
-
//
|
|
597
|
-
// await this.client.ReplaceMetadata({
|
|
598
|
-
// libraryId: libraryId,
|
|
599
|
-
// objectId: objectId,
|
|
600
|
-
// writeToken: edgeWriteToken,
|
|
601
|
-
// metadata: edgeMeta
|
|
602
|
-
// });
|
|
603
|
-
//
|
|
604
|
-
// let fin = await this.client.FinalizeContentObject({
|
|
605
|
-
// libraryId,
|
|
606
|
-
// objectId,
|
|
607
|
-
// writeToken: edgeWriteToken,
|
|
608
|
-
// commitMessage: "Finalize live stream - stop time " + edgeMeta.recording_stop_time,
|
|
609
|
-
// publish: false // Don't publish this version because it is not currently useful
|
|
610
|
-
// });
|
|
611
|
-
//
|
|
612
|
-
// return {
|
|
613
|
-
// fin,
|
|
614
|
-
// name: name,
|
|
615
|
-
// edge_write_token: edgeWriteToken,
|
|
616
|
-
// state: "terminated"
|
|
617
|
-
// };
|
|
618
|
-
//
|
|
619
|
-
// } catch(error) {
|
|
620
|
-
// console.error(error);
|
|
621
|
-
// }
|
|
622
|
-
// }
|
|
623
|
-
|
|
624
|
-
// async Initialize({name, drm=false, format}) {
|
|
625
|
-
//
|
|
626
|
-
// const contentTypes = await this.client.ContentTypes();
|
|
627
|
-
//
|
|
628
|
-
// let typeAbrMaster;
|
|
629
|
-
// let typeLiveStream;
|
|
630
|
-
// for (let i = 0; i < Object.keys(contentTypes).length; i ++) {
|
|
631
|
-
// const key = Object.keys(contentTypes)[i];
|
|
632
|
-
// if(contentTypes[key].name.includes("ABR Master") || contentTypes[key].name.includes("Title")) {
|
|
633
|
-
// typeAbrMaster = contentTypes[key].hash;
|
|
634
|
-
// }
|
|
635
|
-
// if(contentTypes[key].name.includes("Live Stream")) {
|
|
636
|
-
// typeLiveStream = contentTypes[key].hash;
|
|
637
|
-
// }
|
|
638
|
-
// }
|
|
639
|
-
//
|
|
640
|
-
// if(typeAbrMaster === undefined || typeLiveStream === undefined) {
|
|
641
|
-
// console.log("ERROR - unable to find content types", "ABR Master", typeAbrMaster, "Live Stream", typeLiveStream);
|
|
642
|
-
// return {};
|
|
643
|
-
// }
|
|
644
|
-
// let res = await this.SetOfferingAndDRM({name, typeAbrMaster, typeLiveStream, drm, format});
|
|
645
|
-
// return res;
|
|
646
|
-
// }
|
|
647
|
-
|
|
648
|
-
// async SetOfferingAndDRM({name, typeAbrMaster, typeLiveStream, drm=false, format}) {
|
|
649
|
-
//
|
|
650
|
-
// let status = await this.Status({name});
|
|
651
|
-
// if(status.state != "inactive" && status.state != "terminated") {
|
|
652
|
-
// return {
|
|
653
|
-
// state: status.state,
|
|
654
|
-
// error: "stream still active - must terminate first"
|
|
655
|
-
// };
|
|
656
|
-
// }
|
|
657
|
-
//
|
|
658
|
-
// let objectId = status.object_id;
|
|
659
|
-
//
|
|
660
|
-
// console.log("INIT: ", name, objectId);
|
|
661
|
-
//
|
|
662
|
-
// const {GenerateOffering} = require("./LiveObjectSetupStepOne");
|
|
663
|
-
//
|
|
664
|
-
// const aBitRate = 128000;
|
|
665
|
-
// const aChannels = 2;
|
|
666
|
-
// const aSampleRate = 48000;
|
|
667
|
-
// const aStreamIndex = 1;
|
|
668
|
-
// const aTimeBase = "1/48000";
|
|
669
|
-
// const aChannelLayout = "stereo";
|
|
670
|
-
//
|
|
671
|
-
// const vBitRate = 14000000;
|
|
672
|
-
// const vHeight = 720;
|
|
673
|
-
// const vStreamIndex = 0;
|
|
674
|
-
// const vWidth = 1280;
|
|
675
|
-
// const vDisplayAspectRatio = "16/9";
|
|
676
|
-
// const vFrameRate = "30000/1001";
|
|
677
|
-
// const vTimeBase = "1/30000"; // "1/16000";
|
|
678
|
-
//
|
|
679
|
-
// const abrProfile = require("./abr_profile_live_drm.json");
|
|
680
|
-
//
|
|
681
|
-
// let playoutFormats = abrProfile.playout_formats;
|
|
682
|
-
// if(format) {
|
|
683
|
-
// drm = true; // Override DRM parameter
|
|
684
|
-
// playoutFormats = {};
|
|
685
|
-
// let formats = format.split(",");
|
|
686
|
-
// for (let i = 0; i < formats.length; i++) {
|
|
687
|
-
// if(formats[i] === "hls-clear") {
|
|
688
|
-
// abrProfile.drm_optional = true;
|
|
689
|
-
// playoutFormats["hls-clear"] = {
|
|
690
|
-
// "drm": null,
|
|
691
|
-
// "protocol": {
|
|
692
|
-
// "type": "ProtoHls"
|
|
693
|
-
// }
|
|
694
|
-
// };
|
|
695
|
-
// continue;
|
|
696
|
-
// }
|
|
697
|
-
// playoutFormats[formats[i]] = abrProfile.playout_formats[formats[i]];
|
|
698
|
-
// }
|
|
699
|
-
// } else if(!drm) {
|
|
700
|
-
// abrProfile.drm_optional = true;
|
|
701
|
-
// playoutFormats = {
|
|
702
|
-
// "hls-clear": {
|
|
703
|
-
// "drm": null,
|
|
704
|
-
// "protocol": {
|
|
705
|
-
// "type": "ProtoHls"
|
|
706
|
-
// }
|
|
707
|
-
// }
|
|
708
|
-
// };
|
|
709
|
-
// }
|
|
710
|
-
//
|
|
711
|
-
// abrProfile.playout_formats = playoutFormats;
|
|
712
|
-
//
|
|
713
|
-
// let libraryId = await this.client.ContentObjectLibraryId({objectId});
|
|
714
|
-
//
|
|
715
|
-
// try {
|
|
716
|
-
//
|
|
717
|
-
// let mainMeta = await this.client.ContentObjectMetadata({
|
|
718
|
-
// libraryId: libraryId,
|
|
719
|
-
// objectId: objectId
|
|
720
|
-
// });
|
|
721
|
-
//
|
|
722
|
-
// let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
723
|
-
// // Support both hostname and URL ingress_node_api
|
|
724
|
-
// if(!fabURI.startsWith("http")) {
|
|
725
|
-
// // Assume https
|
|
726
|
-
// fabURI = "https://" + fabURI;
|
|
727
|
-
// }
|
|
728
|
-
//
|
|
729
|
-
// this.client.SetNodes({fabricURIs: [fabURI]});
|
|
730
|
-
//
|
|
731
|
-
// let streamUrl = mainMeta.live_recording.recording_config.recording_params.origin_url;
|
|
732
|
-
//
|
|
733
|
-
// await GenerateOffering({
|
|
734
|
-
// client: this.client,
|
|
735
|
-
// libraryId,
|
|
736
|
-
// objectId,
|
|
737
|
-
// typeAbrMaster, typeLiveStream,
|
|
738
|
-
// streamUrl,
|
|
739
|
-
// abrProfile,
|
|
740
|
-
// aBitRate, aChannels, aSampleRate, aStreamIndex,
|
|
741
|
-
// aTimeBase,
|
|
742
|
-
// aChannelLayout,
|
|
743
|
-
// vBitRate, vHeight, vStreamIndex, vWidth,
|
|
744
|
-
// vDisplayAspectRatio, vFrameRate, vTimeBase
|
|
745
|
-
// });
|
|
746
|
-
//
|
|
747
|
-
// console.log("GenerateOffering - DONE");
|
|
748
|
-
//
|
|
749
|
-
// return {
|
|
750
|
-
// name,
|
|
751
|
-
// object_id: objectId,
|
|
752
|
-
// state: "initialized"
|
|
753
|
-
// };
|
|
754
|
-
// } catch(error) {
|
|
755
|
-
// console.error(error);
|
|
756
|
-
// }
|
|
757
|
-
// }
|
|
758
|
-
|
|
759
|
-
// Add a content insertion entry
|
|
760
|
-
// Parameters:
|
|
761
|
-
// - insertionTime - seconds (float)
|
|
762
|
-
// - sinceStart - true if time specified since stream start, false if since epoch
|
|
763
|
-
// - duration - seconds (float, deafault 20.0)
|
|
764
|
-
// - targetHash - playable
|
|
765
|
-
// - remove - flag to remove the insertion at that exact 'time' (instead of adding)
|
|
766
|
-
// async Insertion({name, insertionTime, sinceStart, duration, targetHash, remove}) {
|
|
767
|
-
//
|
|
768
|
-
// // Determine audio and video parameters of the insertion
|
|
769
|
-
// const insertionInfo = await this.getOfferingInfo({versionHash: targetHash});
|
|
770
|
-
// const audioAbrDuration = insertionInfo.audio.seg_duration_sec;
|
|
771
|
-
// const videoAbrDuration = insertionInfo.video.seg_duration_sec;
|
|
772
|
-
//
|
|
773
|
-
// if(audioAbrDuration === 0 || videoAbrDuration === 0) {
|
|
774
|
-
// throw new Error("Bad segment duration hash:", targetHash);
|
|
775
|
-
// }
|
|
776
|
-
//
|
|
777
|
-
// if(duration === undefined) {
|
|
778
|
-
// duration = insertionInfo.duration_sec; // Use full duration of the insertion
|
|
779
|
-
// } else {
|
|
780
|
-
// if(duration > insertionInfo.duration_sec) {
|
|
781
|
-
// throw new Error("Bad duration - larger than insertion object duration", insertionInfo.duration_sec);
|
|
782
|
-
// }
|
|
783
|
-
// }
|
|
784
|
-
//
|
|
785
|
-
// let conf = await this.LoadConf({name});
|
|
786
|
-
// let libraryId = await this.client.ContentObjectLibraryId({objectId: conf.objectId});
|
|
787
|
-
// let objectId = conf.objectId;
|
|
788
|
-
//
|
|
789
|
-
// let mainMeta = await this.client.ContentObjectMetadata({
|
|
790
|
-
// libraryId: libraryId,
|
|
791
|
-
// objectId: conf.objectId
|
|
792
|
-
// });
|
|
793
|
-
//
|
|
794
|
-
// let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
795
|
-
//
|
|
796
|
-
// // Support both hostname and URL ingress_node_api
|
|
797
|
-
// if(!fabURI.startsWith("http")) {
|
|
798
|
-
// // Assume https
|
|
799
|
-
// fabURI = "https://" + fabURI;
|
|
800
|
-
// }
|
|
801
|
-
// this.client.SetNodes({fabricURIs: [fabURI]});
|
|
802
|
-
// let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
803
|
-
//
|
|
804
|
-
// let edgeMeta = await this.client.ContentObjectMetadata({
|
|
805
|
-
// libraryId: libraryId,
|
|
806
|
-
// objectId: conf.objectId,
|
|
807
|
-
// writeToken: edgeWriteToken
|
|
808
|
-
// });
|
|
809
|
-
//
|
|
810
|
-
// // Find stream start time (from the most recent recording section)
|
|
811
|
-
// let recordings = edgeMeta.live_recording.recordings;
|
|
812
|
-
// let sequence = 1;
|
|
813
|
-
// let streamStartTime = 0;
|
|
814
|
-
// if(recordings != undefined && recordings.recording_sequence != undefined) {
|
|
815
|
-
// // We have at least one recording - check if still active
|
|
816
|
-
// sequence = recordings.recording_sequence;
|
|
817
|
-
// let period = recordings.live_offering[sequence - 1];
|
|
818
|
-
//
|
|
819
|
-
// if(period.end_time_epoch_sec > 0) {
|
|
820
|
-
// // The last period is closed - apply insertions to the next period
|
|
821
|
-
// sequence ++;
|
|
822
|
-
// } else {
|
|
823
|
-
// // The period is active
|
|
824
|
-
// streamStartTime = period.start_time_epoch_sec;
|
|
825
|
-
// }
|
|
826
|
-
// }
|
|
827
|
-
//
|
|
828
|
-
// if(streamStartTime === 0) {
|
|
829
|
-
// // There is no active period - must use absolute time
|
|
830
|
-
// if(sinceStart === false) {
|
|
831
|
-
// throw new Error("Stream not running - must use 'time since start'");
|
|
832
|
-
// }
|
|
833
|
-
// }
|
|
834
|
-
//
|
|
835
|
-
// // Find the current period playout configuration
|
|
836
|
-
// if(edgeMeta.live_recording.playout_config.interleaves === undefined) {
|
|
837
|
-
// edgeMeta.live_recording.playout_config.interleaves = {};
|
|
838
|
-
// }
|
|
839
|
-
// if(edgeMeta.live_recording.playout_config.interleaves[sequence] === undefined) {
|
|
840
|
-
// edgeMeta.live_recording.playout_config.interleaves[sequence] = [];
|
|
841
|
-
// }
|
|
842
|
-
//
|
|
843
|
-
// let playoutConfig = edgeMeta.live_recording.playout_config;
|
|
844
|
-
// let insertions = playoutConfig.interleaves[sequence];
|
|
845
|
-
//
|
|
846
|
-
// let res = {};
|
|
847
|
-
//
|
|
848
|
-
// if(!sinceStart) {
|
|
849
|
-
// insertionTime = insertionTime - streamStartTime;
|
|
850
|
-
// }
|
|
851
|
-
//
|
|
852
|
-
// // Assume insertions are sorted by insertion time
|
|
853
|
-
// let errs = [];
|
|
854
|
-
// let currentTime = -1;
|
|
855
|
-
// let insertionDone = false;
|
|
856
|
-
// let newInsertion = {
|
|
857
|
-
// insertion_time: insertionTime,
|
|
858
|
-
// duration: duration,
|
|
859
|
-
// audio_abr_duration: audioAbrDuration,
|
|
860
|
-
// video_abr_duration: videoAbrDuration,
|
|
861
|
-
// playout: "/qfab/" + targetHash + "/rep/playout" // TO FIX - should be a link
|
|
862
|
-
// };
|
|
863
|
-
//
|
|
864
|
-
// for (let i = 0; i < insertions.length; i ++) {
|
|
865
|
-
// if(insertions[i].insertion_time <= currentTime) {
|
|
866
|
-
// // Bad insertion - must be later than current time
|
|
867
|
-
// append(errs, "Bad insertion - time:", insertions[i].insertion_time);
|
|
868
|
-
// }
|
|
869
|
-
// if(remove) {
|
|
870
|
-
// if(insertions[i].insertion_time === insertionTime) {
|
|
871
|
-
// insertions.splice(i, 1);
|
|
872
|
-
// break;
|
|
873
|
-
// }
|
|
874
|
-
// } else {
|
|
875
|
-
// if(insertions[i].insertion_time > insertionTime) {
|
|
876
|
-
// if(i > 0) {
|
|
877
|
-
// insertions = [
|
|
878
|
-
// ...insertions.splice(0, i),
|
|
879
|
-
// newInsertion,
|
|
880
|
-
// ...insertions.splice(i)
|
|
881
|
-
// ];
|
|
882
|
-
// } else {
|
|
883
|
-
// insertions = [
|
|
884
|
-
// newInsertion,
|
|
885
|
-
// ...insertions.splice(i)
|
|
886
|
-
// ];
|
|
887
|
-
// }
|
|
888
|
-
// insertionDone = true;
|
|
889
|
-
// break;
|
|
890
|
-
// }
|
|
891
|
-
// }
|
|
892
|
-
// }
|
|
893
|
-
//
|
|
894
|
-
// if(!remove && !insertionDone) {
|
|
895
|
-
// // Add to the end of the insertions list
|
|
896
|
-
// console.log("Add insertion at the end");
|
|
897
|
-
// insertions = [
|
|
898
|
-
// ...insertions,
|
|
899
|
-
// newInsertion
|
|
900
|
-
// ];
|
|
901
|
-
// }
|
|
902
|
-
//
|
|
903
|
-
// playoutConfig.interleaves[sequence] = insertions;
|
|
904
|
-
//
|
|
905
|
-
// // Store the new insertions in the write token
|
|
906
|
-
// await this.client.ReplaceMetadata({
|
|
907
|
-
// libraryId: libraryId,
|
|
908
|
-
// objectId: objectId,
|
|
909
|
-
// writeToken: edgeWriteToken,
|
|
910
|
-
// metadataSubtree: "/live_recording/playout_config",
|
|
911
|
-
// metadata: edgeMeta.live_recording.playout_config
|
|
912
|
-
// });
|
|
913
|
-
//
|
|
914
|
-
// res.errors = errs;
|
|
915
|
-
// res.insertions = insertions;
|
|
916
|
-
// return res;
|
|
917
|
-
// }
|
|
902
|
+
exports.StreamInitialize = async function({name, drm=false, format}) {
|
|
903
|
+
const contentTypes = await this.ContentTypes();
|
|
904
|
+
|
|
905
|
+
let typeAbrMaster;
|
|
906
|
+
let typeLiveStream;
|
|
907
|
+
|
|
908
|
+
for(let i = 0; i < Object.keys(contentTypes).length; i++) {
|
|
909
|
+
const key = Object.keys(contentTypes)[i];
|
|
910
|
+
|
|
911
|
+
if(contentTypes[key].name.includes("ABR Master") || contentTypes[key].name.includes("Title")) {
|
|
912
|
+
typeAbrMaster = contentTypes[key].hash;
|
|
913
|
+
}
|
|
918
914
|
|
|
915
|
+
if(contentTypes[key].name.includes("Live Stream")) {
|
|
916
|
+
typeLiveStream = contentTypes[key].hash;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if(typeAbrMaster === undefined || typeLiveStream === undefined) {
|
|
921
|
+
console.log("ERROR - unable to find content types", "ABR Master", typeAbrMaster, "Live Stream", typeLiveStream);
|
|
922
|
+
return {};
|
|
923
|
+
}
|
|
919
924
|
|
|
925
|
+
const res = await this.StreamSetOfferingAndDRM({name, typeAbrMaster, typeLiveStream, drm, format});
|
|
926
|
+
|
|
927
|
+
return res;
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Set the Live Stream offering
|
|
932
|
+
*
|
|
933
|
+
* @methodGroup Live Stream
|
|
934
|
+
* @namedParams
|
|
935
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
936
|
+
* @param {string=} typeAbrMaster - Content type hash
|
|
937
|
+
* @param {string=} typeLiveStream - Content type hash
|
|
938
|
+
* @param {boolean=} drm - If specified, DRM will be applied to the stream
|
|
939
|
+
* @param {string=} format - A list of playout formats and DRM to support, comma-separated
|
|
940
|
+
* (hls-clear, hls-aes128, hls-sample-aes, hls-fairplay). If specified,
|
|
941
|
+
* this will take precedence over the drm value
|
|
942
|
+
*
|
|
943
|
+
* @return {Promise<Object>} - The name, object ID, and state of the stream
|
|
944
|
+
*/
|
|
945
|
+
exports.StreamSetOfferingAndDRM = async function({name, typeAbrMaster, typeLiveStream, drm=false, format}) {
|
|
946
|
+
let status = await this.StreamStatus({name});
|
|
947
|
+
if(status.state != "inactive" && status.state != "stopped") {
|
|
948
|
+
return {
|
|
949
|
+
state: status.state,
|
|
950
|
+
error: "stream still active - must terminate first"
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
let objectId = status.object_id;
|
|
955
|
+
|
|
956
|
+
console.log("INIT: ", name, objectId);
|
|
957
|
+
|
|
958
|
+
const aBitRate = 128000;
|
|
959
|
+
const aChannels = 2;
|
|
960
|
+
const aSampleRate = 48000;
|
|
961
|
+
const aStreamIndex = 1;
|
|
962
|
+
const aTimeBase = "1/48000";
|
|
963
|
+
const aChannelLayout = "stereo";
|
|
964
|
+
|
|
965
|
+
const vBitRate = 14000000;
|
|
966
|
+
const vHeight = 720;
|
|
967
|
+
const vStreamIndex = 0;
|
|
968
|
+
const vWidth = 1280;
|
|
969
|
+
const vDisplayAspectRatio = "16/9";
|
|
970
|
+
const vFrameRate = "30000/1001";
|
|
971
|
+
const vTimeBase = "1/30000"; // "1/16000";
|
|
972
|
+
|
|
973
|
+
const abrProfile = require("../abr_profiles/abr_profile_live_drm.js");
|
|
974
|
+
|
|
975
|
+
let playoutFormats = abrProfile.playout_formats;
|
|
976
|
+
if(format) {
|
|
977
|
+
drm = true; // Override DRM parameter
|
|
978
|
+
playoutFormats = {};
|
|
979
|
+
let formats = format.split(",");
|
|
980
|
+
for(let i = 0; i < formats.length; i++) {
|
|
981
|
+
if(formats[i] === "hls-clear") {
|
|
982
|
+
abrProfile.drm_optional = true;
|
|
983
|
+
playoutFormats["hls-clear"] = {
|
|
984
|
+
"drm": null,
|
|
985
|
+
"protocol": {
|
|
986
|
+
"type": "ProtoHls"
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
playoutFormats[formats[i]] = abrProfile.playout_formats[formats[i]];
|
|
992
|
+
}
|
|
993
|
+
} else if(!drm) {
|
|
994
|
+
abrProfile.drm_optional = true;
|
|
995
|
+
playoutFormats = {
|
|
996
|
+
"hls-clear": {
|
|
997
|
+
"drm": null,
|
|
998
|
+
"protocol": {
|
|
999
|
+
"type": "ProtoHls"
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
abrProfile.playout_formats = playoutFormats;
|
|
1006
|
+
|
|
1007
|
+
let libraryId = await this.ContentObjectLibraryId({objectId});
|
|
1008
|
+
|
|
1009
|
+
try {
|
|
1010
|
+
let mainMeta = await this.ContentObjectMetadata({
|
|
1011
|
+
libraryId,
|
|
1012
|
+
objectId
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
1016
|
+
// Support both hostname and URL ingress_node_api
|
|
1017
|
+
if(!fabURI.startsWith("http")) {
|
|
1018
|
+
// Assume https
|
|
1019
|
+
fabURI = "https://" + fabURI;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
this.SetNodes({fabricURIs: [fabURI]});
|
|
1023
|
+
|
|
1024
|
+
let streamUrl = mainMeta.live_recording.recording_config.recording_params.origin_url;
|
|
1025
|
+
|
|
1026
|
+
await StreamGenerateOffering({
|
|
1027
|
+
client: this,
|
|
1028
|
+
libraryId,
|
|
1029
|
+
objectId,
|
|
1030
|
+
typeAbrMaster,
|
|
1031
|
+
typeLiveStream,
|
|
1032
|
+
streamUrl,
|
|
1033
|
+
abrProfile,
|
|
1034
|
+
aBitRate,
|
|
1035
|
+
aChannels,
|
|
1036
|
+
aSampleRate,
|
|
1037
|
+
aStreamIndex,
|
|
1038
|
+
aTimeBase,
|
|
1039
|
+
aChannelLayout,
|
|
1040
|
+
vBitRate,
|
|
1041
|
+
vHeight,
|
|
1042
|
+
vStreamIndex,
|
|
1043
|
+
vWidth,
|
|
1044
|
+
vDisplayAspectRatio,
|
|
1045
|
+
vFrameRate,
|
|
1046
|
+
vTimeBase
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
console.log("Finished generating offering");
|
|
1050
|
+
|
|
1051
|
+
return {
|
|
1052
|
+
name,
|
|
1053
|
+
object_id: objectId,
|
|
1054
|
+
state: "initialized"
|
|
1055
|
+
};
|
|
1056
|
+
} catch(error) {
|
|
1057
|
+
console.error(error);
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Add a content insertion entry
|
|
1063
|
+
*
|
|
1064
|
+
* @methodGroup Live Stream
|
|
1065
|
+
* @namedParams
|
|
1066
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
1067
|
+
* @param {number} insertionTime - Time in seconds (float)
|
|
1068
|
+
* @param {boolean=} sinceStart - If specified, time specified will be elapsed seconds
|
|
1069
|
+
* since stream start, otherwise, time will be elapsed since epoch
|
|
1070
|
+
* @param {number=} duration - Time in seconds (float). Default: 20.0
|
|
1071
|
+
* @param {string} targetHash - The target content object hash (playable)
|
|
1072
|
+
* @param {boolean=} remove - If specified, will remove the inseration at the exact 'time' (instead of adding)
|
|
1073
|
+
*
|
|
1074
|
+
* @return {Promise<Object>} - Insertions, as well as any errors from bad insertions
|
|
1075
|
+
*/
|
|
1076
|
+
exports.StreamInsertion = async function({name, insertionTime, sinceStart=false, duration, targetHash, remove=false}) {
|
|
1077
|
+
// Determine audio and video parameters of the insertion
|
|
1078
|
+
|
|
1079
|
+
// Content Type check is currently disabled due to permissions
|
|
1080
|
+
/*
|
|
1081
|
+
let ct = await this.client.ContentObject({versionHash});
|
|
1082
|
+
if(ct.type != undefined && ct.type != "") {
|
|
1083
|
+
let typeMeta = await this.client.ContentObjectMetadata({
|
|
1084
|
+
versionHash: ct.type
|
|
1085
|
+
});
|
|
1086
|
+
if(typeMeta.bitcode_flags != "abrmaster") {
|
|
1087
|
+
throw new Error("Not a playable VoD object " + versionHash);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
*/
|
|
1091
|
+
let offeringMeta = await this.ContentObjectMetadata({
|
|
1092
|
+
versionHash: targetHash,
|
|
1093
|
+
metadataSubtree: "/offerings/default"
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
var insertionInfo = {
|
|
1097
|
+
duration_sec: 0 // Minimum of video and audio duration
|
|
1098
|
+
};
|
|
1099
|
+
["video", "audio"].forEach(mt => {
|
|
1100
|
+
const stream = offeringMeta.media_struct.streams[mt];
|
|
1101
|
+
insertionInfo[mt] = {
|
|
1102
|
+
seg_duration_sec: stream.optimum_seg_dur.float,
|
|
1103
|
+
duration_sec: stream.duration.float,
|
|
1104
|
+
frame_rate_rat: stream.rate,
|
|
1105
|
+
};
|
|
1106
|
+
if(insertionInfo.duration_sec === 0 || stream.duration.float < insertionInfo.duration_sec) {
|
|
1107
|
+
insertionInfo.duration_sec = stream.duration.float;
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
const audioAbrDuration = insertionInfo.audio.seg_duration_sec;
|
|
1112
|
+
const videoAbrDuration = insertionInfo.video.seg_duration_sec;
|
|
1113
|
+
|
|
1114
|
+
if(audioAbrDuration === 0 || videoAbrDuration === 0) {
|
|
1115
|
+
throw new Error("Bad segment duration hash:", targetHash);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if(duration === undefined) {
|
|
1119
|
+
duration = insertionInfo.duration_sec; // Use full duration of the insertion
|
|
1120
|
+
} else {
|
|
1121
|
+
if(duration > insertionInfo.duration_sec) {
|
|
1122
|
+
throw new Error("Bad duration - larger than insertion object duration", insertionInfo.duration_sec);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
let conf = await this.LoadConf({name});
|
|
1127
|
+
let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
|
|
1128
|
+
let objectId = conf.objectId;
|
|
1129
|
+
|
|
1130
|
+
let mainMeta = await this.ContentObjectMetadata({
|
|
1131
|
+
libraryId: libraryId,
|
|
1132
|
+
objectId: conf.objectId
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
1136
|
+
|
|
1137
|
+
// Support both hostname and URL ingress_node_api
|
|
1138
|
+
if(!fabURI.startsWith("http")) {
|
|
1139
|
+
// Assume https
|
|
1140
|
+
fabURI = "https://" + fabURI;
|
|
1141
|
+
}
|
|
1142
|
+
this.SetNodes({fabricURIs: [fabURI]});
|
|
1143
|
+
let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
1144
|
+
|
|
1145
|
+
let edgeMeta = await this.ContentObjectMetadata({
|
|
1146
|
+
libraryId: libraryId,
|
|
1147
|
+
objectId: conf.objectId,
|
|
1148
|
+
writeToken: edgeWriteToken
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// Find stream start time (from the most recent recording section)
|
|
1152
|
+
let recordings = edgeMeta.live_recording.recordings;
|
|
1153
|
+
let sequence = 1;
|
|
1154
|
+
let streamStartTime = 0;
|
|
1155
|
+
if(recordings != undefined && recordings.recording_sequence != undefined) {
|
|
1156
|
+
// We have at least one recording - check if still active
|
|
1157
|
+
sequence = recordings.recording_sequence;
|
|
1158
|
+
let period = recordings.live_offering[sequence - 1];
|
|
1159
|
+
|
|
1160
|
+
if(period.end_time_epoch_sec > 0) {
|
|
1161
|
+
// The last period is closed - apply insertions to the next period
|
|
1162
|
+
sequence ++;
|
|
1163
|
+
} else {
|
|
1164
|
+
// The period is active
|
|
1165
|
+
streamStartTime = period.start_time_epoch_sec;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
if(streamStartTime === 0) {
|
|
1170
|
+
// There is no active period - must use absolute time
|
|
1171
|
+
if(sinceStart === false) {
|
|
1172
|
+
throw new Error("Stream not running - must use 'time since start'");
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Find the current period playout configuration
|
|
1177
|
+
if(edgeMeta.live_recording.playout_config.interleaves === undefined) {
|
|
1178
|
+
edgeMeta.live_recording.playout_config.interleaves = {};
|
|
1179
|
+
}
|
|
1180
|
+
if(edgeMeta.live_recording.playout_config.interleaves[sequence] === undefined) {
|
|
1181
|
+
edgeMeta.live_recording.playout_config.interleaves[sequence] = [];
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
let playoutConfig = edgeMeta.live_recording.playout_config;
|
|
1185
|
+
let insertions = playoutConfig.interleaves[sequence];
|
|
1186
|
+
|
|
1187
|
+
let res = {};
|
|
1188
|
+
|
|
1189
|
+
if(!sinceStart) {
|
|
1190
|
+
insertionTime = insertionTime - streamStartTime;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Assume insertions are sorted by insertion time
|
|
1194
|
+
let errs = [];
|
|
1195
|
+
let currentTime = -1;
|
|
1196
|
+
let insertionDone = false;
|
|
1197
|
+
let newInsertion = {
|
|
1198
|
+
insertion_time: insertionTime,
|
|
1199
|
+
duration: duration,
|
|
1200
|
+
audio_abr_duration: audioAbrDuration,
|
|
1201
|
+
video_abr_duration: videoAbrDuration,
|
|
1202
|
+
playout: "/qfab/" + targetHash + "/rep/playout" // TO FIX - should be a link
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
for (let i = 0; i < insertions.length; i ++) {
|
|
1206
|
+
if(insertions[i].insertion_time <= currentTime) {
|
|
1207
|
+
// Bad insertion - must be later than current time
|
|
1208
|
+
append(errs, "Bad insertion - time:", insertions[i].insertion_time);
|
|
1209
|
+
}
|
|
1210
|
+
if(remove) {
|
|
1211
|
+
if(insertions[i].insertion_time === insertionTime) {
|
|
1212
|
+
insertions.splice(i, 1);
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
} else {
|
|
1216
|
+
if(insertions[i].insertion_time > insertionTime) {
|
|
1217
|
+
if(i > 0) {
|
|
1218
|
+
insertions = [
|
|
1219
|
+
...insertions.splice(0, i),
|
|
1220
|
+
newInsertion,
|
|
1221
|
+
...insertions.splice(i)
|
|
1222
|
+
];
|
|
1223
|
+
} else {
|
|
1224
|
+
insertions = [
|
|
1225
|
+
newInsertion,
|
|
1226
|
+
...insertions.splice(i)
|
|
1227
|
+
];
|
|
1228
|
+
}
|
|
1229
|
+
insertionDone = true;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if(!remove && !insertionDone) {
|
|
1236
|
+
// Add to the end of the insertions list
|
|
1237
|
+
console.log("Add insertion at the end");
|
|
1238
|
+
insertions = [
|
|
1239
|
+
...insertions,
|
|
1240
|
+
newInsertion
|
|
1241
|
+
];
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
playoutConfig.interleaves[sequence] = insertions;
|
|
1245
|
+
|
|
1246
|
+
// Store the new insertions in the write token
|
|
1247
|
+
await this.ReplaceMetadata({
|
|
1248
|
+
libraryId: libraryId,
|
|
1249
|
+
objectId: objectId,
|
|
1250
|
+
writeToken: edgeWriteToken,
|
|
1251
|
+
metadataSubtree: "/live_recording/playout_config",
|
|
1252
|
+
metadata: edgeMeta.live_recording.playout_config
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
res.errors = errs;
|
|
1256
|
+
res.insertions = insertions;
|
|
1257
|
+
|
|
1258
|
+
return res;
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Load cached stream configuration
|
|
1263
|
+
*
|
|
1264
|
+
* @methodGroup Live Stream
|
|
1265
|
+
* @namedParams
|
|
1266
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
1267
|
+
*
|
|
1268
|
+
* @return {Promise<Object>} - The configuration of the stream
|
|
1269
|
+
*/
|
|
920
1270
|
exports.LoadConf = async function({name}) {
|
|
921
1271
|
if(name.startsWith("iq__")) {
|
|
922
1272
|
return {
|
|
@@ -945,180 +1295,22 @@ exports.LoadConf = async function({name}) {
|
|
|
945
1295
|
return conf;
|
|
946
1296
|
};
|
|
947
1297
|
|
|
948
|
-
/*
|
|
949
|
-
* Read a playable contnet object and get information about a particular offering
|
|
950
|
-
*/
|
|
951
|
-
// async getOfferingInfo({versionHash, offering = "default"}) {
|
|
952
|
-
//
|
|
953
|
-
// // Content Type check is currently disabled due to permissions
|
|
954
|
-
// /*
|
|
955
|
-
// let ct = await this.client.ContentObject({versionHash});
|
|
956
|
-
// if(ct.type != undefined && ct.type != "") {
|
|
957
|
-
// let typeMeta = await this.client.ContentObjectMetadata({
|
|
958
|
-
// versionHash: ct.type
|
|
959
|
-
// });
|
|
960
|
-
// if(typeMeta.bitcode_flags != "abrmaster") {
|
|
961
|
-
// throw new Error("Not a playable VoD object " + versionHash);
|
|
962
|
-
// }
|
|
963
|
-
// }
|
|
964
|
-
// */
|
|
965
|
-
// let offeringMeta = await this.client.ContentObjectMetadata({
|
|
966
|
-
// versionHash,
|
|
967
|
-
// metadataSubtree: "/offerings/" + offering
|
|
968
|
-
// });
|
|
969
|
-
//
|
|
970
|
-
// var info = {
|
|
971
|
-
// duration_sec: 0 // Minimum of video and audio duration
|
|
972
|
-
// };
|
|
973
|
-
// ["video", "audio"].forEach(mt => {
|
|
974
|
-
// const stream = offeringMeta.media_struct.streams[mt];
|
|
975
|
-
// info[mt] = {
|
|
976
|
-
// seg_duration_sec: stream.optimum_seg_dur.float,
|
|
977
|
-
// duration_sec: stream.duration.float,
|
|
978
|
-
// frame_rate_rat: stream.rate,
|
|
979
|
-
// };
|
|
980
|
-
// if(info.duration_sec === 0 || stream.duration.float < info.duration_sec) {
|
|
981
|
-
// info.duration_sec = stream.duration.float;
|
|
982
|
-
// }
|
|
983
|
-
// });
|
|
984
|
-
// return info;
|
|
985
|
-
// }
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
// async StreamDownload({name, period}) {
|
|
989
|
-
//
|
|
990
|
-
// let conf = await this.LoadConf({name});
|
|
991
|
-
//
|
|
992
|
-
// let status = {name};
|
|
993
|
-
//
|
|
994
|
-
// try {
|
|
995
|
-
//
|
|
996
|
-
// let libraryId = await this.client.ContentObjectLibraryId({objectId: conf.objectId});
|
|
997
|
-
// status.library_id = libraryId;
|
|
998
|
-
// status.object_id = conf.objectId;
|
|
999
|
-
//
|
|
1000
|
-
// let mainMeta = await this.client.ContentObjectMetadata({
|
|
1001
|
-
// libraryId: libraryId,
|
|
1002
|
-
// objectId: conf.objectId
|
|
1003
|
-
// });
|
|
1004
|
-
//
|
|
1005
|
-
// let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
1006
|
-
// if(fabURI === undefined) {
|
|
1007
|
-
// console.log("bad fabric config - missing ingress node API");
|
|
1008
|
-
// }
|
|
1009
|
-
//
|
|
1010
|
-
// // Support both hostname and URL ingress_node_api
|
|
1011
|
-
// if(!fabURI.startsWith("http")) {
|
|
1012
|
-
// // Assume https
|
|
1013
|
-
// fabURI = "https://" + fabURI;
|
|
1014
|
-
// }
|
|
1015
|
-
// this.client.SetNodes({fabricURIs: [fabURI]});
|
|
1016
|
-
//
|
|
1017
|
-
// let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
1018
|
-
// let edgeMeta = await this.client.ContentObjectMetadata({
|
|
1019
|
-
// libraryId: libraryId,
|
|
1020
|
-
// objectId: conf.objectId,
|
|
1021
|
-
// writeToken: edgeWriteToken
|
|
1022
|
-
// });
|
|
1023
|
-
//
|
|
1024
|
-
// // If a stream has never been started return state 'inactive'
|
|
1025
|
-
// if(edgeMeta.live_recording === undefined ||
|
|
1026
|
-
// edgeMeta.live_recording.recordings === undefined ||
|
|
1027
|
-
// edgeMeta.live_recording.recordings.recording_sequence === undefined) {
|
|
1028
|
-
// status.state = "no recordings";
|
|
1029
|
-
// return status;
|
|
1030
|
-
// }
|
|
1031
|
-
//
|
|
1032
|
-
// let recordings = edgeMeta.live_recording.recordings;
|
|
1033
|
-
// status.recording_period_sequence = recordings.recording_sequence;
|
|
1034
|
-
//
|
|
1035
|
-
// let sequence = recordings.recording_sequence;
|
|
1036
|
-
// if(period === undefined || period < 0 || period > sequence - 1) {
|
|
1037
|
-
// period = sequence - 1;
|
|
1038
|
-
// }
|
|
1039
|
-
//
|
|
1040
|
-
// console.log("Downloading stream", name, " period", period, " latest", sequence - 1);
|
|
1041
|
-
//
|
|
1042
|
-
// let recording = recordings.live_offering[period];
|
|
1043
|
-
// if(recording === undefined) {
|
|
1044
|
-
// console.log("ERROR - recording period not found: ", period);
|
|
1045
|
-
// }
|
|
1046
|
-
//
|
|
1047
|
-
// let dpath = "DOWNLOAD/" + edgeWriteToken + "." + period;
|
|
1048
|
-
// !fs.existsSync(dpath) && fs.mkdirSync(dpath, {recursive: true});
|
|
1049
|
-
//
|
|
1050
|
-
// let mts = ["audio", "video"];
|
|
1051
|
-
// for (let mi = 0; mi < mts.length; mi ++) {
|
|
1052
|
-
// let mt = mts[mi];
|
|
1053
|
-
// console.log("Downloading ", mt);
|
|
1054
|
-
// let mtpath = dpath + "/" + mt;
|
|
1055
|
-
// let partsfile = dpath + "/parts_" + mt + ".txt";
|
|
1056
|
-
// !fs.existsSync(mtpath) && fs.mkdirSync(mtpath);
|
|
1057
|
-
// var sources = recording.sources[mt];
|
|
1058
|
-
// for (let i = 0; i < sources.length - 1; i++) {
|
|
1059
|
-
// console.log(sources[i].hash);
|
|
1060
|
-
// let partHash = sources[i].hash;
|
|
1061
|
-
// let buf = await this.client.DownloadPart({
|
|
1062
|
-
// libraryId,
|
|
1063
|
-
// objectId: conf.objectId,
|
|
1064
|
-
// partHash,
|
|
1065
|
-
// format: "buffer",
|
|
1066
|
-
// chunked: false,
|
|
1067
|
-
// callback: ({bytesFinished, bytesTotal}) => {
|
|
1068
|
-
// console.log(" progress: ", bytesFinished + "/" + bytesTotal);
|
|
1069
|
-
// }
|
|
1070
|
-
// });
|
|
1071
|
-
//
|
|
1072
|
-
// let partfile = mtpath + "/" + partHash + ".mp4";
|
|
1073
|
-
// fs.appendFile(partfile, buf, (err) => {
|
|
1074
|
-
// if(err)
|
|
1075
|
-
// console.log(err);
|
|
1076
|
-
// });
|
|
1077
|
-
// fs.appendFile(partsfile, "file '" + mt + "/" + partHash + ".mp4'\n", (err) => {
|
|
1078
|
-
// if(err)
|
|
1079
|
-
// console.log(err);
|
|
1080
|
-
// });
|
|
1081
|
-
// }
|
|
1082
|
-
//
|
|
1083
|
-
// // Concatenate parts into one mp4
|
|
1084
|
-
// let cmd = "ffmpeg -f concat -safe 0 -i " + partsfile + " -c copy " + dpath + "/" + mt + ".mp4";
|
|
1085
|
-
// console.log("Running", cmd);
|
|
1086
|
-
// execSync(cmd);
|
|
1087
|
-
// }
|
|
1088
|
-
//
|
|
1089
|
-
// // Create final mp4 file
|
|
1090
|
-
// let f = dpath + "/download.mp4";
|
|
1091
|
-
// let cmd = "ffmpeg -i " + dpath + "/video.mp4" + " -i " + dpath + "/audio.mp4" + " -map 0:v:0 -map 1:a:0 -c copy -shortest " + f;
|
|
1092
|
-
// console.log("Running", cmd);
|
|
1093
|
-
// execSync(cmd);
|
|
1094
|
-
//
|
|
1095
|
-
// status.file = f;
|
|
1096
|
-
// status.state = "completed";
|
|
1097
|
-
// } catch(e) {
|
|
1098
|
-
// console.log("Download failed", e);
|
|
1099
|
-
// throw e;
|
|
1100
|
-
// }
|
|
1101
|
-
//
|
|
1102
|
-
// return status;
|
|
1103
|
-
// }
|
|
1104
|
-
|
|
1105
1298
|
/**
|
|
1106
1299
|
* Configure the stream
|
|
1107
1300
|
*
|
|
1108
1301
|
* @methodGroup Live Stream
|
|
1109
1302
|
* @namedParams
|
|
1110
|
-
* @param {string} name -
|
|
1111
|
-
* @param {
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* - 'stop'
|
|
1303
|
+
* @param {string} name - Object ID or name of the live stream object
|
|
1304
|
+
* @param {Object=} customSettings - Additional options to customize configuration settings
|
|
1305
|
+
* - audioBitrate
|
|
1306
|
+
* - audioIndex
|
|
1307
|
+
* - partTtl
|
|
1308
|
+
* - channelLayout
|
|
1117
1309
|
*
|
|
1118
|
-
* @return {Object} - The status response for the stream
|
|
1310
|
+
* @return {Promise<Object>} - The status response for the stream
|
|
1119
1311
|
*
|
|
1120
1312
|
*/
|
|
1121
|
-
exports.StreamConfig = async function({name, customSettings}) {
|
|
1313
|
+
exports.StreamConfig = async function({name, customSettings={}}) {
|
|
1122
1314
|
let conf = await this.LoadConf({name});
|
|
1123
1315
|
let status = {name};
|
|
1124
1316
|
|
|
@@ -1235,322 +1427,3 @@ exports.StreamConfig = async function({name, customSettings}) {
|
|
|
1235
1427
|
|
|
1236
1428
|
return status;
|
|
1237
1429
|
};
|
|
1238
|
-
|
|
1239
|
-
// const ChannelStatus = async ({client, name}) => {
|
|
1240
|
-
//
|
|
1241
|
-
// let status = {name: name};
|
|
1242
|
-
//
|
|
1243
|
-
// const conf = channels[name];
|
|
1244
|
-
// if(conf === null) {
|
|
1245
|
-
// console.log("Bad name: ", name);
|
|
1246
|
-
// return;
|
|
1247
|
-
// }
|
|
1248
|
-
//
|
|
1249
|
-
// try {
|
|
1250
|
-
//
|
|
1251
|
-
// let meta = await client.ContentObjectMetadata({
|
|
1252
|
-
// libraryId: conf.libraryId,
|
|
1253
|
-
// objectId: conf.objectId
|
|
1254
|
-
// });
|
|
1255
|
-
//
|
|
1256
|
-
// status.channel_title = meta.public.asset_metadata.title;
|
|
1257
|
-
// let source = meta.channel.offerings.default.items[0].source["/"];
|
|
1258
|
-
// let hash = source.split("/")[2];
|
|
1259
|
-
// status.stream_hash = hash;
|
|
1260
|
-
// latestHash = await client.LatestVersionHash({
|
|
1261
|
-
// versionHash: hash
|
|
1262
|
-
// });
|
|
1263
|
-
// status.stream_latest_hash = latestHash;
|
|
1264
|
-
//
|
|
1265
|
-
// if(hash != latestHash) {
|
|
1266
|
-
// status.warnings = ["Stream version is not the latest"];
|
|
1267
|
-
// }
|
|
1268
|
-
//
|
|
1269
|
-
// let channelFormatsUrl = await client.FabricUrl({
|
|
1270
|
-
// libraryId: conf.libraryId,
|
|
1271
|
-
// objectId: conf.objectId,
|
|
1272
|
-
// rep: "channel/options.json"
|
|
1273
|
-
// });
|
|
1274
|
-
//
|
|
1275
|
-
// try {
|
|
1276
|
-
// let offerings = await got(channelFormatsUrl);
|
|
1277
|
-
// status.offerings = JSON.parse(offerings.body);
|
|
1278
|
-
// } catch(error) {
|
|
1279
|
-
// console.log(error);
|
|
1280
|
-
// status.offerings_error = "Failed to retrieve channel offerings";
|
|
1281
|
-
// }
|
|
1282
|
-
//
|
|
1283
|
-
// status.playout = await ChannelPlayout({client, libraryId: conf.libraryId, objectId: conf.objectId});
|
|
1284
|
-
//
|
|
1285
|
-
// } catch(error) {
|
|
1286
|
-
// console.error(error);
|
|
1287
|
-
// }
|
|
1288
|
-
//
|
|
1289
|
-
// return status;
|
|
1290
|
-
// };
|
|
1291
|
-
|
|
1292
|
-
/*
|
|
1293
|
-
* Performs client-side playout operations - open the channel, read offerings,
|
|
1294
|
-
* retrieve playlist and one video init segment.
|
|
1295
|
-
*/
|
|
1296
|
-
// const ChannelPlayout = async({client, libraryId, objectId}) => {
|
|
1297
|
-
//
|
|
1298
|
-
// let playout = {};
|
|
1299
|
-
//
|
|
1300
|
-
// const offerings = await client.AvailableOfferings({
|
|
1301
|
-
// libraryId,
|
|
1302
|
-
// objectId,
|
|
1303
|
-
// handler: "channel",
|
|
1304
|
-
// linkPath: "/public/asset_metadata/offerings"
|
|
1305
|
-
// });
|
|
1306
|
-
//
|
|
1307
|
-
// // Choosing offering 'default'
|
|
1308
|
-
// let offering = offerings.default;
|
|
1309
|
-
//
|
|
1310
|
-
// const playoutOptions = await client.PlayoutOptions({
|
|
1311
|
-
// libraryId,
|
|
1312
|
-
// objectId,
|
|
1313
|
-
// offeringURI: offering.uri
|
|
1314
|
-
// });
|
|
1315
|
-
//
|
|
1316
|
-
// // Retrieve master playlist
|
|
1317
|
-
// let masterPlaylistUrl = playoutOptions["hls"]["playoutMethods"]["fairplay"]["playoutUrl"];
|
|
1318
|
-
// playout.master_playlist_url = masterPlaylistUrl;
|
|
1319
|
-
// try {
|
|
1320
|
-
// //let masterPlaylist = await got(masterPlaylistUrl);
|
|
1321
|
-
// playout.master_playlist = "success";
|
|
1322
|
-
// } catch(error) {
|
|
1323
|
-
// playout.master_playlist = "fail";
|
|
1324
|
-
// }
|
|
1325
|
-
//
|
|
1326
|
-
// let url = new URL(masterPlaylistUrl);
|
|
1327
|
-
// let p = url.pathname.split("/");
|
|
1328
|
-
//
|
|
1329
|
-
// // Retrieve media playlist
|
|
1330
|
-
// p[p.length - 1] = "video/720@14000000/live.m3u8";
|
|
1331
|
-
// let pathMediaPlaylist = p.join("/");
|
|
1332
|
-
// url.pathname = pathMediaPlaylist;
|
|
1333
|
-
// let mediaPlaylistUrl = url.toString();
|
|
1334
|
-
// playout.media_playlist_url = mediaPlaylistUrl;
|
|
1335
|
-
// let mediaPlaylist;
|
|
1336
|
-
// try {
|
|
1337
|
-
// mediaPlaylist = await got(mediaPlaylistUrl);
|
|
1338
|
-
// playout.media_playlist = "success";
|
|
1339
|
-
// } catch(error) {
|
|
1340
|
-
// playout.media_playlist = "fail";
|
|
1341
|
-
// }
|
|
1342
|
-
//
|
|
1343
|
-
// // Retrieve init segment
|
|
1344
|
-
// var regex = new RegExp("^#EXT-X-MAP:URI=\"init.m4s.(.*)\"$", "m");
|
|
1345
|
-
// var match = regex.exec(mediaPlaylist.body);
|
|
1346
|
-
// let initQueryParams;
|
|
1347
|
-
// if(match) {
|
|
1348
|
-
// initQueryParams = match[1];
|
|
1349
|
-
// }
|
|
1350
|
-
//
|
|
1351
|
-
// p[p.length - 1] = "video/720@14000000/init.m4s";
|
|
1352
|
-
// let pathInit = p.join("/");
|
|
1353
|
-
// url.pathname = pathInit;
|
|
1354
|
-
// url.search=initQueryParams;
|
|
1355
|
-
// let initUrl = url.toString();
|
|
1356
|
-
// playout.init_segment_url = initUrl;
|
|
1357
|
-
// /*
|
|
1358
|
-
// try {
|
|
1359
|
-
// let initSegment = await got(initUrl);
|
|
1360
|
-
// playout.init_segment = "success"
|
|
1361
|
-
// } catch(error) {
|
|
1362
|
-
// playout.init_segment = "fail";
|
|
1363
|
-
// }
|
|
1364
|
-
// */
|
|
1365
|
-
// return playout;
|
|
1366
|
-
// };
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
// const Summary = async ({client}) => {
|
|
1370
|
-
//
|
|
1371
|
-
// let summary = {};
|
|
1372
|
-
//
|
|
1373
|
-
// try {
|
|
1374
|
-
// for (const [key] of Object.entries(streams)) {
|
|
1375
|
-
// conf = streams[key];
|
|
1376
|
-
// summary[key] = await Status({client, name: key, stopLro: false});
|
|
1377
|
-
// }
|
|
1378
|
-
//
|
|
1379
|
-
// } catch(error) {
|
|
1380
|
-
// console.error(error);
|
|
1381
|
-
// }
|
|
1382
|
-
// return summary;
|
|
1383
|
-
// };
|
|
1384
|
-
|
|
1385
|
-
// const ChannelSummary = async ({client}) => {
|
|
1386
|
-
//
|
|
1387
|
-
// let summary = {};
|
|
1388
|
-
//
|
|
1389
|
-
// try {
|
|
1390
|
-
// for (const [key] of Object.entries(channels)) {
|
|
1391
|
-
// conf = channels[key];
|
|
1392
|
-
// summary[key] = await ChannelStatus({client, name: key});
|
|
1393
|
-
// }
|
|
1394
|
-
//
|
|
1395
|
-
// } catch(error) {
|
|
1396
|
-
// console.error(error);
|
|
1397
|
-
// }
|
|
1398
|
-
// return summary;
|
|
1399
|
-
// };
|
|
1400
|
-
|
|
1401
|
-
// const ConfigStreamRebroadcast = async () => {
|
|
1402
|
-
//
|
|
1403
|
-
// const t = 1619850660;
|
|
1404
|
-
//
|
|
1405
|
-
// try {
|
|
1406
|
-
// let client;
|
|
1407
|
-
// if(conf.clientConf.configUrl) {
|
|
1408
|
-
// client = await ElvClient.FromConfigurationUrl({
|
|
1409
|
-
// configUrl: conf.clientConf.configUrl
|
|
1410
|
-
// });
|
|
1411
|
-
// } else {
|
|
1412
|
-
// client = new ElvClient(conf.clientConf);
|
|
1413
|
-
// }
|
|
1414
|
-
// const wallet = client.GenerateWallet();
|
|
1415
|
-
// const signer = wallet.AddAccount({ privateKey: conf.signerPrivateKey });
|
|
1416
|
-
// client.SetSigner({ signer });
|
|
1417
|
-
// const fabURI = client.fabricURIs[0];
|
|
1418
|
-
// console.log("Fabric URI: " + fabURI);
|
|
1419
|
-
// const ethURI = client.ethereumURIs[0];
|
|
1420
|
-
// console.log("Ethereum URI: " + ethURI);
|
|
1421
|
-
//
|
|
1422
|
-
// client.ToggleLogging(false);
|
|
1423
|
-
//
|
|
1424
|
-
// let mainMeta = await client.ContentObjectMetadata({
|
|
1425
|
-
// libraryId: conf.libraryId,
|
|
1426
|
-
// objectId: conf.objectId
|
|
1427
|
-
// });
|
|
1428
|
-
// console.log("Main meta:", mainMeta);
|
|
1429
|
-
//
|
|
1430
|
-
// edgeWriteToken = mainMeta.edge_write_token;
|
|
1431
|
-
// console.log("Edge: ", edgeWriteToken);
|
|
1432
|
-
//
|
|
1433
|
-
// let edgeMeta = await client.ContentObjectMetadata({
|
|
1434
|
-
// libraryId: conf.libraryId,
|
|
1435
|
-
// objectId: conf.objectId,
|
|
1436
|
-
// writeToken: edgeWriteToken
|
|
1437
|
-
// });
|
|
1438
|
-
// console.log("Edge meta:", edgeMeta);
|
|
1439
|
-
//
|
|
1440
|
-
// //console.log("CONFIG: ", edgeMeta.live_recording_parameters.live_playout_config);
|
|
1441
|
-
// console.log("recording_start_time: ", edgeMeta.recording_start_time);
|
|
1442
|
-
// console.log("recording_stop_time: ", edgeMeta.recording_stop_time);
|
|
1443
|
-
//
|
|
1444
|
-
// // Set rebroadcast start
|
|
1445
|
-
// edgeMeta.live_recording_parameters.live_playout_config.rebroadcast_start_time_sec_epoch = t;
|
|
1446
|
-
//
|
|
1447
|
-
// if(PRINT_DEBUG) console.log("MergeMetadata", conf.libraryId, conf.objectId, writeToken);
|
|
1448
|
-
// await client.MergeMetadata({
|
|
1449
|
-
// libraryId: conf.libraryId,
|
|
1450
|
-
// objectId: conf.objectId,
|
|
1451
|
-
// writeToken: edgeWriteToken,
|
|
1452
|
-
// metadata: {
|
|
1453
|
-
// "live_recording_parameters": {
|
|
1454
|
-
// "live_playout_config" : edgeMeta.live_recording_parameters.live_playout_config
|
|
1455
|
-
// }
|
|
1456
|
-
// }
|
|
1457
|
-
// });
|
|
1458
|
-
//
|
|
1459
|
-
// } catch(error) {
|
|
1460
|
-
// console.error(error);
|
|
1461
|
-
// }
|
|
1462
|
-
// };
|
|
1463
|
-
|
|
1464
|
-
// async function EnsureAll() {
|
|
1465
|
-
// client = await StatusPrep({name: null});
|
|
1466
|
-
// let summary = await Summary({client});
|
|
1467
|
-
//
|
|
1468
|
-
// var res = {
|
|
1469
|
-
// running: 0,
|
|
1470
|
-
// stalled: 0,
|
|
1471
|
-
// terminated: 0
|
|
1472
|
-
// };
|
|
1473
|
-
//
|
|
1474
|
-
// try {
|
|
1475
|
-
// for (const [key, value] of Object.entries(summary)) {
|
|
1476
|
-
// if(value.state === "stalled") {
|
|
1477
|
-
// console.log("Stream stalled: ", key, " - restarting");
|
|
1478
|
-
// console.log("todo ...");
|
|
1479
|
-
// }
|
|
1480
|
-
// res[value.state] = res[value.state] + 1;
|
|
1481
|
-
// }
|
|
1482
|
-
// } catch(error) {
|
|
1483
|
-
// console.error(error);
|
|
1484
|
-
// }
|
|
1485
|
-
//
|
|
1486
|
-
// return res;
|
|
1487
|
-
// }
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
/*
|
|
1491
|
-
* Original Run() function - kept for reference
|
|
1492
|
-
*/
|
|
1493
|
-
// async function Run() {
|
|
1494
|
-
//
|
|
1495
|
-
// var client;
|
|
1496
|
-
//
|
|
1497
|
-
// switch (command) {
|
|
1498
|
-
//
|
|
1499
|
-
// case "start":
|
|
1500
|
-
// StartStream({name});
|
|
1501
|
-
// break;
|
|
1502
|
-
//
|
|
1503
|
-
// case "status":
|
|
1504
|
-
// client = await StatusPrep({name});
|
|
1505
|
-
// let status = await Status({client, name, stopLro: false});
|
|
1506
|
-
// console.log(JSON.stringify(status, null, 4));
|
|
1507
|
-
// break;
|
|
1508
|
-
//
|
|
1509
|
-
// case "stop":
|
|
1510
|
-
// client = await UpdatePrep({name});
|
|
1511
|
-
// Status({client, name, stopLro: true});
|
|
1512
|
-
// break;
|
|
1513
|
-
//
|
|
1514
|
-
// case "summary":
|
|
1515
|
-
// client = await StatusPrep({name: null});
|
|
1516
|
-
// let summary = await Summary({client});
|
|
1517
|
-
// console.log(JSON.stringify(summary, null, 4));
|
|
1518
|
-
// break;
|
|
1519
|
-
//
|
|
1520
|
-
// case "init": // Set up DRM
|
|
1521
|
-
// SetOfferingAndDRM();
|
|
1522
|
-
// break;
|
|
1523
|
-
//
|
|
1524
|
-
// case "reset": // Stop and start LRO recording (same edge write token)
|
|
1525
|
-
// client = await StatusPrep({name});
|
|
1526
|
-
// let reset = await Reset({client, name, stopLro: false});
|
|
1527
|
-
// console.log(JSON.stringify(reset, null, 4));
|
|
1528
|
-
// break;
|
|
1529
|
-
//
|
|
1530
|
-
// case "channel":
|
|
1531
|
-
// client = await StatusPrep({name});
|
|
1532
|
-
// let channelStatus = await ChannelStatus({client, name});
|
|
1533
|
-
// console.log(JSON.stringify(channelStatus, null, 4));
|
|
1534
|
-
// break;
|
|
1535
|
-
//
|
|
1536
|
-
// case "channel_summary":
|
|
1537
|
-
// client = await StatusPrep({name});
|
|
1538
|
-
// let channelSummary = await ChannelSummary({client, name});
|
|
1539
|
-
// console.log(JSON.stringify(channelSummary, null, 4));
|
|
1540
|
-
// break;
|
|
1541
|
-
//
|
|
1542
|
-
// case "ensure_all": // Check all and restart stalled
|
|
1543
|
-
// let ensureSummary = await EnsureAll();
|
|
1544
|
-
// console.log(JSON.stringify(ensureSummary, null, 4));
|
|
1545
|
-
// break;
|
|
1546
|
-
//
|
|
1547
|
-
// case "future_use_config":
|
|
1548
|
-
// ConfigStreamRebroadcast();
|
|
1549
|
-
// break;
|
|
1550
|
-
//
|
|
1551
|
-
// default:
|
|
1552
|
-
// console.log("Bad command: ", command);
|
|
1553
|
-
// break;
|
|
1554
|
-
//
|
|
1555
|
-
// }
|
|
1556
|
-
// }
|