@eluvio/elv-client-js 3.1.66 → 3.1.67
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/README.md +13 -2
- package/dist/ElvClient-min.js +2 -2
- package/dist/ElvClient-node-min.js +2 -2
- package/dist/ElvFrameClient-min.js +1 -1
- package/dist/src/ElvClient.js +6 -3
- package/dist/src/client/ContentAccess.js +868 -818
- package/package-lock.json +16 -11
- package/package.json +3 -1
- package/src/ElvClient.js +4 -1
- package/src/client/ContentAccess.js +29 -0
- package/testScripts/InitializeTenant.js +32 -1
- package/testScripts/Test.js +19 -0
- package/typeSpecs/Default.js +80 -0
- package/typeSpecs/DropEventSite.js +673 -0
- package/typeSpecs/EventSite.js +776 -0
- package/typeSpecs/EventSiteExtras.js +59 -0
- package/typeSpecs/EventTenant.js +86 -0
- package/typeSpecs/LanguageCodes.js +221 -0
- package/typeSpecs/MainSite.js +279 -0
- package/typeSpecs/Marketplace.js +203 -0
- package/typeSpecs/NFTCollection.js +69 -0
- package/typeSpecs/NFTTemplate.js +269 -0
- package/utilities/ChannelCreate.js +205 -0
- package/utilities/ChannelStartVaLOffering.js +63 -29
- package/utilities/LibraryListObjects.js +2 -0
- package/utilities/MezUnifyAudioDrmKeys.js +69 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const R = require("ramda");
|
|
2
|
+
|
|
3
|
+
const {ModOpt, NewOpt, StdOpt} = require("./lib/options");
|
|
4
|
+
const Utility = require("./lib/Utility");
|
|
5
|
+
|
|
6
|
+
const ArgLibraryId = require("./lib/concerns/ArgLibraryId");
|
|
7
|
+
const ArgType = require("./lib/concerns/ArgType");
|
|
8
|
+
const FabricObject = require("./lib/concerns/FabricObject");
|
|
9
|
+
const Version = require("./lib/concerns/Version");
|
|
10
|
+
|
|
11
|
+
const STREAM_FIELDS = [
|
|
12
|
+
"aspect_ratio",
|
|
13
|
+
"channel_layout",
|
|
14
|
+
"channels",
|
|
15
|
+
"codec_name",
|
|
16
|
+
"codec_type",
|
|
17
|
+
"height",
|
|
18
|
+
"rate",
|
|
19
|
+
"width"
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const itemParser = itemStr => {
|
|
23
|
+
const parsed = itemStr.split("/");
|
|
24
|
+
if(parsed.length !== 2) throw Error(`Failed to parse item '${itemStr} - each item should be a version hash + slash + offering key, e.g. 'hq__1234/default'`);
|
|
25
|
+
return {hash: parsed[0], offering: parsed[1]};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const mediaStructStreamFields = R.pick(STREAM_FIELDS);
|
|
29
|
+
|
|
30
|
+
const msStreamsFieldSubset = R.map(mediaStructStreamFields);
|
|
31
|
+
|
|
32
|
+
// return media_struct stream keys appearing in playout.streams.representations (usually only 1)
|
|
33
|
+
const poStreamMsStreamKeys = poStream => R.uniq(R.map(R.prop("media_struct_stream_key"), poStream.representations));
|
|
34
|
+
|
|
35
|
+
// return fields that need to be checked from media_struct.streams, include only streams referred to by playout.streams
|
|
36
|
+
const msStreamFields = (poStreams, msStreams) => {
|
|
37
|
+
const msStreamKeys = R.uniq(R.values(R.map(poStreamMsStreamKeys, poStreams)));
|
|
38
|
+
const usedMsStreams = R.pick(msStreamKeys, msStreams);
|
|
39
|
+
return R.map(msStreamsFieldSubset, usedMsStreams);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const withoutDrmContentId = poFormat => {
|
|
43
|
+
const clone = R.clone(poFormat);
|
|
44
|
+
if(clone.drm) clone.drm.content_id = null;
|
|
45
|
+
return clone;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const withoutEncryptionSchemes = poStream => {
|
|
49
|
+
const clone = R.clone(poStream);
|
|
50
|
+
clone.encryption_schemes = null;
|
|
51
|
+
return clone;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ChannelCreate extends Utility {
|
|
56
|
+
blueprint() {
|
|
57
|
+
return {
|
|
58
|
+
concerns: [ArgLibraryId, ArgType, FabricObject, Version],
|
|
59
|
+
options: [
|
|
60
|
+
ModOpt("libraryId", {demand: true}),
|
|
61
|
+
ModOpt("type", {demand: true}),
|
|
62
|
+
StdOpt("name",
|
|
63
|
+
{
|
|
64
|
+
demand: true,
|
|
65
|
+
forX: "channel object"
|
|
66
|
+
}),
|
|
67
|
+
NewOpt("items", {
|
|
68
|
+
demand: true,
|
|
69
|
+
descTemplate: "List of channel items, separated by spaces. Each item should be of form VERSION_HASH/OFFERING_KEY, e.g. hq__12345/default ",
|
|
70
|
+
string: true,
|
|
71
|
+
type: "array"
|
|
72
|
+
}),
|
|
73
|
+
NewOpt("val", {
|
|
74
|
+
descTemplate: "VoD-as-Live (make the channel a simulated live stream)",
|
|
75
|
+
type: "boolean"
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async body() {
|
|
82
|
+
const logger = this.logger;
|
|
83
|
+
const {name, items, libraryId, val} = this.args;
|
|
84
|
+
const type = await this.concerns.ArgType.typVersionHash();
|
|
85
|
+
|
|
86
|
+
const itemList = R.map(itemParser, items);
|
|
87
|
+
|
|
88
|
+
// check items
|
|
89
|
+
logger.log("\nChecking items for any parameter mismatches...");
|
|
90
|
+
|
|
91
|
+
// get items
|
|
92
|
+
const itemOfferings = [];
|
|
93
|
+
for(let i = 0; i < itemList.length; i++) {
|
|
94
|
+
const item = itemList[i];
|
|
95
|
+
const versionHash = item.hash;
|
|
96
|
+
const objectId = this.concerns.Version.objectId({versionHash});
|
|
97
|
+
const offering = await this.concerns.FabricObject.metadata({
|
|
98
|
+
libraryId,
|
|
99
|
+
objectId,
|
|
100
|
+
subtree: "/offerings/" + item.offering,
|
|
101
|
+
versionHash
|
|
102
|
+
});
|
|
103
|
+
itemOfferings.push(offering);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// make sure streams, playout formats, and ladders are the same
|
|
107
|
+
const firstPoStreams = R.map(withoutEncryptionSchemes, itemOfferings[0].playout.streams);
|
|
108
|
+
const firstPoFormats = R.map(withoutDrmContentId, itemOfferings[0].playout.playout_formats);
|
|
109
|
+
const firstMsStreamFields = msStreamFields(firstPoStreams, itemOfferings[0].media_struct.streams);
|
|
110
|
+
|
|
111
|
+
for(let i = 1; i < itemList.length; i++) {
|
|
112
|
+
const testPoStreams = R.map(withoutEncryptionSchemes, itemOfferings[i].playout.streams);
|
|
113
|
+
//
|
|
114
|
+
// console.log(JSON.stringify(firstPoStreams,null,2));
|
|
115
|
+
// console.log(JSON.stringify(testPoStreams,null,2));
|
|
116
|
+
|
|
117
|
+
if(!R.equals(firstPoStreams, testPoStreams)) throw Error("ERROR: All items must have identical playout.streams.representations");
|
|
118
|
+
|
|
119
|
+
const testPoFormats = R.map(withoutDrmContentId, itemOfferings[i].playout.playout_formats);
|
|
120
|
+
if(!R.equals(firstPoFormats, testPoFormats)) throw Error("ERROR: All items must have identical playout.playout_formats (other than widevine content_id field)");
|
|
121
|
+
|
|
122
|
+
const testMsStreamFields = msStreamFields(testPoStreams, itemOfferings[i].media_struct.streams);
|
|
123
|
+
if(!R.equals(firstMsStreamFields, testMsStreamFields)) throw Error("ERROR: All items must have matching values for included streams in the following media_struct stream fields: " + STREAM_FIELDS.join(", "));
|
|
124
|
+
}
|
|
125
|
+
// passed checks, assemble channel items metadata
|
|
126
|
+
logger.log("Mezzanine item parameter checks passed.");
|
|
127
|
+
|
|
128
|
+
// create new object
|
|
129
|
+
let metadata = {public: {name}};
|
|
130
|
+
|
|
131
|
+
logger.log("\nCreating new channel object...");
|
|
132
|
+
|
|
133
|
+
const {objectId} = await this.concerns.ArgLibraryId.libCreateObject({
|
|
134
|
+
metadata,
|
|
135
|
+
type
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
logger.log("\nAdding channel metadata to new object...");
|
|
139
|
+
|
|
140
|
+
// retrieve newly created object's metadata
|
|
141
|
+
metadata = await this.concerns.FabricObject.metadata({libraryId, objectId});
|
|
142
|
+
|
|
143
|
+
const channelMeta = {
|
|
144
|
+
offerings: {
|
|
145
|
+
default: {
|
|
146
|
+
items: [],
|
|
147
|
+
live_end_tol: 60,
|
|
148
|
+
live_seg_count: 60,
|
|
149
|
+
playout: {
|
|
150
|
+
playout_formats: itemOfferings[0].playout.playout_formats,
|
|
151
|
+
streams: itemOfferings[0].playout.streams
|
|
152
|
+
},
|
|
153
|
+
playout_type: (val ? "ch_val" : "ch_vod")
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// add items
|
|
159
|
+
for(let i = 0; i < itemList.length; i++) {
|
|
160
|
+
const offering = itemOfferings[i];
|
|
161
|
+
const item = itemList[i];
|
|
162
|
+
const itemMeta = {
|
|
163
|
+
duration_rat: offering.media_struct.duration_rat,
|
|
164
|
+
source: {
|
|
165
|
+
".": {
|
|
166
|
+
auto_update: {
|
|
167
|
+
tag: "latest"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"/": `/qfab/${item.hash}/rep/playout/${item.offering}`
|
|
171
|
+
},
|
|
172
|
+
type: "mez_vod"
|
|
173
|
+
};
|
|
174
|
+
channelMeta.offerings.default.items.push(itemMeta);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
metadata.channel = channelMeta;
|
|
178
|
+
metadata.public.offerings = {"/": "./rep/channel/options.json"};
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
// Write back metadata
|
|
182
|
+
logger.log("Writing metadata...");
|
|
183
|
+
const versionHash = await this.concerns.Metadata.write({
|
|
184
|
+
libraryId,
|
|
185
|
+
metadata,
|
|
186
|
+
objectId
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
logger.log("");
|
|
190
|
+
logger.log(`New object ID: ${objectId}`);
|
|
191
|
+
logger.data("object_id", objectId);
|
|
192
|
+
logger.log("New version hash: " + versionHash);
|
|
193
|
+
logger.data("version_hash", versionHash);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
header() {
|
|
197
|
+
return `Create channel '${this.args.name}' in lib ${this.args.libraryId}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if(require.main === module) {
|
|
202
|
+
Utility.cmdLineInvoke(ChannelCreate);
|
|
203
|
+
} else {
|
|
204
|
+
module.exports = ChannelCreate;
|
|
205
|
+
}
|
|
@@ -53,6 +53,8 @@ class ChannelStartVaLOffering extends Utility {
|
|
|
53
53
|
// ----------------------------------------------------
|
|
54
54
|
const {delay, libraryId, objectId, offeringKey} = await this.concerns.ExistObj.argsProc();
|
|
55
55
|
|
|
56
|
+
const client = await this.concerns.Client.get();
|
|
57
|
+
|
|
56
58
|
logger.log("Retrieving existing metadata from object...");
|
|
57
59
|
const currentMetadata = await this.concerns.ExistObj.metadata();
|
|
58
60
|
|
|
@@ -83,7 +85,34 @@ class ChannelStartVaLOffering extends Utility {
|
|
|
83
85
|
0
|
|
84
86
|
);
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
// get item list from channel offering, get an auth token for each to include in playout URLs
|
|
89
|
+
const mezAuthTokens = [];
|
|
90
|
+
const items = currentMetadata.channel.offerings[offeringKey].items;
|
|
91
|
+
|
|
92
|
+
const RE_URI_HASH = /\/(hq__[a-zA-Z0-9]+)\//;
|
|
93
|
+
logger.log("Generating auth tokens...");
|
|
94
|
+
for(let i = 0; i < items.length; i++) {
|
|
95
|
+
const itemURI = items[i].source["/"];
|
|
96
|
+
const mezVersionHash = RE_URI_HASH.exec(itemURI)[1];
|
|
97
|
+
if(!mezVersionHash) throw Error(`hash not found in item URI: ${itemURI}`);
|
|
98
|
+
|
|
99
|
+
const mezObjectId = this.concerns.Version.objectId({versionHash: mezVersionHash});
|
|
100
|
+
const mezLibId = await this.concerns.FabricObject.libraryId({objectId: mezObjectId});
|
|
101
|
+
|
|
102
|
+
mezAuthTokens.push(
|
|
103
|
+
await client.authClient.AuthorizationToken(
|
|
104
|
+
{
|
|
105
|
+
libraryId: mezLibId,
|
|
106
|
+
objectId: mezObjectId,
|
|
107
|
+
versionHash: mezVersionHash,
|
|
108
|
+
update: false
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
const liveSegCount = this.args.liveWindowSize || currentMetadata.channel.offerings[offeringKey].live_seg_count || 60;
|
|
87
116
|
const liveEndTol = this.args.liveEndTolerance || currentMetadata.channel.offerings[offeringKey].live_end_tol || 300;
|
|
88
117
|
|
|
89
118
|
const now = new Date; // ).toISOString();
|
|
@@ -114,7 +143,6 @@ class ChannelStartVaLOffering extends Utility {
|
|
|
114
143
|
objectId
|
|
115
144
|
});
|
|
116
145
|
|
|
117
|
-
const client = await this.concerns.Client.get();
|
|
118
146
|
const url = await client.FabricUrl({
|
|
119
147
|
libraryId,
|
|
120
148
|
objectId,
|
|
@@ -154,14 +182,15 @@ class ChannelStartVaLOffering extends Utility {
|
|
|
154
182
|
});
|
|
155
183
|
|
|
156
184
|
let offUrlObj = new URL(offeringUrl);
|
|
157
|
-
const urlBase =
|
|
185
|
+
const urlBase = offUrlObj.origin + offUrlObj.pathname;
|
|
158
186
|
const authToken = offUrlObj.searchParams.get("authorization");
|
|
159
187
|
let sid = "";
|
|
160
188
|
for(const [playoutFormatKey, playoutFormatInfo] of Object.entries(offeringOptions)) {
|
|
161
189
|
const pfUrlObj = new URL(playoutFormatInfo.uri, urlBase);
|
|
162
190
|
sid = pfUrlObj.searchParams.get("sid");
|
|
163
|
-
const playoutUrl =
|
|
191
|
+
const playoutUrl = new URL(playoutFormatInfo.uri, urlBase);
|
|
164
192
|
playoutUrl.searchParams.set("authorization", authToken);
|
|
193
|
+
mezAuthTokens.forEach(t => playoutUrl.searchParams.append("authorization", t));
|
|
165
194
|
playoutUrl.searchParams.set("sid", sid);
|
|
166
195
|
|
|
167
196
|
logger.log();
|
|
@@ -170,37 +199,42 @@ class ChannelStartVaLOffering extends Utility {
|
|
|
170
199
|
logger.log(playoutUrl.toString());
|
|
171
200
|
}
|
|
172
201
|
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
202
|
+
const multiviewPresent = currentMetadata.channel.offerings[offeringKey].multiview &&
|
|
203
|
+
!R.empty(currentMetadata.channel.offerings[offeringKey].multiview);
|
|
204
|
+
if(multiviewPresent) {
|
|
205
|
+
const viewsUrl = await client.FabricUrl({
|
|
206
|
+
libraryId,
|
|
207
|
+
objectId,
|
|
208
|
+
versionHash,
|
|
209
|
+
rep: `channel/${offeringKey}/views.json`
|
|
210
|
+
});
|
|
211
|
+
const viewsUrlObj = new URL(viewsUrl);
|
|
212
|
+
viewsUrlObj.searchParams.set("sid", sid);
|
|
213
|
+
logger.log();
|
|
214
|
+
logger.log(`Sample offering '${offeringKey}' current available views URL (sid must be same as in playout URL):`);
|
|
215
|
+
logger.log();
|
|
216
|
+
logger.log(viewsUrlObj.toString());
|
|
185
217
|
|
|
186
218
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
219
|
+
const selectViewUrl = await client.FabricUrl({
|
|
220
|
+
libraryId,
|
|
221
|
+
objectId,
|
|
222
|
+
versionHash,
|
|
223
|
+
rep: `channel/${offeringKey}/select_view`
|
|
224
|
+
});
|
|
225
|
+
const viewSelectUrlObj = new URL(selectViewUrl);
|
|
226
|
+
viewSelectUrlObj.searchParams.set("sid", sid);
|
|
227
|
+
|
|
228
|
+
logger.log();
|
|
229
|
+
logger.log("Sample curl command to select view (sid must be same as in playout URL):");
|
|
230
|
+
logger.log();
|
|
231
|
+
logger.log(`curl -X POST '${viewSelectUrlObj.toString()}' -d '{"view":1}'`);
|
|
232
|
+
}
|
|
195
233
|
|
|
196
|
-
logger.log();
|
|
197
|
-
logger.log("Sample curl command to select view (sid must be same as in playout URL):");
|
|
198
|
-
logger.log();
|
|
199
|
-
logger.log(`curl -X POST '${viewSelectUrlObj.toString()}' -d '{"view":1}'`);
|
|
200
234
|
}
|
|
201
235
|
|
|
202
236
|
header() {
|
|
203
|
-
return `Start playout of
|
|
237
|
+
return `Start playout of simulated live channel object ${this.args.objectId}${this.args.delay ? ` with ${this.args.delay} second(s) delay` : ""}`;
|
|
204
238
|
}
|
|
205
239
|
}
|
|
206
240
|
|
|
@@ -53,6 +53,8 @@ class LibraryListObjects extends Utility {
|
|
|
53
53
|
const logger = this.logger;
|
|
54
54
|
const filter = this.args.filter && this.concerns.JSON.parseStringOrFile({strOrPath: this.args.filter});
|
|
55
55
|
|
|
56
|
+
if(!this.args.fields) this.args.fields = [];
|
|
57
|
+
|
|
56
58
|
const select = ["/public/name", ...this.args.fields];
|
|
57
59
|
|
|
58
60
|
const objectList = await this.concerns.ArgLibraryId.libObjectList(
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// go through offerings and set all audio streams to use same encryption keys
|
|
2
|
+
|
|
3
|
+
const R = require("ramda");
|
|
4
|
+
|
|
5
|
+
const Utility = require("./lib/Utility");
|
|
6
|
+
|
|
7
|
+
const ExistObj = require("./lib/concerns/ExistObj");
|
|
8
|
+
const Metadata = require("./lib/concerns/Metadata");
|
|
9
|
+
|
|
10
|
+
class MezUnifyAudioDrmKeys extends Utility {
|
|
11
|
+
blueprint() {
|
|
12
|
+
return {
|
|
13
|
+
concerns: [
|
|
14
|
+
ExistObj, Metadata
|
|
15
|
+
]
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async body() {
|
|
20
|
+
const logger = this.logger;
|
|
21
|
+
|
|
22
|
+
// operations that need to wait on network access
|
|
23
|
+
// ----------------------------------------------------
|
|
24
|
+
const {libraryId, objectId} = await this.concerns.ExistObj.argsProc();
|
|
25
|
+
|
|
26
|
+
logger.log("Retrieving existing metadata from object...");
|
|
27
|
+
const metadata = await this.concerns.ExistObj.metadata();
|
|
28
|
+
|
|
29
|
+
if(!metadata.offerings || R.isEmpty(metadata.offerings)) throw Error("no offerings found in metadata");
|
|
30
|
+
|
|
31
|
+
// loop through offerings
|
|
32
|
+
for(const [offeringKey, offering] of Object.entries(metadata.offerings)) {
|
|
33
|
+
logger.log(` Checking offering ${offeringKey}...`);
|
|
34
|
+
// loop through playout streams, saving first audio stream's keys
|
|
35
|
+
let keyIds;
|
|
36
|
+
for(const [streamKey, stream] of Object.entries(offering.playout.streams)) {
|
|
37
|
+
if(stream.representations && Object.entries(stream.representations)[0][1].type === "RepAudio") {
|
|
38
|
+
if(keyIds) {
|
|
39
|
+
logger.log(` Setting keys for stream '${streamKey}'...`);
|
|
40
|
+
stream.encryption_schemes = R.clone(keyIds);
|
|
41
|
+
} else {
|
|
42
|
+
if(!stream.encryption_schemes || R.isEmpty(stream.encryption_schemes)) throw Error(`Audio stream ${streamKey} has no encryption scheme info`);
|
|
43
|
+
logger.log(` Using keys from stream '${streamKey}'...`);
|
|
44
|
+
keyIds = stream.encryption_schemes;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Write back metadata
|
|
51
|
+
const newHash = await this.concerns.Metadata.write({
|
|
52
|
+
libraryId,
|
|
53
|
+
metadata,
|
|
54
|
+
objectId
|
|
55
|
+
});
|
|
56
|
+
this.logger.data("version_hash", newHash);
|
|
57
|
+
this.logger.log("New version hash: " + newHash);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
header() {
|
|
61
|
+
return `Make all audio streams use same DRM keys in object ${this.args.objectId}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if(require.main === module) {
|
|
66
|
+
Utility.cmdLineInvoke(MezUnifyAudioDrmKeys);
|
|
67
|
+
} else {
|
|
68
|
+
module.exports = MezUnifyAudioDrmKeys;
|
|
69
|
+
}
|