eyevinn-channel-engine 3.1.3 → 3.1.6

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 CHANGED
@@ -35,7 +35,11 @@ Then point your HLS video player to `http://localhost:8000/channels/1/master.m3u
35
35
 
36
36
  ## Master manifest filtering
37
37
 
38
- The engine supports a very simplistic and basic filtering of media playlists included in the master manifest. Currently supports to filter on video bandwidth and video height. To specify a filter provide the query parameter `filter` when loading the master manifest, e.g. `(type=="video"&&height>200)&&(type=="video"&&height<400)`. This needs to be URL encoded resulting in the following URL: `http://localhost:8000/channels/1/master.m3u8?filter=%28type%3D%3D%22video%22%26%26height%3E200%29%26%26%28type%3D%3D%22video%22%26%26height%3C400%29`
38
+ The engine supports a very simplistic and basic filtering of media playlists included in the master manifest. Currently supports to filter on video bandwidth and video height. To specify a filter provide the query parameter `filter` when loading the master manifest, e.g. `(type=="video"ANDheight>200)AND(type=="video"ANDheight<400)`. This needs to be URL encoded resulting in the following URL: `http://localhost:8000/channels/1/master.m3u8?filter=%28type%3D%3D%22video%22ANDheight%3E200%29AND%28type%3D%3D%22video%22ANDheight%3C400%29`.
39
+
40
+ To filter by video bandwidth use the `systemBitrate` keyword in the query, e.g. `(type=="video"ANDsystemBitrate>2121000)AND(type=="video"ANDsystemBitrate<6161000)`.
41
+
42
+ You can also combine the filter conditions, e.g. `(type=="video"ANDheight>240)AND(type=="video"ANDsystemBitrate<4141000)`
39
43
 
40
44
  ## API
41
45
 
package/engine/session.js CHANGED
@@ -44,6 +44,8 @@ class Session {
44
44
  this.maxTickInterval = DEFAULT_MAX_TICK_INTERVAL;
45
45
  this.diffCompensation = null;
46
46
  this.waitingForNextVod = false;
47
+ this.leaderIsSettingNextVod = false;
48
+ this.isSwitchingBackFromLive = false;
47
49
 
48
50
  if (config) {
49
51
  if (config.sessionId) {
@@ -258,6 +260,8 @@ class Session {
258
260
  throw new Error('Session not ready');
259
261
  }
260
262
 
263
+ this.isSwitchingBackFromLive = true;
264
+
261
265
  let isLeader = await this._sessionStateStore.isLeader(this._instanceId);
262
266
  if (!isLeader) {
263
267
  debug(`[${this._sessionId}]: FOLLOWER: Invalidate cache to ensure having the correct VOD!`);
@@ -278,6 +282,7 @@ class Session {
278
282
  if (!isLeader || vodReloaded) {
279
283
  debug(`[${this._sessionId}]: FOLLOWER: leader is alive, and has presumably updated currentVod. Clearing the cache now`);
280
284
  await this._sessionState.clearCurrentVodCache();
285
+ this.isSwitchingBackFromLive = false;
281
286
  return;
282
287
  }
283
288
  }
@@ -313,6 +318,7 @@ class Session {
313
318
  await this._playheadState.set("vodMediaSeqAudio", 0);
314
319
  await this._playheadState.set("playheadRef", Date.now());
315
320
  }
321
+ this.isSwitchingBackFromLive = false;
316
322
  }
317
323
 
318
324
  async getCurrentMediaSequenceSegments() {
@@ -350,6 +356,9 @@ class Session {
350
356
  if (!this._sessionState) {
351
357
  throw new Error('Session not ready');
352
358
  }
359
+
360
+ this.isSwitchingBackFromLive = true;
361
+
353
362
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
354
363
  if (isLeader) {
355
364
  await this._sessionState.set("mediaSeq", _mediaSeq);
@@ -357,6 +366,7 @@ class Session {
357
366
  await this._sessionState.set("discSeq", _discSeq);
358
367
  debug(`[${this._sessionId}]: Setting current media and discontinuity count -> [${_mediaSeq}]:[${_discSeq}]`);
359
368
  }
369
+ this.isSwitchingBackFromLive = false;
360
370
  }
361
371
 
362
372
  async getCurrentMediaAndDiscSequenceCount() {
@@ -395,16 +405,36 @@ class Session {
395
405
  if (!this._sessionState) {
396
406
  throw new Error('Session not ready');
397
407
  }
398
- const sessionState = await this._sessionState.getValues(["discSeq"]);
408
+ // Be sure that the leader is not in the middle of setting new vod data in store.
409
+ // Followers will never run this part...
410
+ let tries = 4;
411
+ while(tries > 0 && this.leaderIsSettingNextVod) {
412
+ debug(`[${this._sessionId}]: Leader is setting the next vod. Waiting 500ms_${tries}`);
413
+ await timer(500);
414
+ tries--;
415
+ }
416
+
417
+ const sessionState = await this._sessionState.getValues(["vodMediaSeqVideo", "discSeq"]);
399
418
  const playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo"]);
400
419
 
401
- if (playheadState.vodMediaSeqVideo === 0) {
420
+ if (playheadState.vodMediaSeqVideo > sessionState.vodMediaSeqVideo) {
421
+ debug(`[${this._sessionId}]: Recently Loaded Next Vod. PlayheadState not up-to-date. Return the last generated m3u8`);
422
+ const m3u8 = await this._playheadState.getLastM3u8();
423
+ if (m3u8) {
424
+ return m3u8;
425
+ } else {
426
+ debug(`[${this._sessionId}]: We don't have any previously generated m3u8`);
427
+ }
428
+ }
429
+
430
+ if (playheadState.vodMediaSeqVideo < 2) {
402
431
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
403
432
  if (!isLeader) {
404
- debug(`[${this._sessionId}]: Not a leader and first media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
433
+ debug(`[${this._sessionId}]: Not a leader and first|second media sequence in a VOD is requested. Invalidate cache to ensure having the correct VOD.`);
405
434
  await this._sessionState.clearCurrentVodCache(); // force reading up from shared store
406
435
  }
407
436
  }
437
+
408
438
  const currentVod = await this._sessionState.getCurrentVod();
409
439
  if (currentVod) {
410
440
  try {
@@ -450,8 +480,16 @@ class Session {
450
480
  }
451
481
 
452
482
  async incrementAsync() {
453
- await this._tickAsync();
483
+ await this._tickAsync();
454
484
  const isLeader = await this._sessionStateStore.isLeader(this._instanceId);
485
+ // Avoid reading old values if new ones are on their way...
486
+ let tries = 10;
487
+ while (tries > 0 && this.isSwitchingBackFromLive) {
488
+ debug(`[${this._sessionId}]: Leader is currently setting new data in store. Waiting 500ms_${tries}`);
489
+ await timer(500);
490
+ tries--;
491
+ }
492
+
455
493
  let sessionState = await this._sessionState.getValues(
456
494
  ["state", "mediaSeq", "discSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
457
495
  let playheadState = await this._playheadState.getValues(["mediaSeq", "vodMediaSeqVideo", "vodMediaSeqAudio"]);
@@ -487,12 +525,13 @@ class Session {
487
525
  sessionState.vodMediaSeqVideo = await this._sessionState.increment("vodMediaSeqVideo");
488
526
  sessionState.vodMediaSeqAudio = await this._sessionState.increment("vodMediaSeqAudio");
489
527
  }
490
-
528
+
491
529
  if (sessionState.vodMediaSeqVideo >= currentVod.getLiveMediaSequencesCount() - 1) {
492
530
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", currentVod.getLiveMediaSequencesCount() - 1);
493
531
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", currentVod.getLiveMediaSequencesCount() - 1);
494
532
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_NEXT_INIT);
495
533
  }
534
+
496
535
  playheadState.mediaSeq = await this._playheadState.set("mediaSeq", sessionState.mediaSeq);
497
536
  playheadState.vodMediaSeqVideo = await this._playheadState.set("vodMediaSeqVideo", sessionState.vodMediaSeqVideo);
498
537
  playheadState.vodMediaSeqAudio = await this._playheadState.set("vodMediaSeqAudio", sessionState.vodMediaSeqAudio);
@@ -727,11 +766,10 @@ class Session {
727
766
  length = currentVod.getLiveMediaSequencesCount();
728
767
  lastDiscontinuity = currentVod.getLastDiscontinuity();
729
768
  }
730
- await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
731
- await this._sessionState.setCurrentVod(slateVod);
732
-
733
769
  await this._sessionState.set("vodMediaSeqVideo", 0);
734
770
  await this._sessionState.set("vodMediaSeqAudio", 0);
771
+ await this._sessionState.set("state", SessionState.VOD_NEXT_INITIATING);
772
+ await this._sessionState.setCurrentVod(slateVod);
735
773
  await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
736
774
  await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
737
775
  await this._sessionState.set("slateCount", sessionState.slateCount + 1);
@@ -815,6 +853,10 @@ class Session {
815
853
  debug(`[${this._sessionId}]: ${currentVod.getDeltaTimes()}`);
816
854
  debug(`[${this._sessionId}]: ${currentVod.getPlayheadPositions()}`);
817
855
  //debug(newVod);
856
+ sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
857
+ sessionState.discSeq = await this._sessionState.set("discSeq", 0);
858
+ sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
859
+ sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
818
860
  await this._playheadState.set("playheadRef", Date.now());
819
861
  this.produceEvent({
820
862
  type: 'NOW_PLAYING',
@@ -825,10 +867,6 @@ class Session {
825
867
  });
826
868
  sessionState.state = await this._sessionState.set("state", SessionState.VOD_PLAYING);
827
869
  sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
828
- sessionState.mediaSeq = await this._sessionState.set("mediaSeq", 0);
829
- sessionState.discSeq = await this._sessionState.set("discSeq", 0);
830
- sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
831
- sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
832
870
  await this._sessionState.remove("nextVod");
833
871
  return;
834
872
  } else {
@@ -932,16 +970,19 @@ class Session {
932
970
  await ChaosMonkey.loadVod(loadPromise);
933
971
  cloudWatchLog(!this.cloudWatchLogging, 'engine-session',
934
972
  { event: 'loadVod', channel: this._sessionId, loadTimeMs: Date.now() - loadStart });
973
+ this.leaderIsSettingNextVod = true;
935
974
  debug(`[${this._sessionId}]: next VOD loaded (${newVod.getDeltaTimes()})`);
936
975
  debug(`[${this._sessionId}]: ${newVod.getPlayheadPositions()}`);
937
976
  currentVod = newVod;
938
977
  debug(`[${this._sessionId}]: msequences=${currentVod.getLiveMediaSequencesCount()}`);
939
- await this._sessionState.remove("nextVod");
940
- sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
941
978
  sessionState.vodMediaSeqVideo = await this._sessionState.set("vodMediaSeqVideo", 0);
942
979
  sessionState.vodMediaSeqAudio = await this._sessionState.set("vodMediaSeqAudio", 0);
943
980
  sessionState.mediaSeq = await this._sessionState.set("mediaSeq", sessionState.mediaSeq + length);
944
981
  sessionState.discSeq = await this._sessionState.set("discSeq", sessionState.discSeq + lastDiscontinuity);
982
+ debug(`[${this._sessionId}]: new sequence data set in store [${sessionState.mediaSeq}][${sessionState.discSeq}]`);
983
+ await this._sessionState.remove("nextVod");
984
+ sessionState.currentVod = await this._sessionState.setCurrentVod(currentVod, { ttl: currentVod.getDuration() * 1000 });
985
+ this.leaderIsSettingNextVod = false;
945
986
  await this._playheadState.set("playheadRef", Date.now());
946
987
  this.produceEvent({
947
988
  type: 'NOW_PLAYING',
@@ -242,24 +242,28 @@ class SessionLive {
242
242
  // Make it possible to add & share new segments
243
243
  this.allowedToSet = true;
244
244
  const allBws = Object.keys(segments);
245
- for (let i = 0; i < allBws.length; i++) {
246
- const bw = allBws[i];
247
- if (!this.vodSegments[bw]) {
248
- this.vodSegments[bw] = [];
249
- }
250
-
251
- if (segments[bw][0].discontinuity) {
252
- segments[bw].shift();
253
- }
245
+ if (this._isEmpty(this.vodSegments)) {
246
+ for (let i = 0; i < allBws.length; i++) {
247
+ const bw = allBws[i];
248
+ if (!this.vodSegments[bw]) {
249
+ this.vodSegments[bw] = [];
250
+ }
254
251
 
255
- for (let segIdx = 0; segIdx < segments[bw].length; segIdx++) {
256
- this.vodSegments[bw].push(segments[bw][segIdx]);
257
- }
258
- if (!segments[bw][segments[bw].length - 1].discontinuity) {
259
- this.vodSegments[bw].push({ discontinuity: true, cue: { in: true } });
260
- } else {
261
- segments[bw][segments[bw].length - 1]["cue"] = { in: true };
252
+ if (segments[bw][0].discontinuity) {
253
+ segments[bw].shift();
254
+ }
255
+
256
+ for (let segIdx = 0; segIdx < segments[bw].length; segIdx++) {
257
+ this.vodSegments[bw].push(segments[bw][segIdx]);
258
+ }
259
+ if (!segments[bw][segments[bw].length - 1].discontinuity) {
260
+ this.vodSegments[bw].push({ discontinuity: true, cue: { in: true } });
261
+ } else {
262
+ segments[bw][segments[bw].length - 1]["cue"] = { in: true };
263
+ }
262
264
  }
265
+ } else {
266
+ debug(`[${this.sessionId}]: 'vodSegments' not empty = Using 'transitSegs'`);
263
267
  }
264
268
 
265
269
  debug(`[${this.sessionId}]: Setting CurrentMediaSequenceSegments. First seg is: [${this.vodSegments[allBws[0]][0].uri}]`);
@@ -301,6 +305,11 @@ class SessionLive {
301
305
  if (leadersMediaSeqCount !== null) {
302
306
  this.mediaSeqCount = leadersMediaSeqCount;
303
307
  debug(`[${this.sessionId}]: Setting mediaSeqCount to: [${this.mediaSeqCount}]`);
308
+ const transitSegs = await this.sessionLiveState.get("transitSegs");
309
+ if (!this._isEmpty(transitSegs)) {
310
+ debug(`[${this.sessionId}]: Getting and loading 'transitSegs'`);
311
+ this.vodSegments = transitSegs;
312
+ }
304
313
  }
305
314
  if (leadersDiscSeqCount !== null) {
306
315
  this.discSeqCount = leadersDiscSeqCount;
@@ -1040,7 +1049,6 @@ class SessionLive {
1040
1049
  */
1041
1050
  _addLiveSegmentsToQueue(startIdx, playlistItems, baseUrl, liveTargetBandwidth) {
1042
1051
  for (let i = startIdx; i < playlistItems.length; i++) {
1043
- debug(`[${this.sessionId}]: Adding Live Segment(s) to Queue (for bw=${liveTargetBandwidth})`);
1044
1052
  let seg = {};
1045
1053
  let playlistItem = playlistItems[i];
1046
1054
  let segmentUri;
@@ -1108,6 +1116,7 @@ class SessionLive {
1108
1116
  seg["daterange"] = daterangeData;
1109
1117
  }
1110
1118
  // Push new Live Segments!
1119
+ debug(`[${this.sessionId}]: Adding Live Segment to Queue (for bw=${liveTargetBandwidth})\n${seg.uri ? seg.uri : ''}`);
1111
1120
  this.liveSegQueue[liveTargetBandwidth].push(seg);
1112
1121
  this.liveSegsForFollowers[liveTargetBandwidth].push(seg);
1113
1122
  }
package/engine/util.js CHANGED
@@ -1,29 +1,33 @@
1
- const { version } = require('../package.json');
1
+ const { version } = require("../package.json");
2
2
 
3
3
  const filterQueryParser = (filterQuery) => {
4
4
  const conditions = filterQuery.match(/\(([^\(\)]*?)\)/g);
5
5
 
6
6
  let filter = {};
7
+
8
+ if (!conditions) {
9
+ return filter;
10
+ }
7
11
  conditions.map((c) => {
8
- const m = c.match(/\(type=="(.*?)"(&&|\|\|)(.*?)(<|>)(.*)\)/);
12
+ const m = c.match(/\(type=="(.*?)"(AND|\|\|)(.*?)(<|>)(.*)\)/);
9
13
  if (m) {
10
14
  const type = m[1];
11
15
  const operator = m[2];
12
16
  const key = m[3];
13
17
  const comp = m[4];
14
18
  const value = m[5];
15
-
19
+
16
20
  if (!filter[type]) {
17
21
  filter[type] = {};
18
22
  }
19
- if (operator === "&&") {
23
+ if (operator === "AND") {
20
24
  if (!filter[type][key]) {
21
25
  filter[type][key] = {};
22
26
  }
23
27
  if (comp === "<") {
24
- filter[type][key].high = parseInt(value, 10);
28
+ filter[type][key].high = parseInt(value, 10);
25
29
  } else if (comp === ">") {
26
- filter[type][key].low = parseInt(value, 10);
30
+ filter[type][key].low = parseInt(value, 10);
27
31
  }
28
32
  }
29
33
  }
@@ -32,42 +36,85 @@ const filterQueryParser = (filterQuery) => {
32
36
  };
33
37
 
34
38
  const applyFilter = (profiles, filter) => {
35
- return profiles.filter(profile => {
36
- if (filter.video && filter.video.systemBitrate) {
37
- return (profile.bw >= filter.video.systemBitrate.low &&
38
- profile.bw <= filter.video.systemBitrate.high);
39
- } else if (filter.video && filter.video.height) {
40
- return (profile.resolution[1] >= filter.video.height.low &&
41
- profile.resolution[1] <= filter.video.height.high);
39
+ let filteredProfiles = {};
40
+ const supportedFilterKeys = ["systemBitrate", "height"];
41
+
42
+ if (!filter.video) {
43
+ return profiles;
44
+ }
45
+
46
+ const keys = Object.keys(filter.video);
47
+ if (supportedFilterKeys.every((supportedKey) => !keys.includes(supportedKey))) {
48
+ return profiles;
49
+ }
50
+
51
+ if (filter.video.systemBitrate) {
52
+ filteredProfiles = profiles.filter((profile) => {
53
+ if (filter.video.systemBitrate.low && filter.video.systemBitrate.high) {
54
+ return profile.bw >= filter.video.systemBitrate.low && profile.bw <= filter.video.systemBitrate.high;
55
+ } else if (filter.video.systemBitrate.low && !filter.video.systemBitrate.high) {
56
+ return profile.bw >= filter.video.systemBitrate.low;
57
+ } else if (!filter.video.systemBitrate.low && filter.video.systemBitrate.high) {
58
+ return profile.bw <= filter.video.systemBitrate.high;
59
+ }
60
+ return true;
61
+ });
62
+ }
63
+
64
+ if (filter.video.height) {
65
+ let toFilter = profiles;
66
+ if (!ItemIsEmpty(filteredProfiles)) {
67
+ toFilter = filteredProfiles;
42
68
  }
69
+ filteredProfiles = toFilter.filter((profile) => {
70
+ if (filter.video.height.low && filter.video.height.high) {
71
+ return profile.resolution[1] >= filter.video.height.low && profile.resolution[1] <= filter.video.height.high;
72
+ } else if (filter.video.height.low) {
73
+ return profile.resolution[1] >= filter.video.height.low;
74
+ } else if (filter.video.height.high) {
75
+ return profile.resolution[1] <= filter.video.height.high;
76
+ }
77
+ return true;
78
+ });
79
+ }
80
+
81
+ return filteredProfiles;
82
+ };
83
+
84
+ const ItemIsEmpty = (obj) => {
85
+ if (!obj) {
43
86
  return true;
44
- });
87
+ }
88
+ for (var key in obj) {
89
+ if (obj.hasOwnProperty(key)) {
90
+ return false;
91
+ }
92
+ }
93
+ return true;
45
94
  };
46
95
 
47
96
  const cloudWatchLog = (silent, type, logEntry) => {
48
97
  if (!silent) {
49
98
  logEntry.type = type;
50
- logEntry.time = (new Date()).toISOString();
99
+ logEntry.time = new Date().toISOString();
51
100
  console.log(JSON.stringify(logEntry));
52
101
  }
53
102
  };
54
103
 
55
104
  const m3u8Header = (instanceId) => {
56
105
  let m3u8 = "";
57
- m3u8 += `## Created with Eyevinn Channel Engine library (version=${version}${instanceId ? "<" + instanceId + ">" : "" })\n`;
106
+ m3u8 += `## Created with Eyevinn Channel Engine library (version=${version}${instanceId ? "<" + instanceId + ">" : ""})\n`;
58
107
  m3u8 += "## https://www.npmjs.com/package/eyevinn-channel-engine\n";
59
108
  return m3u8;
60
109
  };
61
110
 
62
111
  const toHHMMSS = (secs) => {
63
- var sec_num = parseInt(secs, 10)
64
- var hours = Math.floor(sec_num / 3600)
65
- var minutes = Math.floor(sec_num / 60) % 60
66
- var seconds = sec_num % 60
67
-
68
- return [hours,minutes,seconds]
69
- .map(v => v < 10 ? "0" + v : v)
70
- .join(":")
112
+ var sec_num = parseInt(secs, 10);
113
+ var hours = Math.floor(sec_num / 3600);
114
+ var minutes = Math.floor(sec_num / 60) % 60;
115
+ var seconds = sec_num % 60;
116
+
117
+ return [hours, minutes, seconds].map((v) => (v < 10 ? "0" + v : v)).join(":");
71
118
  };
72
119
 
73
120
  const logerror = (sessionId, err) => {
@@ -81,5 +128,5 @@ module.exports = {
81
128
  cloudWatchLog,
82
129
  m3u8Header,
83
130
  toHHMMSS,
84
- logerror
85
- }
131
+ logerror,
132
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyevinn-channel-engine",
3
- "version": "3.1.3",
3
+ "version": "3.1.6",
4
4
  "description": "OTT TV Channel Engine",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -2,7 +2,7 @@ const { filterQueryParser, applyFilter } = require('../../engine/util.js');
2
2
 
3
3
  describe("Filter Query parser", () => {
4
4
  it("can handle systemBitrate low/high range", () => {
5
- const filterQuery = `(type=="video"&&systemBitrate>100000)&&(type=="video"&&systemBitrate<800000)`;
5
+ const filterQuery = `(type=="video"ANDsystemBitrate>100000)AND(type=="video"ANDsystemBitrate<800000)`;
6
6
  const filter = filterQueryParser(filterQuery);
7
7
 
8
8
  expect(filter.video.systemBitrate.low).toEqual(100000);
@@ -10,12 +10,19 @@ describe("Filter Query parser", () => {
10
10
  });
11
11
 
12
12
  it("can handle resolution low/high range", () => {
13
- const filterQuery = `(type=="video"&&height>600)&&(type=="video"&&height<1080)`;
13
+ const filterQuery = `(type=="video"ANDheight>600)AND(type=="video"ANDheight<1080)`;
14
14
  const filter = filterQueryParser(filterQuery);
15
15
 
16
16
  expect(filter.video.height.low).toEqual(600);
17
17
  expect(filter.video.height.high).toEqual(1080);
18
18
  });
19
+
20
+ it("can handle a faulty filter query", () => {
21
+ const filterQuery = `type=video&height=600&type=video&height=1080`;
22
+ const filter = filterQueryParser(filterQuery);
23
+
24
+ expect(filter).toEqual({});
25
+ });
19
26
  });
20
27
 
21
28
  describe("Profile filter", () => {
@@ -60,10 +67,68 @@ describe("Profile filter", () => {
60
67
 
61
68
 
62
69
  it ("can filter out video tracks with height min 200 and max 400", () => {
63
- const filterQuery = `(type=="video"&&height>200)&&(type=="video"&&height<400)`;
70
+ const filterQuery = `(type=="video"ANDheight>200)AND(type=="video"ANDheight<400)`;
71
+ const filter = filterQueryParser(filterQuery);
72
+
73
+ const filteredProfiles = applyFilter(PROFILE, filter);
74
+ expect(filteredProfiles.length).toEqual(2);
75
+ });
76
+
77
+ it ("can filter out video tracks with bitrate min 1414000 and max 3232000", () => {
78
+ const filterQuery = `(type=="video"ANDsystemBitrate>1414000)AND(type=="video"ANDsystemBitrate<3232000)`;
79
+ const filter = filterQueryParser(filterQuery);
80
+
81
+ const filteredProfiles = applyFilter(PROFILE, filter);
82
+ expect(filteredProfiles.length).toEqual(1);
83
+ expect(filteredProfiles[0].bw).toEqual(2323000);
84
+ });
85
+
86
+ it ("can filter out video tracks with bitrate min 1414000 and height min 300", () => {
87
+ const filterQuery = `(type=="video"ANDsystemBitrate>1414000)AND(type=="video"ANDheight>300)`;
88
+ const filter = filterQueryParser(filterQuery);
89
+
90
+ const filteredProfiles = applyFilter(PROFILE, filter);
91
+ expect(filteredProfiles.length).toEqual(1);
92
+ expect(filteredProfiles[0].bw).toEqual(6134000);
93
+ });
94
+
95
+ it ("can filter out video tracks with bitrate min 1414000", () => {
96
+ const filterQuery = `(type=="video"ANDsystemBitrate>1414000)`;
64
97
  const filter = filterQueryParser(filterQuery);
65
98
 
66
99
  const filteredProfiles = applyFilter(PROFILE, filter);
67
100
  expect(filteredProfiles.length).toEqual(2);
101
+ expect(filteredProfiles[1].bw).toEqual(2323000);
102
+ expect(filteredProfiles[0].bw).toEqual(6134000);
103
+ });
104
+
105
+ it ("can filter out video tracks with bitrate max 1414000", () => {
106
+ const filterQuery = `(type=="video"ANDsystemBitrate<1414000)`;
107
+ const filter = filterQueryParser(filterQuery);
108
+
109
+ const filteredProfiles = applyFilter(PROFILE, filter);
110
+ expect(filteredProfiles.length).toEqual(1);
111
+ expect(filteredProfiles[0].bw).toEqual(1313000);
68
112
  });
113
+
114
+ it ("can filter out video tracks with height min 250", () => {
115
+ const filterQuery = `(type=="video"ANDheight>250)`;
116
+ const filter = filterQueryParser(filterQuery);
117
+
118
+ const filteredProfiles = applyFilter(PROFILE, filter);
119
+ expect(filteredProfiles.length).toEqual(2);
120
+ expect(filteredProfiles[1].bw).toEqual(2323000);
121
+ expect(filteredProfiles[0].bw).toEqual(6134000);
122
+ });
123
+
124
+ it ("can filter out video tracks with height max 300", () => {
125
+ const filterQuery = `(type=="video"ANDheight<300)`;
126
+ const filter = filterQueryParser(filterQuery);
127
+
128
+ const filteredProfiles = applyFilter(PROFILE, filter);
129
+ expect(filteredProfiles.length).toEqual(2);
130
+ expect(filteredProfiles[1].bw).toEqual(1313000);
131
+ expect(filteredProfiles[0].bw).toEqual(2323000);
132
+ });
133
+
69
134
  });