mlbserver 2021.10.1 → 2021.10.10

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.
Files changed (4) hide show
  1. package/README.md +2 -1
  2. package/index.js +82 -63
  3. package/package.json +1 -1
  4. package/session.js +36 -14
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mlbserver
2
2
 
3
- Current version 2021.10.01
3
+ Current version 2021.10.10
4
4
 
5
5
  Credit to https://github.com/tonycpsu/streamglob and https://github.com/mafintosh/hls-decryptor
6
6
 
@@ -41,6 +41,7 @@ Advanced command line options:
41
41
  --ffmpeg_logging (if present, logs all ffmpeg output -- useful for experimenting or troubleshooting)
42
42
  --page_username (username to protect pages; default is no protection)
43
43
  --page_password (password to protect pages; default is no protection)
44
+ --content_protect (specify the content protection key to include as a URL parameter, if page protection is enabled)
44
45
  ```
45
46
 
46
47
  You may want to experiment with different ffmpeg hardware encoders depending on your system as described at https://stackoverflow.com/a/50703794
package/index.js CHANGED
@@ -57,6 +57,7 @@ const SAMPLE_STREAM_URL = 'https://www.radiantmediaplayer.com/media/rmp-segment/
57
57
  // --ffmpeg_logging (if present, logs all ffmpeg output -- useful for experimenting or troubleshooting)
58
58
  // --page_username (username to protect pages; default is no protection)
59
59
  // --page_password (password to protect pages; default is no protection)
60
+ // --content_protect (specify the content protection key to include as a URL parameter, if page protection is enabled)
60
61
  var argv = minimist(process.argv, {
61
62
  alias: {
62
63
  p: 'port',
@@ -67,7 +68,7 @@ var argv = minimist(process.argv, {
67
68
  v: 'version'
68
69
  },
69
70
  boolean: ['ffmpeg_logging', 'debug', 'logout', 'session', 'cache', 'version'],
70
- string: ['port', 'account_username', 'account_password', 'multiview_port', 'multiview_path', 'ffmpeg_path', 'ffmpeg_encoder', 'page_username', 'page_password']
71
+ string: ['port', 'account_username', 'account_password', 'multiview_port', 'multiview_path', 'ffmpeg_path', 'ffmpeg_encoder', 'page_username', 'page_password', 'content_protect']
71
72
  })
72
73
 
73
74
  // Version
@@ -147,23 +148,17 @@ multiview_app.listen(multiview_port)
147
148
 
148
149
  // Listen for stream requests
149
150
  app.get('/stream.m3u8', async function(req, res) {
151
+ if ( ! (await protect(req, res)) ) return
152
+
150
153
  try {
151
154
  session.log('stream.m3u8 request : ' + req.url)
152
155
 
153
- if ( session.protection.content_protect ) {
154
- if ( !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
155
- session.log('stream request rejected due to missing/invalid content_protect value')
156
- res.end('')
157
- return
158
- }
159
- }
160
-
161
156
  let mediaId
162
157
  let contentId
163
158
  let streamURL
164
159
  let options = {}
165
160
  let urlArray = req.url.split('?')
166
- if ( (urlArray.length == 1) || ((session.data.scan_mode == 'on') && req.query.team) || (!req.query.src && !req.query.highlight_src && !req.query.type && !req.query.mediaId && !req.query.contentId) ) {
161
+ if ( (urlArray.length == 1) || ((session.data.scan_mode == 'on') && req.query.team) || (!req.query.team && !req.query.src && !req.query.highlight_src && !req.query.type && !req.query.id && !req.query.mediaId && !req.query.contentId) ) {
167
162
  // load a sample encrypted HLS stream
168
163
  session.log('loading sample stream')
169
164
  options.resolution = 'adaptive'
@@ -333,6 +328,11 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
333
328
  inning_half = VALID_INNING_HALF[1]
334
329
  }
335
330
 
331
+ var content_protect = ''
332
+ if ( session.protection.content_protect ) {
333
+ content_protect = '&content_protect=' + session.protection.content_protect
334
+ }
335
+
336
336
  // Some variables for controlling audio/video stream selection, if specified
337
337
  var video_track_matched = false
338
338
  var audio_track_matched = false
@@ -344,10 +344,22 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
344
344
  resolution = resolution.slice(0, 3)
345
345
  }
346
346
 
347
+ var segment_found = false
348
+
347
349
  body = body
348
350
  .map(function(line) {
349
351
  let newurl = ''
350
352
 
353
+ // Check if segment playlist instead of master
354
+ if ( line.startsWith('#EXTINF:') ) {
355
+ session.debuglog('segment playlist instead of master')
356
+ segment_found = true
357
+ return line
358
+ } else if ( segment_found ) {
359
+ segment_found = false
360
+ return 'ts?url='+encodeURIComponent(url.resolve(streamURL, line.trim())) + content_protect
361
+ }
362
+
351
363
  // Omit keyframe tracks
352
364
  if (line.indexOf('#EXT-X-I-FRAME-STREAM-INF:') === 0) {
353
365
  return
@@ -363,7 +375,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
363
375
  if ( audio_track_matched ) return
364
376
  if ( audio_url != '' ) {
365
377
  audio_track_matched = true
366
- return '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Audio",AUTOSELECT=YES,DEFAULT=YES,URI="' + audio_url + '"'
378
+ return '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Audio",AUTOSELECT=YES,DEFAULT=YES,URI="' + audio_url + content_protect + '"'
367
379
  }
368
380
  if ( audio_track == 'none') return
369
381
  if ( (resolution == 'none') && (line.indexOf(',URI=') < 0) ) return
@@ -385,6 +397,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
385
397
  if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
386
398
  if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
387
399
  if ( contentId ) newurl += '&contentId=' + contentId
400
+ newurl += content_protect
388
401
  if ( resolution == 'none' ) {
389
402
  audio_track_matched = true
390
403
  return line.replace(parsed[0],'') + "\n" + '#EXT-X-STREAM-INF:BANDWIDTH=50000,CODECS="mp4a.40.2",AUDIO="aac"' + "\n" + newurl
@@ -430,6 +443,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
430
443
  if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
431
444
  if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
432
445
  if ( contentId ) newurl += '&contentId=' + contentId
446
+ newurl += content_protect
433
447
  return 'playlist?url='+newurl
434
448
  }
435
449
  })
@@ -453,7 +467,9 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
453
467
 
454
468
 
455
469
  // Listen for playlist requests
456
- app.get('/playlist', function(req, res) {
470
+ app.get('/playlist', async function(req, res) {
471
+ if ( ! (await protect(req, res)) ) return
472
+
457
473
  session.debuglog('playlist request : ' + req.url)
458
474
 
459
475
  delete req.headers.host
@@ -478,6 +494,11 @@ app.get('/playlist', function(req, res) {
478
494
  var key
479
495
  var iv
480
496
 
497
+ var content_protect = ''
498
+ if ( session.protection.content_protect ) {
499
+ content_protect = '&content_protect=' + session.protection.content_protect
500
+ }
501
+
481
502
  if ( (contentId) && ((inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0]))) {
482
503
  // If inning offsets don't exist, we'll force those options off
483
504
  if ( (typeof session.temp_cache[contentId] === 'undefined') || (typeof session.temp_cache[contentId].inning_offsets === 'undefined') ) {
@@ -569,8 +590,8 @@ app.get('/playlist', function(req, res) {
569
590
 
570
591
  if (line[0] === '#') return line
571
592
 
572
- if ( key ) return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim()))+'&key='+encodeURIComponent(key)+'&iv='+encodeURIComponent(iv)
573
- else return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim()))
593
+ if ( key ) return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim()))+'&key='+encodeURIComponent(key)+'&iv='+encodeURIComponent(iv) + content_protect
594
+ else return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim())) + content_protect
574
595
  })
575
596
  .filter(function(line) {
576
597
  return line
@@ -592,7 +613,9 @@ app.get('/playlist', function(req, res) {
592
613
  })
593
614
 
594
615
  // Listen for ts requests (video segments) and decode them
595
- app.get('/ts', function(req, res) {
616
+ app.get('/ts', async function(req, res) {
617
+ if ( ! (await protect(req, res)) ) return
618
+
596
619
  session.debuglog('ts request : ' + req.url)
597
620
 
598
621
  delete req.headers.host
@@ -622,25 +645,29 @@ app.get('/ts', function(req, res) {
622
645
  })
623
646
  })
624
647
 
625
- // Protect pages by password
648
+ // Protect pages by password, or content by content_protect url parameter
626
649
  async function protect(req, res) {
627
650
  if (argv.page_username && argv.page_password) {
628
- const reject = () => {
629
- res.setHeader('www-authenticate', 'Basic')
630
- res.error(401, ' Not Authorized')
631
- return false
632
- }
651
+ if ( !session.protection.content_protect || !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
652
+ if ( !session.protection.content_protect || !req.query.content_protect || !req.query.content_protect[0] || (req.query.content_protect[0] != session.protection.content_protect) ) {
653
+ const reject = () => {
654
+ res.setHeader('www-authenticate', 'Basic')
655
+ res.error(401, ' Not Authorized')
656
+ return false
657
+ }
633
658
 
634
- const authorization = req.headers.authorization
659
+ const authorization = req.headers.authorization
635
660
 
636
- if(!authorization) {
637
- return reject()
638
- }
661
+ if(!authorization) {
662
+ return reject()
663
+ }
639
664
 
640
- const [username, password] = Buffer.from(authorization.replace('Basic ', ''), 'base64').toString().split(':')
665
+ const [username, password] = Buffer.from(authorization.replace('Basic ', ''), 'base64').toString().split(':')
641
666
 
642
- if(! (username === argv.page_username && password === argv.page_password)) {
643
- return reject()
667
+ if(! (username === argv.page_username && password === argv.page_password)) {
668
+ return reject()
669
+ }
670
+ }
644
671
  }
645
672
  }
646
673
  return true
@@ -1034,8 +1061,13 @@ app.get('/', async function(req, res) {
1034
1061
  if ( ((typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType) == 'undefined') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType.indexOf('IN_MARKET_') == -1) ) {
1035
1062
  if ( ((typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].language) == 'undefined') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].language == language) ) {
1036
1063
  let teamabbr
1037
- if ( ((typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType) != 'undefined') && (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType == 'NATIONAL') ) {
1064
+ if ( (((typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType) != 'undefined') && (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType == 'NATIONAL')) || ((mediaType == 'MLBTV') && cache_data.dates[0].games[j].gameUtils.isPostSeason) ) {
1038
1065
  teamabbr = 'NATIONAL'
1066
+ if ( (typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType) != 'undefined' ) {
1067
+ if ( cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'AWAY' ) {
1068
+ teamabbr += '*'
1069
+ }
1070
+ }
1039
1071
  } else {
1040
1072
  teamabbr = hometeam
1041
1073
  if ( cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'AWAY' ) {
@@ -1075,7 +1107,13 @@ app.get('/', async function(req, res) {
1075
1107
  }
1076
1108
  }
1077
1109
  querystring += content_protect_b
1078
- body += teamabbr + ': <a href="' + thislink + querystring + '">' + station + '</a>'
1110
+ multiviewquerystring += content_protect_b
1111
+ if ( teamabbr.endsWith('*') ) {
1112
+ body += '<span class="tooltip">' + teamabbr + '<span class="tooltiptext">* includes the away team\'s alternate audio, instead of the default home team\'s</span></span>'
1113
+ } else {
1114
+ body += teamabbr
1115
+ }
1116
+ body += ': <a href="' + thislink + querystring + '">' + station + '</a>'
1079
1117
  if ( mediaType == 'MLBTV' ) {
1080
1118
  body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1081
1119
  }
@@ -1098,7 +1136,12 @@ app.get('/', async function(req, res) {
1098
1136
  body += ', '
1099
1137
  }
1100
1138
  } else {
1101
- body += teamabbr + ': ' + station + ', '
1139
+ if ( teamabbr.endsWith('*') ) {
1140
+ body += '<span class="tooltip">' + teamabbr + '<span class="tooltiptext">* will include the away team\'s alternate audio, instead of the default home team\'s</span></span>'
1141
+ } else {
1142
+ body += teamabbr
1143
+ }
1144
+ body += ': ' + station + ', '
1102
1145
  }
1103
1146
  }
1104
1147
  }
@@ -1416,15 +1459,9 @@ app.get('/chromecast.html', async function(req, res) {
1416
1459
 
1417
1460
  // Listen for channels.m3u playlist request
1418
1461
  app.get('/channels.m3u', async function(req, res) {
1419
- session.log('channels.m3u request : ' + req.url)
1462
+ if ( ! (await protect(req, res)) ) return
1420
1463
 
1421
- if ( session.protection.content_protect ) {
1422
- if ( !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
1423
- session.log('playlist request rejected due to missing/invalid content_protect value')
1424
- res.end('')
1425
- return
1426
- }
1427
- }
1464
+ session.log('channels.m3u request : ' + req.url)
1428
1465
 
1429
1466
  let mediaType = 'Video'
1430
1467
  if ( req.query.mediaType ) {
@@ -1465,15 +1502,9 @@ app.get('/channels.m3u', async function(req, res) {
1465
1502
 
1466
1503
  // Listen for guide.xml request
1467
1504
  app.get('/guide.xml', async function(req, res) {
1468
- session.log('guide.xml request : ' + req.url)
1505
+ if ( ! (await protect(req, res)) ) return
1469
1506
 
1470
- if ( session.protection.content_protect ) {
1471
- if ( !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
1472
- session.log('xml request rejected due to missing/invalid content_protect value')
1473
- res.end('')
1474
- return
1475
- }
1476
- }
1507
+ session.log('guide.xml request : ' + req.url)
1477
1508
 
1478
1509
  let mediaType = 'Video'
1479
1510
  if ( req.query.mediaType ) {
@@ -1498,15 +1529,9 @@ app.get('/guide.xml', async function(req, res) {
1498
1529
 
1499
1530
  // Listen for image requests
1500
1531
  app.get('/image.svg', async function(req, res) {
1501
- session.debuglog('image request : ' + req.url)
1532
+ if ( ! (await protect(req, res)) ) return
1502
1533
 
1503
- if ( session.protection.content_protect ) {
1504
- if ( !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
1505
- session.debuglog('image request rejected due to missing/invalid content_protect value')
1506
- res.end('')
1507
- return
1508
- }
1509
- }
1534
+ session.debuglog('image request : ' + req.url)
1510
1535
 
1511
1536
  let teamId = 'MLB'
1512
1537
  if ( req.query.teamId ) {
@@ -1521,15 +1546,9 @@ app.get('/image.svg', async function(req, res) {
1521
1546
 
1522
1547
  // Listen for favicon requests
1523
1548
  app.get('/favicon.svg', async function(req, res) {
1524
- session.debuglog('favicon request : ' + req.url)
1549
+ if ( ! (await protect(req, res)) ) return
1525
1550
 
1526
- if ( session.protection.content_protect ) {
1527
- if ( !req.query.content_protect || (req.query.content_protect != session.protection.content_protect) ) {
1528
- session.debuglog('image request rejected due to missing/invalid content_protect value')
1529
- res.end('')
1530
- return
1531
- }
1532
- }
1551
+ session.debuglog('favicon request : ' + req.url)
1533
1552
 
1534
1553
  var body = await session.getImage('MLB')
1535
1554
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2021.10.01",
3
+ "version": "2021.10.10",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -66,11 +66,19 @@ class sessionClass {
66
66
  // Read protection data from file, if present
67
67
  this.protection = this.readFileToJson(PROTECTION_FILE) || {}
68
68
 
69
- if ( !this.protection.content_protect ) {
70
- this.log('generating new content protection value')
71
- this.log('** YOU WILL NEED TO UPDATE ANY CONTENT URLS YOU HAVE COPIED OUTSIDE OF MLBSERVER **')
72
- this.protection.content_protect = this.getRandomString(this.getRandomInteger(32,64))
73
- this.save_protection()
69
+ // Check if content_protect key was provided and if it is different from the stored one
70
+ if ( argv.content_protect && (argv.content_protect != this.protection.content_protect) ) {
71
+ this.log('using specified content protection key')
72
+ this.log('you may need to update any content URLs you have copied outside of mlbserver')
73
+ this.protection.content_protect = argv.content_protect
74
+ } else {
75
+ // Generate a content_protect key if it doesn't exist
76
+ if ( !this.protection.content_protect ) {
77
+ this.log('generating new content protection key')
78
+ this.log('** YOU WILL NEED TO UPDATE ANY CONTENT URLS YOU HAVE COPIED OUTSIDE OF MLBSERVER **')
79
+ this.protection.content_protect = this.getRandomString(this.getRandomInteger(32,64))
80
+ this.save_protection()
81
+ }
74
82
  }
75
83
  }
76
84
 
@@ -348,7 +356,7 @@ class sessionClass {
348
356
  try {
349
357
  fs.unlinkSync(CREDENTIALS_FILE)
350
358
  } catch(e){
351
- this.halt('logout error : ' + e.message)
359
+ this.debuglog('credentials cannot be cleared or do not exist yet : ' + e.message)
352
360
  }
353
361
  }
354
362
 
@@ -357,7 +365,7 @@ class sessionClass {
357
365
  fs.unlinkSync(COOKIE_FILE)
358
366
  fs.unlinkSync(DATA_FILE)
359
367
  } catch(e){
360
- this.halt('reset session error : ' + e.message)
368
+ this.debuglog('session cannot be cleared or does not exist yet : ' + e.message)
361
369
  }
362
370
  }
363
371
 
@@ -365,7 +373,7 @@ class sessionClass {
365
373
  try {
366
374
  fs.unlinkSync(CACHE_FILE)
367
375
  } catch(e){
368
- this.halt('clear cache error : ' + e.message)
376
+ this.debuglog('cache cannot be cleared or does not exist yet : ' + e.message)
369
377
  }
370
378
  }
371
379
 
@@ -397,7 +405,7 @@ class sessionClass {
397
405
  })
398
406
  }
399
407
  } catch(e){
400
- this.halt('clear multiview files error : ' + e.message)
408
+ this.debuglog('clear multiview files error : ' + e.message)
401
409
  }
402
410
  }
403
411
  }
@@ -884,8 +892,8 @@ class sessionClass {
884
892
  'content-type': 'application/json'
885
893
  },
886
894
  json: {
887
- 'username': this.credentials.account_username || this.halt('missing username'),
888
- 'password': this.credentials.account_password || this.halt('missing password'),
895
+ 'username': this.credentials.account_username || this.halt('missing account username'),
896
+ 'password': this.credentials.account_password || this.halt('missing account password'),
889
897
  'options': {
890
898
  'multiOptionalFactorEnroll': false,
891
899
  'warnBeforePasswordExpired': true
@@ -927,13 +935,13 @@ class sessionClass {
927
935
  let mediaId = false
928
936
  let contentId = false
929
937
 
930
- // First check if cached day data is available and non-expired
938
+ // First check if national game or if cached day data is available and non-expired
931
939
  // if not, just get data for this team
932
940
  let cache_data
933
941
  let cache_name = gameDate
934
942
  let cache_file = path.join(CACHE_DIRECTORY, gameDate+'.json')
935
943
  let currentDate = new Date()
936
- if ( fs.existsSync(cache_file) && this.cache && this.cache.dates && this.cache.dates[cache_name] && this.cache.dates[cache_name].dateCacheExpiry && (currentDate <= new Date(this.cache.dates[cache_name].dateCacheExpiry)) ) {
944
+ if ( (team.toUpperCase().indexOf('NATIONAL.') == 0) || (fs.existsSync(cache_file) && this.cache && this.cache.dates && this.cache.dates[cache_name] && this.cache.dates[cache_name].dateCacheExpiry && (currentDate <= new Date(this.cache.dates[cache_name].dateCacheExpiry))) ) {
937
945
  cache_data = await this.getDayData(gameDate)
938
946
  } else {
939
947
  cache_data = await this.getDayData(gameDate, team)
@@ -950,7 +958,7 @@ class sessionClass {
950
958
  for (var x = 0; x < cache_data.dates[0].games[j].content.media.epg[k].items.length; x++) {
951
959
  if ( (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON') || ((mediaDate) && ((cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE') || (cache_data.dates[0].games[j].status.abstractGameState == 'Final'))) ) {
952
960
  if ( ((typeof cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType) == 'undefined') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaFeedType.indexOf('IN_MARKET_') == -1) ) {
953
- if ( (team.toUpperCase().indexOf('NATIONAL.') == 0) && (cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'NATIONAL') ) {
961
+ if ( (team.toUpperCase().indexOf('NATIONAL.') == 0) && ((cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'NATIONAL') || ((mediaType == 'MLBTV') && cache_data.dates[0].games[j].gameUtils.isPostSeason)) ) {
954
962
  nationalCount += 1
955
963
  let nationalArray = team.split('.')
956
964
  if ( (nationalArray.length == 2) && (nationalArray[1] == nationalCount) ) {
@@ -1234,6 +1242,9 @@ class sessionClass {
1234
1242
  for (var x = 0; x < cache_data.dates[i].games[j].content.media.epg[k].items.length; x++) {
1235
1243
  if ( ((((typeof cache_data.dates[i].games[j].content.media.epg[k].items[x].mediaFeedType) == 'undefined') || (cache_data.dates[i].games[j].content.media.epg[k].items[x].mediaFeedType.indexOf('IN_MARKET_') == -1)) && (((typeof cache_data.dates[i].games[j].content.media.epg[k].items[x].language) == 'undefined') || (cache_data.dates[i].games[j].content.media.epg[k].items[x].language != 'es'))) ) {
1236
1244
  let teamType = cache_data.dates[i].games[j].content.media.epg[k].items[x][mediaFeedType]
1245
+ if ( (mediaType == 'MLBTV') && cache_data.dates[i].games[j].gameUtils.isPostSeason ) {
1246
+ teamType = 'NATIONAL'
1247
+ }
1237
1248
  let team
1238
1249
  let opponent_team
1239
1250
  if ( teamType == 'NATIONAL' ) {
@@ -1388,6 +1399,9 @@ class sessionClass {
1388
1399
  for (var x = 0; x < cache_data.dates[i].games[j].content.media.epg[k].items.length; x++) {
1389
1400
  if ( ((((typeof cache_data.dates[i].games[j].content.media.epg[k].items[x].mediaFeedType) == 'undefined') || (cache_data.dates[i].games[j].content.media.epg[k].items[x].mediaFeedType.indexOf('IN_MARKET_') == -1)) && (((typeof cache_data.dates[i].games[j].content.media.epg[k].items[x].language) == 'undefined') || (cache_data.dates[i].games[j].content.media.epg[k].items[x].language != 'es'))) ) {
1390
1401
  let teamType = cache_data.dates[i].games[j].content.media.epg[k].items[x][mediaFeedType]
1402
+ if ( (mediaType == 'MLBTV') && cache_data.dates[i].games[j].gameUtils.isPostSeason ) {
1403
+ teamType = 'NATIONAL'
1404
+ }
1391
1405
  let team
1392
1406
  let opponent_team
1393
1407
  if ( teamType == 'NATIONAL' ) {
@@ -1449,6 +1463,14 @@ class sessionClass {
1449
1463
  }
1450
1464
  description += '. '
1451
1465
  }
1466
+ if ( teamType == 'NATIONAL' ) {
1467
+ if ( cache_data.dates[i].games[j].content.media.epg[k].items[x][mediaFeedType] == 'AWAY' ) {
1468
+ description += cache_data.dates[i].games[j].teams['away'].team.teamName
1469
+ } else {
1470
+ description += cache_data.dates[i].games[j].teams['home'].team.teamName
1471
+ }
1472
+ description += ' alternate audio. '
1473
+ }
1452
1474
 
1453
1475
  let gameDate = new Date(cache_data.dates[i].games[j].gameDate)
1454
1476
  let gameHours = 3