@eluvio/elv-client-js 4.0.47 → 4.0.49
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 +10 -10
- package/dist/ElvClient-node-min.js +5 -5
- package/dist/ElvFrameClient-min.js +5 -5
- package/dist/ElvPermissionsClient-min.js +9 -9
- package/dist/ElvWalletClient-min.js +4 -4
- package/dist/ElvWalletClient-node-min.js +8 -8
- package/dist/src/FrameClient.js +1 -1
- package/dist/src/Validation.js +1 -1
- package/dist/src/client/ContentAccess.js +405 -574
- package/dist/src/client/ContentManagement.js +1 -1
- package/package.json +1 -1
- package/src/ElvClient.js +81 -0
- package/src/FrameClient.js +6 -0
- package/src/HttpClient.js +12 -1
- package/src/UserProfileClient.js +13 -0
- package/src/Utils.js +11 -0
- package/src/client/LiveConf.js +351 -0
- package/src/client/LiveStream.js +1546 -0
|
@@ -0,0 +1,1546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Methods for Live Stream creation and management
|
|
3
|
+
*
|
|
4
|
+
* @module ElvClient/LiveStream
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const {LiveConf} = require("./LiveConf");
|
|
8
|
+
const path = require("path");
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
|
|
12
|
+
const HttpClient = require("../HttpClient");
|
|
13
|
+
//
|
|
14
|
+
// const {
|
|
15
|
+
// ValidateLibrary,
|
|
16
|
+
// ValidateVersion,
|
|
17
|
+
// ValidateParameters
|
|
18
|
+
// } = require("../Validation");
|
|
19
|
+
|
|
20
|
+
const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
|
|
21
|
+
const tok = await client.authClient.AuthorizationToken({libraryId, objectId,
|
|
22
|
+
versionHash, channelAuth: false, noCache: true,
|
|
23
|
+
noAuth: true});
|
|
24
|
+
|
|
25
|
+
return tok;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Retrieve the status of the current live stream session
|
|
34
|
+
*
|
|
35
|
+
* @methodGroup Live Stream
|
|
36
|
+
* @namedParams
|
|
37
|
+
* @param {string} name -
|
|
38
|
+
* @param {boolean} stopLro -
|
|
39
|
+
* @param {boolean} showParams -
|
|
40
|
+
* States:
|
|
41
|
+
* unconfigured - no live_recording_config
|
|
42
|
+
* uninitialized - no live_recording config generated
|
|
43
|
+
* inactive - live_recording config initialized but no 'edge write token'
|
|
44
|
+
* stopped - edge-write-token but not started
|
|
45
|
+
* starting - LRO running but no source data yet
|
|
46
|
+
* running - stream is running and producing output
|
|
47
|
+
* stalled - LRO running but no source data (so not producing output)
|
|
48
|
+
*
|
|
49
|
+
* @return {Object} - The status response for the object, as well as logs, warnings and errors from the master initialization
|
|
50
|
+
*/
|
|
51
|
+
exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
|
|
52
|
+
let conf = await this.LoadConf({name});
|
|
53
|
+
|
|
54
|
+
let status = {name: name};
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
|
|
58
|
+
let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
|
|
59
|
+
status.library_id = libraryId;
|
|
60
|
+
status.object_id = conf.objectId;
|
|
61
|
+
|
|
62
|
+
let mainMeta = await this.ContentObjectMetadata({
|
|
63
|
+
libraryId: libraryId,
|
|
64
|
+
objectId: conf.objectId,
|
|
65
|
+
select: [
|
|
66
|
+
"live_recording_config",
|
|
67
|
+
"live_recording"
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if(mainMeta.live_recording_config == undefined || mainMeta.live_recording_config.url == undefined) {
|
|
72
|
+
status.state = "unconfigured";
|
|
73
|
+
return status;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if(mainMeta.live_recording == undefined || mainMeta.live_recording.fabric_config == undefined ||
|
|
77
|
+
mainMeta.live_recording.playout_config == undefined || mainMeta.live_recording.recording_config == undefined) {
|
|
78
|
+
status.state = "uninitialized";
|
|
79
|
+
return status;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let fabURI = mainMeta.live_recording.fabric_config.ingress_node_api;
|
|
83
|
+
if(fabURI === undefined) {
|
|
84
|
+
console.log("bad fabric config - missing ingress node API");
|
|
85
|
+
status.state = "uninitialized";
|
|
86
|
+
return status;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Support both hostname and URL ingress_node_api
|
|
90
|
+
if(!fabURI.startsWith("http")) {
|
|
91
|
+
// Assume https
|
|
92
|
+
fabURI = "https://" + fabURI;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
status.fabric_api = fabURI;
|
|
96
|
+
status.url = mainMeta.live_recording.recording_config.recording_params.origin_url;
|
|
97
|
+
|
|
98
|
+
let edgeWriteToken = mainMeta.live_recording.fabric_config.edge_write_token;
|
|
99
|
+
if(edgeWriteToken == undefined) {
|
|
100
|
+
status.state = "inactive";
|
|
101
|
+
return status;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.RecordWriteToken({writeToken: edgeWriteToken, fabricNodeUrl: fabURI});
|
|
105
|
+
|
|
106
|
+
status.edge_write_token = edgeWriteToken;
|
|
107
|
+
status.stream_id = edgeWriteToken; // By convention the stream ID is its write token
|
|
108
|
+
let edgeMeta = await this.ContentObjectMetadata({
|
|
109
|
+
libraryId: libraryId,
|
|
110
|
+
objectId: conf.objectId,
|
|
111
|
+
writeToken: edgeWriteToken,
|
|
112
|
+
select: [
|
|
113
|
+
"live_recording"
|
|
114
|
+
]
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// If a stream has never been started return state 'inactive'
|
|
118
|
+
if(edgeMeta.live_recording === undefined ||
|
|
119
|
+
edgeMeta.live_recording.recordings === undefined ||
|
|
120
|
+
edgeMeta.live_recording.recordings.recording_sequence === undefined) {
|
|
121
|
+
status.state = "stopped";
|
|
122
|
+
return status;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let recordings = edgeMeta.live_recording.recordings;
|
|
126
|
+
status.recording_period_sequence = recordings.recording_sequence;
|
|
127
|
+
|
|
128
|
+
let sequence = recordings.recording_sequence;
|
|
129
|
+
let period = recordings.live_offering[sequence - 1];
|
|
130
|
+
|
|
131
|
+
let tlro = period.live_recording_handle;
|
|
132
|
+
status.tlro = tlro;
|
|
133
|
+
|
|
134
|
+
let sinceLastFinalize = Math.floor(new Date().getTime() / 1000) -
|
|
135
|
+
period.video_finalized_parts_info.last_finalization_time /1000000;
|
|
136
|
+
|
|
137
|
+
let recording_period = {
|
|
138
|
+
activation_time_epoch_sec: period.recording_start_time_epoch_sec,
|
|
139
|
+
start_time_epoch_sec: period.start_time_epoch_sec,
|
|
140
|
+
start_time_text: new Date(period.start_time_epoch_sec * 1000).toLocaleString(),
|
|
141
|
+
end_time_epoch_sec: period.end_time_epoch_sec,
|
|
142
|
+
end_time_text: period.end_time_epoch_sec === 0 ? null : new Date(period.end_time_epoch_sec * 1000).toLocaleString(),
|
|
143
|
+
video_parts: period.video_finalized_parts_info.n_parts,
|
|
144
|
+
video_last_part_finalized_epoch_sec: period.video_finalized_parts_info.last_finalization_time / 1000000,
|
|
145
|
+
video_since_last_finalize_sec : sinceLastFinalize
|
|
146
|
+
};
|
|
147
|
+
status.recording_period = recording_period;
|
|
148
|
+
|
|
149
|
+
status.lro_status_url = await this.FabricUrl({
|
|
150
|
+
libraryId: libraryId,
|
|
151
|
+
objectId: conf.objectId,
|
|
152
|
+
writeToken: edgeWriteToken,
|
|
153
|
+
call: "live/status/" + tlro
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
status.insertions = [];
|
|
157
|
+
if((edgeMeta.live_recording.playout_config.interleaves != undefined) &&
|
|
158
|
+
(edgeMeta.live_recording.playout_config.interleaves[sequence] != undefined)) {
|
|
159
|
+
let insertions = edgeMeta.live_recording.playout_config.interleaves[sequence];
|
|
160
|
+
for(let i = 0; i < insertions.length; i ++) {
|
|
161
|
+
let insertionTimeSinceEpoch = recording_period.start_time_epoch_sec + insertions[i].insertion_time;
|
|
162
|
+
status.insertions[i] = {
|
|
163
|
+
insertion_time_since_start: insertions[i].insertion_time,
|
|
164
|
+
insertion_time: new Date(insertionTimeSinceEpoch * 1000).toISOString(),
|
|
165
|
+
insertion_time_local: new Date(insertionTimeSinceEpoch * 1000).toLocaleString(),
|
|
166
|
+
target: insertions[i].playout};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if(showParams) {
|
|
171
|
+
status.recording_paramse = edgeMeta.live_recording.recording_config.recording_params;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let state = "stopped";
|
|
175
|
+
let lroStatus = "";
|
|
176
|
+
try {
|
|
177
|
+
lroStatus = await this.utils.ResponseToJson(
|
|
178
|
+
await HttpClient.Fetch(status.lro_status_url)
|
|
179
|
+
);
|
|
180
|
+
state = lroStatus.state;
|
|
181
|
+
} catch(error) {
|
|
182
|
+
console.log("LRO Status (failed): ", error.response.statusCode);
|
|
183
|
+
status.state = "stopped";
|
|
184
|
+
status.error = error.response;
|
|
185
|
+
return status;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Convert LRO 'state' to desired 'state'
|
|
189
|
+
if(state === "running" && period.video_finalized_parts_info.last_finalization_time === 0) {
|
|
190
|
+
state = "starting";
|
|
191
|
+
} else if(state === "running" && sinceLastFinalize > 32.9) {
|
|
192
|
+
state = "stalled";
|
|
193
|
+
} else if(state == "terminated") {
|
|
194
|
+
state = "stopped";
|
|
195
|
+
}
|
|
196
|
+
status.state = state;
|
|
197
|
+
|
|
198
|
+
if((state === "running" || state === "stalled" || state === "starting") && stopLro) {
|
|
199
|
+
lroStopUrl = await this.FabricUrl({
|
|
200
|
+
libraryId: libraryId,
|
|
201
|
+
objectId: conf.objectId,
|
|
202
|
+
writeToken: edgeWriteToken,
|
|
203
|
+
call: "live/stop/" + tlro
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
await this.utils.ResponseToJson(
|
|
208
|
+
await HttpClient.Fetch(lroStopUrl)
|
|
209
|
+
);
|
|
210
|
+
console.log("LRO Stop: ", lroStatus.body);
|
|
211
|
+
} catch(error) {
|
|
212
|
+
console.log("LRO Stop (failed): ", error.response.statusCode);
|
|
213
|
+
}
|
|
214
|
+
state = "stopped";
|
|
215
|
+
status.state = state;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if(state === "running") {
|
|
219
|
+
let playout_urls = {};
|
|
220
|
+
let objectId = conf.objectId;
|
|
221
|
+
let playout_options = await this.PlayoutOptions({
|
|
222
|
+
objectId,
|
|
223
|
+
linkPath: "public/asset_metadata/sources/default"
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
let hls_clear_enabled = (
|
|
227
|
+
playout_options &&
|
|
228
|
+
playout_options.hls &&
|
|
229
|
+
playout_options.hls.playoutMethods &&
|
|
230
|
+
playout_options.hls.playoutMethods.clear !== undefined
|
|
231
|
+
);
|
|
232
|
+
if(hls_clear_enabled) {
|
|
233
|
+
playout_urls.hls_clear = await this.FabricUrl({
|
|
234
|
+
libraryId: libraryId,
|
|
235
|
+
objectId: objectId,
|
|
236
|
+
rep: "playout/default/hls-clear/playlist.m3u8",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let hls_aes128_enabled = (
|
|
241
|
+
playout_options &&
|
|
242
|
+
playout_options.hls &&
|
|
243
|
+
playout_options.hls.playoutMethods &&
|
|
244
|
+
playout_options.hls.playoutMethods["aes-128"] !== undefined
|
|
245
|
+
);
|
|
246
|
+
if(hls_aes128_enabled) {
|
|
247
|
+
playout_urls.hls_aes128 = await this.FabricUrl({
|
|
248
|
+
libraryId: libraryId,
|
|
249
|
+
objectId: objectId,
|
|
250
|
+
rep: "playout/default/hls-aes128/playlist.m3u8",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let hls_sample_aes_enabled = (
|
|
255
|
+
playout_options &&
|
|
256
|
+
playout_options.hls &&
|
|
257
|
+
playout_options.hls.playoutMethods &&
|
|
258
|
+
playout_options.hls.playoutMethods["sample-aes"] !== undefined
|
|
259
|
+
);
|
|
260
|
+
if(hls_sample_aes_enabled) {
|
|
261
|
+
playout_urls.hls_sample_aes = await this.FabricUrl({
|
|
262
|
+
libraryId: libraryId,
|
|
263
|
+
objectId: objectId,
|
|
264
|
+
rep: "playout/default/hls-sample-aes/playlist.m3u8",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const networkInfo = await this.NetworkInfo();
|
|
269
|
+
let token = await this.authClient.AuthorizationToken({
|
|
270
|
+
libraryId,
|
|
271
|
+
objectId,
|
|
272
|
+
channelAuth: false,
|
|
273
|
+
noCache: true,
|
|
274
|
+
noAuth: true
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
let embed_net = "main";
|
|
278
|
+
if(networkInfo.name.includes("demo")) {
|
|
279
|
+
embed_net = "demo";
|
|
280
|
+
}
|
|
281
|
+
let embed_url = `https://embed.v3.contentfabric.io/?net=${embed_net}&p&ct=h&oid=${conf.objectId}&mt=v&ath=${token}`;
|
|
282
|
+
playout_urls.embed_url = embed_url;
|
|
283
|
+
|
|
284
|
+
status.playout_urls = playout_urls;
|
|
285
|
+
}
|
|
286
|
+
} catch(error) {
|
|
287
|
+
console.error(error);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return status;
|
|
291
|
+
};
|
|
292
|
+
|
|
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
|
+
/**
|
|
311
|
+
* Create a new edge write token
|
|
312
|
+
*
|
|
313
|
+
* @methodGroup Live Stream
|
|
314
|
+
* @namedParams
|
|
315
|
+
* @param {string} name -
|
|
316
|
+
* @param {boolean} start -
|
|
317
|
+
*
|
|
318
|
+
* @return {Object} - The status response for the object
|
|
319
|
+
*
|
|
320
|
+
*/
|
|
321
|
+
exports.StreamCreate = async function({name, start = false}) {
|
|
322
|
+
|
|
323
|
+
let status = await this.StreamStatus({name});
|
|
324
|
+
if(status.state !== "inactive" && status.state !== "terminated" && status.state !== "stopped") {
|
|
325
|
+
return {
|
|
326
|
+
state: status.state,
|
|
327
|
+
error: "stream still active - must terminate first"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let objectId = status.object_id;
|
|
332
|
+
console.log("START: ", name, "start", start);
|
|
333
|
+
|
|
334
|
+
let libraryId = await this.ContentObjectLibraryId({objectId: objectId});
|
|
335
|
+
|
|
336
|
+
// Read live recording parameters - determine ingest node
|
|
337
|
+
let liveRecording = await this.ContentObjectMetadata({
|
|
338
|
+
libraryId: libraryId,
|
|
339
|
+
objectId: objectId,
|
|
340
|
+
metadataSubtree: "/live_recording"
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
let fabURI = liveRecording.fabric_config.ingress_node_api;
|
|
344
|
+
// Support both hostname and URL ingress_node_api
|
|
345
|
+
if(!fabURI.startsWith("http")) {
|
|
346
|
+
// Assume https
|
|
347
|
+
fabURI = "https://" + fabURI;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.SetNodes({fabricURIs: [fabURI]});
|
|
351
|
+
|
|
352
|
+
console.log("Node URI", fabURI, "ID", liveRecording.fabric_config.ingress_node_id);
|
|
353
|
+
|
|
354
|
+
let response = await this.EditContentObject({
|
|
355
|
+
libraryId: libraryId,
|
|
356
|
+
objectId: objectId
|
|
357
|
+
});
|
|
358
|
+
const edgeToken = response.write_token;
|
|
359
|
+
console.log("Edge token:", edgeToken);
|
|
360
|
+
|
|
361
|
+
/*
|
|
362
|
+
* Set the metadata, including the edge token.
|
|
363
|
+
*/
|
|
364
|
+
response = await this.EditContentObject({
|
|
365
|
+
libraryId: libraryId,
|
|
366
|
+
objectId: objectId
|
|
367
|
+
});
|
|
368
|
+
let writeToken = response.write_token;
|
|
369
|
+
|
|
370
|
+
this.Log("Merging metadata: ", libraryId, objectId, writeToken);
|
|
371
|
+
await this.MergeMetadata({
|
|
372
|
+
libraryId: libraryId,
|
|
373
|
+
objectId: objectId,
|
|
374
|
+
writeToken: writeToken,
|
|
375
|
+
metadata: {
|
|
376
|
+
live_recording: {
|
|
377
|
+
status: {
|
|
378
|
+
edge_write_token: edgeToken,
|
|
379
|
+
state: "active" // indicates there is an active session (set to 'closed' when done)
|
|
380
|
+
},
|
|
381
|
+
fabric_config: {
|
|
382
|
+
edge_write_token: edgeToken
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
this.Log("Finalizing content draft: ", libraryId, objectId, writeToken);
|
|
389
|
+
response = await this.FinalizeContentObject({
|
|
390
|
+
libraryId: libraryId,
|
|
391
|
+
objectId: objectId,
|
|
392
|
+
writeToken: writeToken,
|
|
393
|
+
commitMessage: "Create stream edge write token " + edgeToken
|
|
394
|
+
});
|
|
395
|
+
const objectHash = response.hash;
|
|
396
|
+
this.Log("Finalized object: ", objectHash);
|
|
397
|
+
|
|
398
|
+
status = {
|
|
399
|
+
object_id: objectId,
|
|
400
|
+
hash: objectHash,
|
|
401
|
+
library_id: libraryId,
|
|
402
|
+
stream_id: edgeToken,
|
|
403
|
+
edge_write_token: edgeToken,
|
|
404
|
+
fabric_api: fabURI,
|
|
405
|
+
state: "stopped"
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
if(start) {
|
|
409
|
+
status = this.StreamStartOrStopOrReset({name, op: start});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return status;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Start, stop or reset a stream within the current session (current edge write token)
|
|
417
|
+
*
|
|
418
|
+
* @methodGroup Live Stream
|
|
419
|
+
* @namedParams
|
|
420
|
+
* @param {string} name -
|
|
421
|
+
* @param {string=} op - The operation to perform. Possible values:
|
|
422
|
+
* 'start'
|
|
423
|
+
* 'reset' - Stops current LRO recording and starts a new one. Does
|
|
424
|
+
* not create a new edge write token (just creates a new recording
|
|
425
|
+
* period in the existing edge write token)
|
|
426
|
+
* - 'stop'
|
|
427
|
+
*
|
|
428
|
+
* @return {Object} - The status response for the stream
|
|
429
|
+
*
|
|
430
|
+
*/
|
|
431
|
+
exports.StreamStartOrStopOrReset = async function({name, op}) {
|
|
432
|
+
try {
|
|
433
|
+
console.log("Stream ", op, ": ", name);
|
|
434
|
+
let status = await this.StreamStatus({name});
|
|
435
|
+
if(status.state != "stopped") {
|
|
436
|
+
if(op === "start") {
|
|
437
|
+
status.error = "Unable to start stream - state: " + status.state;
|
|
438
|
+
return status;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if(status.state == "running" || status.state == "starting" || status.state == "stalled") {
|
|
443
|
+
console.log("STOPPING");
|
|
444
|
+
try {
|
|
445
|
+
await this.CallBitcodeMethod({
|
|
446
|
+
libraryId: status.library_id,
|
|
447
|
+
objectId: status.object_id,
|
|
448
|
+
writeToken: status.edge_write_token,
|
|
449
|
+
method: "/live/stop/" + status.tlro,
|
|
450
|
+
constant: false
|
|
451
|
+
});
|
|
452
|
+
} catch(error) {
|
|
453
|
+
// The /call/lro/stop API returns empty response
|
|
454
|
+
// console.log("LRO Stop (failed): ", error);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Wait until LRO is terminated
|
|
458
|
+
let tries = 10;
|
|
459
|
+
while(status.state != "stopped" && tries-- > 0) {
|
|
460
|
+
console.log("Wait to terminate - ", status.state);
|
|
461
|
+
await sleep(1000);
|
|
462
|
+
status = await this.StreamStatus({name});
|
|
463
|
+
}
|
|
464
|
+
console.log("Status after terminate - ", status.state);
|
|
465
|
+
|
|
466
|
+
if(tries <= 0) {
|
|
467
|
+
console.log("Failed to terminate");
|
|
468
|
+
return status;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if(op === "stop") {
|
|
473
|
+
return status;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log("STARTING", "edge_write_token", status.edge_write_token);
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
await this.CallBitcodeMethod({
|
|
480
|
+
libraryId: status.library_id,
|
|
481
|
+
objectId: status.object_id,
|
|
482
|
+
writeToken: status.edge_write_token,
|
|
483
|
+
method: "/live/start",
|
|
484
|
+
constant: false
|
|
485
|
+
});
|
|
486
|
+
} catch(error) {
|
|
487
|
+
console.log("LRO Start (failed): ", error);
|
|
488
|
+
return {
|
|
489
|
+
state: status.state,
|
|
490
|
+
error: "LRO start failed - must create a stream first"
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Wait until LRO is 'starting'
|
|
495
|
+
let tries = 10;
|
|
496
|
+
while(status.state != "starting" && tries-- > 0) {
|
|
497
|
+
console.log("Wait to start - ", status.state);
|
|
498
|
+
await sleep(1000);
|
|
499
|
+
status = await this.StreamStatus({name});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
console.log("Status after restart - ", status.state);
|
|
503
|
+
return status;
|
|
504
|
+
|
|
505
|
+
} catch(error) {
|
|
506
|
+
console.error(error);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
/*
|
|
511
|
+
* Stop the live stream session and close the edge write token.
|
|
512
|
+
* Not implemented fully
|
|
513
|
+
*/
|
|
514
|
+
// async StopSession({name}) {
|
|
515
|
+
//
|
|
516
|
+
// try {
|
|
517
|
+
//
|
|
518
|
+
// console.log("TERMINATE: ", name);
|
|
519
|
+
//
|
|
520
|
+
// let conf = await this.LoadConf({name});
|
|
521
|
+
//
|
|
522
|
+
// let objectId = conf.objectId;
|
|
523
|
+
// let libraryId = await this.client.ContentObjectLibraryId({objectId: objectId});
|
|
524
|
+
//
|
|
525
|
+
// let mainMeta = await this.client.ContentObjectMetadata({
|
|
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
|
+
// }
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
exports.LoadConf = async function({name}) {
|
|
921
|
+
if(name.startsWith("iq__")) {
|
|
922
|
+
return {
|
|
923
|
+
name: name,
|
|
924
|
+
objectId: name
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// If name is not a QID, load liveconf.json
|
|
929
|
+
let streamsBuf;
|
|
930
|
+
try {
|
|
931
|
+
streamsBuf = fs.readFileSync(
|
|
932
|
+
path.resolve(__dirname, "../liveconf.json")
|
|
933
|
+
);
|
|
934
|
+
} catch(error) {
|
|
935
|
+
console.log("Stream name must be a QID or a label in liveconf.json");
|
|
936
|
+
return {};
|
|
937
|
+
}
|
|
938
|
+
const streams = JSON.parse(streamsBuf);
|
|
939
|
+
const conf = streams[name];
|
|
940
|
+
if(conf === null) {
|
|
941
|
+
console.log("Bad name: ", name);
|
|
942
|
+
return {};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return conf;
|
|
946
|
+
};
|
|
947
|
+
|
|
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
|
+
/**
|
|
1106
|
+
* Configure the stream
|
|
1107
|
+
*
|
|
1108
|
+
* @methodGroup Live Stream
|
|
1109
|
+
* @namedParams
|
|
1110
|
+
* @param {string} name -
|
|
1111
|
+
* @param {string=} op - The operation to perform. Possible values:
|
|
1112
|
+
* 'start'
|
|
1113
|
+
* 'reset' - Stops current LRO recording and starts a new one. Does
|
|
1114
|
+
* not create a new edge write token (just creates a new recording
|
|
1115
|
+
* period in the existing edge write token)
|
|
1116
|
+
* - 'stop'
|
|
1117
|
+
*
|
|
1118
|
+
* @return {Object} - The status response for the stream
|
|
1119
|
+
*
|
|
1120
|
+
*/
|
|
1121
|
+
exports.StreamConfig = async function({name}) {
|
|
1122
|
+
let conf = await this.LoadConf({name});
|
|
1123
|
+
let status = {name};
|
|
1124
|
+
|
|
1125
|
+
try {
|
|
1126
|
+
let libraryId = await this.ContentObjectLibraryId({objectId: conf.objectId});
|
|
1127
|
+
status.library_id = libraryId;
|
|
1128
|
+
status.object_id = conf.objectId;
|
|
1129
|
+
|
|
1130
|
+
let mainMeta = await this.ContentObjectMetadata({
|
|
1131
|
+
libraryId: libraryId,
|
|
1132
|
+
objectId: conf.objectId
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
let userConfig = mainMeta.live_recording_config;
|
|
1136
|
+
status.user_config = userConfig;
|
|
1137
|
+
|
|
1138
|
+
// Get node URI from user config
|
|
1139
|
+
const hostName = userConfig.url.replace("udp://", "").replace("rtmp://", "").split(":")[0];
|
|
1140
|
+
const streamUrl = new URL(userConfig.url);
|
|
1141
|
+
|
|
1142
|
+
console.log("Retrieving nodes...");
|
|
1143
|
+
let nodes = await this.SpaceNodes({matchEndpoint: hostName});
|
|
1144
|
+
if(nodes.length < 1) {
|
|
1145
|
+
status.error = "No node matching stream URL " + streamUrl.href;
|
|
1146
|
+
return status;
|
|
1147
|
+
}
|
|
1148
|
+
const node = nodes[0];
|
|
1149
|
+
status.node = node;
|
|
1150
|
+
|
|
1151
|
+
let endpoint = node.endpoints[0];
|
|
1152
|
+
this.SetNodes({fabricURIs: [endpoint]});
|
|
1153
|
+
|
|
1154
|
+
// Probe the stream
|
|
1155
|
+
let probe = {};
|
|
1156
|
+
const controller = new AbortController();
|
|
1157
|
+
const timeoutId = setTimeout(() => {
|
|
1158
|
+
controller.abort();
|
|
1159
|
+
}, 60 * 1000); // milliseconds
|
|
1160
|
+
try {
|
|
1161
|
+
|
|
1162
|
+
let probeUrl = await this.Rep({
|
|
1163
|
+
libraryId,
|
|
1164
|
+
objectId: conf.objectId,
|
|
1165
|
+
rep: "probe"
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
probe = await this.utils.ResponseToJson(
|
|
1169
|
+
await HttpClient.Fetch(probeUrl, {
|
|
1170
|
+
body: JSON.stringify({
|
|
1171
|
+
"filename": streamUrl.href,
|
|
1172
|
+
"listen": true
|
|
1173
|
+
}),
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
signal: controller.signal
|
|
1176
|
+
})
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
if(probe) { clearTimeout(timeoutId); }
|
|
1180
|
+
} catch(error) {
|
|
1181
|
+
if(error.code === "ETIMEDOUT") {
|
|
1182
|
+
status.error = "Stream probe time out - make sure the stream source is available";
|
|
1183
|
+
} else {
|
|
1184
|
+
console.log("Stream probe failed", error);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
probe.format.filename = streamUrl.href;
|
|
1189
|
+
console.log("PROBE", probe);
|
|
1190
|
+
|
|
1191
|
+
// Create live reocording config
|
|
1192
|
+
let lc = new LiveConf(probe, node.id, endpoint, false, false, true);
|
|
1193
|
+
|
|
1194
|
+
const liveRecordingConfigStr = lc.generateLiveConf();
|
|
1195
|
+
let liveRecordingConfig = JSON.parse(liveRecordingConfigStr);
|
|
1196
|
+
console.log("CONFIG", JSON.stringify(liveRecordingConfig.live_recording));
|
|
1197
|
+
|
|
1198
|
+
// Store live recording config into the stream object
|
|
1199
|
+
let e = await this.EditContentObject({
|
|
1200
|
+
libraryId,
|
|
1201
|
+
objectId: conf.objectId
|
|
1202
|
+
});
|
|
1203
|
+
let writeToken = e.write_token;
|
|
1204
|
+
|
|
1205
|
+
await this.ReplaceMetadata({
|
|
1206
|
+
libraryId,
|
|
1207
|
+
objectId: conf.objectId,
|
|
1208
|
+
writeToken,
|
|
1209
|
+
metadataSubtree: "live_recording",
|
|
1210
|
+
metadata: liveRecordingConfig.live_recording
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
let fin = await this.FinalizeContentObject({
|
|
1214
|
+
libraryId,
|
|
1215
|
+
objectId: conf.objectId,
|
|
1216
|
+
writeToken,
|
|
1217
|
+
commitMessage: "Apply live stream configuration"
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
status.fin = fin;
|
|
1221
|
+
|
|
1222
|
+
return status;
|
|
1223
|
+
|
|
1224
|
+
} catch(e) {
|
|
1225
|
+
console.log("ERROR", e);
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
// const ChannelStatus = async ({client, name}) => {
|
|
1230
|
+
//
|
|
1231
|
+
// let status = {name: name};
|
|
1232
|
+
//
|
|
1233
|
+
// const conf = channels[name];
|
|
1234
|
+
// if(conf === null) {
|
|
1235
|
+
// console.log("Bad name: ", name);
|
|
1236
|
+
// return;
|
|
1237
|
+
// }
|
|
1238
|
+
//
|
|
1239
|
+
// try {
|
|
1240
|
+
//
|
|
1241
|
+
// let meta = await client.ContentObjectMetadata({
|
|
1242
|
+
// libraryId: conf.libraryId,
|
|
1243
|
+
// objectId: conf.objectId
|
|
1244
|
+
// });
|
|
1245
|
+
//
|
|
1246
|
+
// status.channel_title = meta.public.asset_metadata.title;
|
|
1247
|
+
// let source = meta.channel.offerings.default.items[0].source["/"];
|
|
1248
|
+
// let hash = source.split("/")[2];
|
|
1249
|
+
// status.stream_hash = hash;
|
|
1250
|
+
// latestHash = await client.LatestVersionHash({
|
|
1251
|
+
// versionHash: hash
|
|
1252
|
+
// });
|
|
1253
|
+
// status.stream_latest_hash = latestHash;
|
|
1254
|
+
//
|
|
1255
|
+
// if(hash != latestHash) {
|
|
1256
|
+
// status.warnings = ["Stream version is not the latest"];
|
|
1257
|
+
// }
|
|
1258
|
+
//
|
|
1259
|
+
// let channelFormatsUrl = await client.FabricUrl({
|
|
1260
|
+
// libraryId: conf.libraryId,
|
|
1261
|
+
// objectId: conf.objectId,
|
|
1262
|
+
// rep: "channel/options.json"
|
|
1263
|
+
// });
|
|
1264
|
+
//
|
|
1265
|
+
// try {
|
|
1266
|
+
// let offerings = await got(channelFormatsUrl);
|
|
1267
|
+
// status.offerings = JSON.parse(offerings.body);
|
|
1268
|
+
// } catch(error) {
|
|
1269
|
+
// console.log(error);
|
|
1270
|
+
// status.offerings_error = "Failed to retrieve channel offerings";
|
|
1271
|
+
// }
|
|
1272
|
+
//
|
|
1273
|
+
// status.playout = await ChannelPlayout({client, libraryId: conf.libraryId, objectId: conf.objectId});
|
|
1274
|
+
//
|
|
1275
|
+
// } catch(error) {
|
|
1276
|
+
// console.error(error);
|
|
1277
|
+
// }
|
|
1278
|
+
//
|
|
1279
|
+
// return status;
|
|
1280
|
+
// };
|
|
1281
|
+
|
|
1282
|
+
/*
|
|
1283
|
+
* Performs client-side playout operations - open the channel, read offerings,
|
|
1284
|
+
* retrieve playlist and one video init segment.
|
|
1285
|
+
*/
|
|
1286
|
+
// const ChannelPlayout = async({client, libraryId, objectId}) => {
|
|
1287
|
+
//
|
|
1288
|
+
// let playout = {};
|
|
1289
|
+
//
|
|
1290
|
+
// const offerings = await client.AvailableOfferings({
|
|
1291
|
+
// libraryId,
|
|
1292
|
+
// objectId,
|
|
1293
|
+
// handler: "channel",
|
|
1294
|
+
// linkPath: "/public/asset_metadata/offerings"
|
|
1295
|
+
// });
|
|
1296
|
+
//
|
|
1297
|
+
// // Choosing offering 'default'
|
|
1298
|
+
// let offering = offerings.default;
|
|
1299
|
+
//
|
|
1300
|
+
// const playoutOptions = await client.PlayoutOptions({
|
|
1301
|
+
// libraryId,
|
|
1302
|
+
// objectId,
|
|
1303
|
+
// offeringURI: offering.uri
|
|
1304
|
+
// });
|
|
1305
|
+
//
|
|
1306
|
+
// // Retrieve master playlist
|
|
1307
|
+
// let masterPlaylistUrl = playoutOptions["hls"]["playoutMethods"]["fairplay"]["playoutUrl"];
|
|
1308
|
+
// playout.master_playlist_url = masterPlaylistUrl;
|
|
1309
|
+
// try {
|
|
1310
|
+
// //let masterPlaylist = await got(masterPlaylistUrl);
|
|
1311
|
+
// playout.master_playlist = "success";
|
|
1312
|
+
// } catch(error) {
|
|
1313
|
+
// playout.master_playlist = "fail";
|
|
1314
|
+
// }
|
|
1315
|
+
//
|
|
1316
|
+
// let url = new URL(masterPlaylistUrl);
|
|
1317
|
+
// let p = url.pathname.split("/");
|
|
1318
|
+
//
|
|
1319
|
+
// // Retrieve media playlist
|
|
1320
|
+
// p[p.length - 1] = "video/720@14000000/live.m3u8";
|
|
1321
|
+
// let pathMediaPlaylist = p.join("/");
|
|
1322
|
+
// url.pathname = pathMediaPlaylist;
|
|
1323
|
+
// let mediaPlaylistUrl = url.toString();
|
|
1324
|
+
// playout.media_playlist_url = mediaPlaylistUrl;
|
|
1325
|
+
// let mediaPlaylist;
|
|
1326
|
+
// try {
|
|
1327
|
+
// mediaPlaylist = await got(mediaPlaylistUrl);
|
|
1328
|
+
// playout.media_playlist = "success";
|
|
1329
|
+
// } catch(error) {
|
|
1330
|
+
// playout.media_playlist = "fail";
|
|
1331
|
+
// }
|
|
1332
|
+
//
|
|
1333
|
+
// // Retrieve init segment
|
|
1334
|
+
// var regex = new RegExp("^#EXT-X-MAP:URI=\"init.m4s.(.*)\"$", "m");
|
|
1335
|
+
// var match = regex.exec(mediaPlaylist.body);
|
|
1336
|
+
// let initQueryParams;
|
|
1337
|
+
// if(match) {
|
|
1338
|
+
// initQueryParams = match[1];
|
|
1339
|
+
// }
|
|
1340
|
+
//
|
|
1341
|
+
// p[p.length - 1] = "video/720@14000000/init.m4s";
|
|
1342
|
+
// let pathInit = p.join("/");
|
|
1343
|
+
// url.pathname = pathInit;
|
|
1344
|
+
// url.search=initQueryParams;
|
|
1345
|
+
// let initUrl = url.toString();
|
|
1346
|
+
// playout.init_segment_url = initUrl;
|
|
1347
|
+
// /*
|
|
1348
|
+
// try {
|
|
1349
|
+
// let initSegment = await got(initUrl);
|
|
1350
|
+
// playout.init_segment = "success"
|
|
1351
|
+
// } catch(error) {
|
|
1352
|
+
// playout.init_segment = "fail";
|
|
1353
|
+
// }
|
|
1354
|
+
// */
|
|
1355
|
+
// return playout;
|
|
1356
|
+
// };
|
|
1357
|
+
|
|
1358
|
+
|
|
1359
|
+
// const Summary = async ({client}) => {
|
|
1360
|
+
//
|
|
1361
|
+
// let summary = {};
|
|
1362
|
+
//
|
|
1363
|
+
// try {
|
|
1364
|
+
// for (const [key] of Object.entries(streams)) {
|
|
1365
|
+
// conf = streams[key];
|
|
1366
|
+
// summary[key] = await Status({client, name: key, stopLro: false});
|
|
1367
|
+
// }
|
|
1368
|
+
//
|
|
1369
|
+
// } catch(error) {
|
|
1370
|
+
// console.error(error);
|
|
1371
|
+
// }
|
|
1372
|
+
// return summary;
|
|
1373
|
+
// };
|
|
1374
|
+
|
|
1375
|
+
// const ChannelSummary = async ({client}) => {
|
|
1376
|
+
//
|
|
1377
|
+
// let summary = {};
|
|
1378
|
+
//
|
|
1379
|
+
// try {
|
|
1380
|
+
// for (const [key] of Object.entries(channels)) {
|
|
1381
|
+
// conf = channels[key];
|
|
1382
|
+
// summary[key] = await ChannelStatus({client, name: key});
|
|
1383
|
+
// }
|
|
1384
|
+
//
|
|
1385
|
+
// } catch(error) {
|
|
1386
|
+
// console.error(error);
|
|
1387
|
+
// }
|
|
1388
|
+
// return summary;
|
|
1389
|
+
// };
|
|
1390
|
+
|
|
1391
|
+
// const ConfigStreamRebroadcast = async () => {
|
|
1392
|
+
//
|
|
1393
|
+
// const t = 1619850660;
|
|
1394
|
+
//
|
|
1395
|
+
// try {
|
|
1396
|
+
// let client;
|
|
1397
|
+
// if(conf.clientConf.configUrl) {
|
|
1398
|
+
// client = await ElvClient.FromConfigurationUrl({
|
|
1399
|
+
// configUrl: conf.clientConf.configUrl
|
|
1400
|
+
// });
|
|
1401
|
+
// } else {
|
|
1402
|
+
// client = new ElvClient(conf.clientConf);
|
|
1403
|
+
// }
|
|
1404
|
+
// const wallet = client.GenerateWallet();
|
|
1405
|
+
// const signer = wallet.AddAccount({ privateKey: conf.signerPrivateKey });
|
|
1406
|
+
// client.SetSigner({ signer });
|
|
1407
|
+
// const fabURI = client.fabricURIs[0];
|
|
1408
|
+
// console.log("Fabric URI: " + fabURI);
|
|
1409
|
+
// const ethURI = client.ethereumURIs[0];
|
|
1410
|
+
// console.log("Ethereum URI: " + ethURI);
|
|
1411
|
+
//
|
|
1412
|
+
// client.ToggleLogging(false);
|
|
1413
|
+
//
|
|
1414
|
+
// let mainMeta = await client.ContentObjectMetadata({
|
|
1415
|
+
// libraryId: conf.libraryId,
|
|
1416
|
+
// objectId: conf.objectId
|
|
1417
|
+
// });
|
|
1418
|
+
// console.log("Main meta:", mainMeta);
|
|
1419
|
+
//
|
|
1420
|
+
// edgeWriteToken = mainMeta.edge_write_token;
|
|
1421
|
+
// console.log("Edge: ", edgeWriteToken);
|
|
1422
|
+
//
|
|
1423
|
+
// let edgeMeta = await client.ContentObjectMetadata({
|
|
1424
|
+
// libraryId: conf.libraryId,
|
|
1425
|
+
// objectId: conf.objectId,
|
|
1426
|
+
// writeToken: edgeWriteToken
|
|
1427
|
+
// });
|
|
1428
|
+
// console.log("Edge meta:", edgeMeta);
|
|
1429
|
+
//
|
|
1430
|
+
// //console.log("CONFIG: ", edgeMeta.live_recording_parameters.live_playout_config);
|
|
1431
|
+
// console.log("recording_start_time: ", edgeMeta.recording_start_time);
|
|
1432
|
+
// console.log("recording_stop_time: ", edgeMeta.recording_stop_time);
|
|
1433
|
+
//
|
|
1434
|
+
// // Set rebroadcast start
|
|
1435
|
+
// edgeMeta.live_recording_parameters.live_playout_config.rebroadcast_start_time_sec_epoch = t;
|
|
1436
|
+
//
|
|
1437
|
+
// if(PRINT_DEBUG) console.log("MergeMetadata", conf.libraryId, conf.objectId, writeToken);
|
|
1438
|
+
// await client.MergeMetadata({
|
|
1439
|
+
// libraryId: conf.libraryId,
|
|
1440
|
+
// objectId: conf.objectId,
|
|
1441
|
+
// writeToken: edgeWriteToken,
|
|
1442
|
+
// metadata: {
|
|
1443
|
+
// "live_recording_parameters": {
|
|
1444
|
+
// "live_playout_config" : edgeMeta.live_recording_parameters.live_playout_config
|
|
1445
|
+
// }
|
|
1446
|
+
// }
|
|
1447
|
+
// });
|
|
1448
|
+
//
|
|
1449
|
+
// } catch(error) {
|
|
1450
|
+
// console.error(error);
|
|
1451
|
+
// }
|
|
1452
|
+
// };
|
|
1453
|
+
|
|
1454
|
+
// async function EnsureAll() {
|
|
1455
|
+
// client = await StatusPrep({name: null});
|
|
1456
|
+
// let summary = await Summary({client});
|
|
1457
|
+
//
|
|
1458
|
+
// var res = {
|
|
1459
|
+
// running: 0,
|
|
1460
|
+
// stalled: 0,
|
|
1461
|
+
// terminated: 0
|
|
1462
|
+
// };
|
|
1463
|
+
//
|
|
1464
|
+
// try {
|
|
1465
|
+
// for (const [key, value] of Object.entries(summary)) {
|
|
1466
|
+
// if(value.state === "stalled") {
|
|
1467
|
+
// console.log("Stream stalled: ", key, " - restarting");
|
|
1468
|
+
// console.log("todo ...");
|
|
1469
|
+
// }
|
|
1470
|
+
// res[value.state] = res[value.state] + 1;
|
|
1471
|
+
// }
|
|
1472
|
+
// } catch(error) {
|
|
1473
|
+
// console.error(error);
|
|
1474
|
+
// }
|
|
1475
|
+
//
|
|
1476
|
+
// return res;
|
|
1477
|
+
// }
|
|
1478
|
+
|
|
1479
|
+
|
|
1480
|
+
/*
|
|
1481
|
+
* Original Run() function - kept for reference
|
|
1482
|
+
*/
|
|
1483
|
+
// async function Run() {
|
|
1484
|
+
//
|
|
1485
|
+
// var client;
|
|
1486
|
+
//
|
|
1487
|
+
// switch (command) {
|
|
1488
|
+
//
|
|
1489
|
+
// case "start":
|
|
1490
|
+
// StartStream({name});
|
|
1491
|
+
// break;
|
|
1492
|
+
//
|
|
1493
|
+
// case "status":
|
|
1494
|
+
// client = await StatusPrep({name});
|
|
1495
|
+
// let status = await Status({client, name, stopLro: false});
|
|
1496
|
+
// console.log(JSON.stringify(status, null, 4));
|
|
1497
|
+
// break;
|
|
1498
|
+
//
|
|
1499
|
+
// case "stop":
|
|
1500
|
+
// client = await UpdatePrep({name});
|
|
1501
|
+
// Status({client, name, stopLro: true});
|
|
1502
|
+
// break;
|
|
1503
|
+
//
|
|
1504
|
+
// case "summary":
|
|
1505
|
+
// client = await StatusPrep({name: null});
|
|
1506
|
+
// let summary = await Summary({client});
|
|
1507
|
+
// console.log(JSON.stringify(summary, null, 4));
|
|
1508
|
+
// break;
|
|
1509
|
+
//
|
|
1510
|
+
// case "init": // Set up DRM
|
|
1511
|
+
// SetOfferingAndDRM();
|
|
1512
|
+
// break;
|
|
1513
|
+
//
|
|
1514
|
+
// case "reset": // Stop and start LRO recording (same edge write token)
|
|
1515
|
+
// client = await StatusPrep({name});
|
|
1516
|
+
// let reset = await Reset({client, name, stopLro: false});
|
|
1517
|
+
// console.log(JSON.stringify(reset, null, 4));
|
|
1518
|
+
// break;
|
|
1519
|
+
//
|
|
1520
|
+
// case "channel":
|
|
1521
|
+
// client = await StatusPrep({name});
|
|
1522
|
+
// let channelStatus = await ChannelStatus({client, name});
|
|
1523
|
+
// console.log(JSON.stringify(channelStatus, null, 4));
|
|
1524
|
+
// break;
|
|
1525
|
+
//
|
|
1526
|
+
// case "channel_summary":
|
|
1527
|
+
// client = await StatusPrep({name});
|
|
1528
|
+
// let channelSummary = await ChannelSummary({client, name});
|
|
1529
|
+
// console.log(JSON.stringify(channelSummary, null, 4));
|
|
1530
|
+
// break;
|
|
1531
|
+
//
|
|
1532
|
+
// case "ensure_all": // Check all and restart stalled
|
|
1533
|
+
// let ensureSummary = await EnsureAll();
|
|
1534
|
+
// console.log(JSON.stringify(ensureSummary, null, 4));
|
|
1535
|
+
// break;
|
|
1536
|
+
//
|
|
1537
|
+
// case "future_use_config":
|
|
1538
|
+
// ConfigStreamRebroadcast();
|
|
1539
|
+
// break;
|
|
1540
|
+
//
|
|
1541
|
+
// default:
|
|
1542
|
+
// console.log("Bad command: ", command);
|
|
1543
|
+
// break;
|
|
1544
|
+
//
|
|
1545
|
+
// }
|
|
1546
|
+
// }
|