mlbserver 2021.10.11 → 2022.3.22

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 +1 -1
  2. package/index.js +259 -102
  3. package/package.json +1 -1
  4. package/session.js +351 -269
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mlbserver
2
2
 
3
- Current version 2021.10.11
3
+ Current version 2022.03.22
4
4
 
5
5
  Credit to https://github.com/tonycpsu/streamglob and https://github.com/mafintosh/hls-decryptor
6
6
 
package/index.js CHANGED
@@ -37,7 +37,7 @@ const DEFAULT_MULTIVIEW_AUDIO_TRACK = 'English'
37
37
  const VALID_SKIP = [ 'off', 'breaks', 'pitches' ]
38
38
  const VALID_FORCE_VOD = [ 'off', 'on' ]
39
39
 
40
- const SAMPLE_STREAM_URL = 'https://www.radiantmediaplayer.com/media/rmp-segment/bbb-abr-aes/playlist.m3u8'
40
+ const SAMPLE_STREAM_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
41
41
 
42
42
  // Basic command line arguments, if specified:
43
43
  // --port or -p (primary port to run on; defaults to 9999 if not specified)
@@ -72,7 +72,9 @@ var argv = minimist(process.argv, {
72
72
  })
73
73
 
74
74
  // Version
75
- if (argv.version) return console.log(require('./package').version)
75
+ var version = require('./package').version
76
+ console.log('Version ' + version)
77
+ if (argv.version) return
76
78
 
77
79
  // Declare a session, pass arguments to it
78
80
  var session = new sessionClass(argv)
@@ -117,8 +119,8 @@ var appname = path.basename(__dirname)
117
119
  // Multiview server variables
118
120
  var hls_base = 'multiview'
119
121
  var multiview_stream_name = 'master.m3u8'
120
- if ( session.protection.content_protect ) multiview_stream_name += '?content_protect=' + session.protection.content_protect
121
122
  var multiview_url_path = '/' + hls_base + '/' + multiview_stream_name
123
+ if ( session.protection.content_protect ) multiview_url_path += '?content_protect=' + session.protection.content_protect
122
124
  session.setMultiviewStreamURLPath(multiview_url_path)
123
125
  var ffmpeg_command
124
126
  var ffmpeg_status = false
@@ -158,11 +160,12 @@ app.get('/stream.m3u8', async function(req, res) {
158
160
  let streamURL
159
161
  let options = {}
160
162
  let urlArray = req.url.split('?')
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) ) {
163
+ if ( (urlArray.length == 1) || ((session.data.scan_mode == 'on') && req.query.team) || (!req.query.team && !req.query.src && !req.query.highlight_src && !req.query.event && !req.query.id && !req.query.mediaId && !req.query.contentId) ) {
162
164
  // load a sample encrypted HLS stream
163
165
  session.log('loading sample stream')
164
166
  options.resolution = 'adaptive'
165
167
  streamURL = SAMPLE_STREAM_URL
168
+ options.referer = 'https://hls-js.netlify.app/'
166
169
  } else {
167
170
  if ( req.query.resolution && (options.resolution == 'best') ) {
168
171
  options.resolution = VALID_RESOLUTIONS[1]
@@ -181,8 +184,8 @@ app.get('/stream.m3u8', async function(req, res) {
181
184
  streamURL = req.query.src
182
185
  } else if ( req.query.highlight_src ) {
183
186
  streamURL = req.query.highlight_src
184
- } else if ( req.query.type && (req.query.type.toUpperCase() == 'BIGINNING') ) {
185
- streamURL = await session.getBigInningStreamURL()
187
+ } else if ( req.query.event ) {
188
+ streamURL = await session.getEventStreamURL(req.query.event.toUpperCase())
186
189
  } else {
187
190
  if ( req.query.contentId ) {
188
191
  contentId = req.query.contentId
@@ -240,6 +243,11 @@ app.get('/stream.m3u8', async function(req, res) {
240
243
  }
241
244
  }
242
245
 
246
+ if ( req.query.referer ) {
247
+ options.referer = req.query.referer
248
+ session.debuglog('sending referer : ' + options.referer)
249
+ }
250
+
243
251
  getMasterPlaylist(streamURL, req, res, options)
244
252
  } else {
245
253
  session.log('failed to get streamURL : ' + req.url)
@@ -269,6 +277,11 @@ var getKey = function(url, headers, cb) {
269
277
  })
270
278
  }
271
279
 
280
+ function getOriginFromURL(url) {
281
+ let pathArray = url.split('/')
282
+ return pathArray[0] + '//' + pathArray[2]
283
+ }
284
+
272
285
  // Default respond function, for adjusting content-length and updating CORS headers
273
286
  var respond = function(proxy, res, body) {
274
287
  delete proxy.headers['content-length']
@@ -307,6 +320,15 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
307
320
  session.debuglog('getMasterPlaylist of streamURL : ' + streamURL)
308
321
  var req = function () {
309
322
  var headers = {}
323
+ var referer = false
324
+ var referer_parameter = ''
325
+ if ( options.referer ) {
326
+ referer = decodeURIComponent(options.referer)
327
+ headers.referer = referer
328
+ headers.origin = getOriginFromURL(referer)
329
+ session.debuglog('found stream referer : ' + referer)
330
+ referer_parameter = '&referer=' + encodeURIComponent(options.referer)
331
+ }
310
332
  requestRetry(streamURL, headers, function(err, response) {
311
333
  if (err) return res.error(err)
312
334
 
@@ -314,6 +336,26 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
314
336
 
315
337
  var body = response.body.trim().split('\n')
316
338
 
339
+ // check if HLS
340
+ let hls_detected = false
341
+ for (var i=0; i<body.length; i++) {
342
+ if ( body[i] == '#EXTM3U' ) {
343
+ session.debuglog('hls detected')
344
+ hls_detected = true
345
+ break
346
+ } else if ( body[i] == '' ) {
347
+ session.debuglog('skipping blank lines at beginning of file')
348
+ continue
349
+ } else {
350
+ session.debuglog(body[i])
351
+ break
352
+ }
353
+ }
354
+ if ( !hls_detected ) {
355
+ session.log('not a valid hls stream')
356
+ return
357
+ }
358
+
317
359
  let resolution = options.resolution || VALID_RESOLUTIONS[0]
318
360
  let audio_track = options.audio_track || VALID_AUDIO_TRACKS[0]
319
361
  let audio_url = options.audio_url || ''
@@ -338,12 +380,13 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
338
380
  var audio_track_matched = false
339
381
  var frame_rate = '29.97'
340
382
  if ( (resolution != 'adaptive') && (resolution != 'none') ) {
341
- if ( resolution.slice(4) === '60' ) {
383
+ if ( resolution.endsWith('p60') ) {
342
384
  frame_rate = '59.94'
385
+ resolution = resolution.slice(0, -3)
343
386
  }
344
- resolution = resolution.slice(0, 3)
345
387
  }
346
388
 
389
+ var segment_playlist = false
347
390
  var segment_found = false
348
391
 
349
392
  body = body
@@ -352,12 +395,15 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
352
395
 
353
396
  // Check if segment playlist instead of master
354
397
  if ( line.startsWith('#EXTINF:') ) {
355
- session.debuglog('segment playlist instead of master')
398
+ if ( !segment_playlist ) {
399
+ session.debuglog('segment playlist instead of master')
400
+ segment_playlist = true
401
+ }
356
402
  segment_found = true
357
403
  return line
358
404
  } else if ( segment_found ) {
359
405
  segment_found = false
360
- return 'ts?url='+encodeURIComponent(url.resolve(streamURL, line.trim())) + content_protect
406
+ return 'ts?url='+encodeURIComponent(url.resolve(streamURL, line.trim())) + content_protect + referer_parameter
361
407
  }
362
408
 
363
409
  // Omit keyframe tracks
@@ -375,7 +421,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
375
421
  if ( audio_track_matched ) return
376
422
  if ( audio_url != '' ) {
377
423
  audio_track_matched = true
378
- return '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Audio",AUTOSELECT=YES,DEFAULT=YES,URI="' + audio_url + content_protect + '"'
424
+ return '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Audio",AUTOSELECT=YES,DEFAULT=YES,URI="' + audio_url + content_protect + referer_parameter + '"'
379
425
  }
380
426
  if ( audio_track == 'none') return
381
427
  if ( (resolution == 'none') && (line.indexOf(',URI=') < 0) ) return
@@ -397,7 +443,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
397
443
  if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
398
444
  if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
399
445
  if ( contentId ) newurl += '&contentId=' + contentId
400
- newurl += content_protect
446
+ newurl += content_protect + referer_parameter
401
447
  if ( resolution == 'none' ) {
402
448
  audio_track_matched = true
403
449
  return line.replace(parsed[0],'') + "\n" + '#EXT-X-STREAM-INF:BANDWIDTH=50000,CODECS="mp4a.40.2",AUDIO="aac"' + "\n" + newurl
@@ -443,7 +489,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
443
489
  if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
444
490
  if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
445
491
  if ( contentId ) newurl += '&contentId=' + contentId
446
- newurl += content_protect
492
+ newurl += content_protect + referer_parameter
447
493
  return 'playlist?url='+newurl
448
494
  }
449
495
  })
@@ -477,6 +523,14 @@ app.get('/playlist', async function(req, res) {
477
523
  var u = req.query.url
478
524
  session.debuglog('playlist url : ' + u)
479
525
 
526
+ var referer = false
527
+ var referer_parameter = ''
528
+ if ( req.query.referer ) {
529
+ referer = decodeURIComponent(req.query.referer)
530
+ session.debuglog('found playlist referer : ' + referer)
531
+ referer_parameter = '&referer=' + encodeURIComponent(req.query.referer)
532
+ }
533
+
480
534
  var force_vod = req.query.force_vod || VALID_FORCE_VOD[0]
481
535
  var inning_half = req.query.inning_half || VALID_INNING_HALF[0]
482
536
  var inning_number = req.query.inning_number || VALID_INNING_NUMBER[0]
@@ -485,12 +539,37 @@ app.get('/playlist', async function(req, res) {
485
539
 
486
540
  var req = function () {
487
541
  var headers = {}
542
+ if ( referer ) {
543
+ headers.referer = referer
544
+ headers.origin = getOriginFromURL(referer)
545
+ }
488
546
  requestRetry(u, headers, function(err, response) {
489
547
  if (err) return res.error(err)
490
548
 
491
549
  //session.debuglog(response.body)
492
550
 
493
551
  var body = response.body.trim().split('\n')
552
+
553
+ // check if HLS
554
+ let hls_detected = false
555
+ for (var i=0; i<body.length; i++) {
556
+ if ( body[i] == '#EXTM3U' ) {
557
+ session.debuglog('hls detected')
558
+ hls_detected = true
559
+ break
560
+ } else if ( body[i] == '' ) {
561
+ session.debuglog('skipping blank lines at beginning of file')
562
+ continue
563
+ } else {
564
+ session.debuglog(body[i])
565
+ break
566
+ }
567
+ }
568
+ if ( !hls_detected ) {
569
+ session.log('not a valid hls stream')
570
+ return
571
+ }
572
+
494
573
  var key
495
574
  var iv
496
575
 
@@ -517,6 +596,9 @@ app.get('/playlist', async function(req, res) {
517
596
 
518
597
  body = body
519
598
  .map(function(line) {
599
+ // Skip blank lines
600
+ if (line.trim() == '') return null
601
+
520
602
  if ( ((skip != 'off') || (inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0])) && (typeof session.temp_cache[contentId] !== 'undefined') && (typeof session.temp_cache[contentId].inning_offsets !== 'undefined') ) {
521
603
  if ( skip_next ) {
522
604
  skip_next = false
@@ -579,10 +661,17 @@ app.get('/playlist', async function(req, res) {
579
661
  }
580
662
 
581
663
  if (line.indexOf('-KEY:METHOD=AES-128') > 0) {
664
+ session.debuglog('key line : ' + line)
582
665
  var parsed = line.match(/URI="([^"]+)"(?:,IV=(.+))?$/)
583
666
  if ( parsed ) {
584
667
  if ( parsed[1].substr(0,4) == 'http' ) key = parsed[1]
585
668
  else key = url.resolve(u, parsed[1])
669
+ session.debuglog('found key : ' + key)
670
+ if ( key.startsWith('data:;base64,') ) {
671
+ let newparsed = key.split(',')
672
+ key = newparsed[1]
673
+ session.debuglog('found new key : ' + key)
674
+ }
586
675
  if (parsed[2]) iv = parsed[2].slice(2).toLowerCase()
587
676
  }
588
677
  return null
@@ -590,8 +679,8 @@ app.get('/playlist', async function(req, res) {
590
679
 
591
680
  if (line[0] === '#') return line
592
681
 
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
682
+ if ( key ) return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim()))+'&key='+encodeURIComponent(key)+'&iv='+encodeURIComponent(iv) + content_protect + referer_parameter
683
+ else return 'ts?url='+encodeURIComponent(url.resolve(u, line.trim())) + content_protect + referer_parameter
595
684
  })
596
685
  .filter(function(line) {
597
686
  return line
@@ -625,23 +714,42 @@ app.get('/ts', async function(req, res) {
625
714
 
626
715
  var headers = {encoding:null}
627
716
 
717
+ if ( req.query.referer ) {
718
+ session.debuglog('found segment referer : ' + req.query.referer)
719
+ referer = decodeURIComponent(req.query.referer)
720
+ headers.referer = referer
721
+ headers.origin = getOriginFromURL(referer)
722
+ }
723
+
628
724
  requestRetry(u, headers, function(err, response) {
629
725
  if (err) return res.error(err)
630
726
  if (!req.query.key) return respond(response, res, response.body)
631
727
 
632
728
  //var ku = url.resolve(manifest, req.query.key)
633
729
  var ku = req.query.key
634
- getKey(ku, headers, function(err, key) {
635
- if (err) return res.error(err)
636
-
730
+ if ( ku.substr(0,4) != 'http' ) {
637
731
  var iv = Buffer.from(req.query.iv, 'hex')
638
732
  session.debuglog('iv : 0x'+req.query.iv)
639
733
 
734
+ let key = Buffer.from(ku, "base64")
735
+
640
736
  var dc = crypto.createDecipheriv('aes-128-cbc', key, iv)
641
737
  var buffer = Buffer.concat([dc.update(response.body), dc.final()])
642
738
 
643
739
  respond(response, res, buffer)
644
- })
740
+ } else {
741
+ getKey(ku, headers, function(err, key) {
742
+ if (err) return res.error(err)
743
+
744
+ var iv = Buffer.from(req.query.iv, 'hex')
745
+ session.debuglog('iv : 0x'+req.query.iv)
746
+
747
+ var dc = crypto.createDecipheriv('aes-128-cbc', key, iv)
748
+ var buffer = Buffer.concat([dc.update(response.body), dc.final()])
749
+
750
+ respond(response, res, buffer)
751
+ })
752
+ }
645
753
  })
646
754
  })
647
755
 
@@ -872,6 +980,8 @@ app.get('/', async function(req, res) {
872
980
  }
873
981
  body += '</p>' + "\n"
874
982
 
983
+ body += '<p><span class="tooltip tinytext">* indicates a free game<span class="tooltiptext">Free games are available to anyone with an account, no subscription necessary. Blackouts still apply.</span></span></p>' + "\n"
984
+
875
985
  body += "<table>" + "\n"
876
986
 
877
987
  // Rename some parameters before display links
@@ -903,7 +1013,7 @@ app.get('/', async function(req, res) {
903
1013
  let compareEnd = new Date(big_inning.end)
904
1014
  compareEnd.setHours(compareEnd.getHours()+1)
905
1015
  if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
906
- let querystring = '?type=biginning'
1016
+ let querystring = '?event=biginning'
907
1017
  let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
908
1018
  if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
909
1019
  querystring += content_protect_b
@@ -921,6 +1031,10 @@ app.get('/', async function(req, res) {
921
1031
 
922
1032
  let awayteam = cache_data.dates[0].games[j].teams['away'].team.abbreviation
923
1033
  let hometeam = cache_data.dates[0].games[j].teams['home'].team.abbreviation
1034
+ if ( cache_data.dates[0].games[j].teams['home'].team.sport.name == 'Winter Leagues' ) {
1035
+ awayteam = cache_data.dates[0].games[j].teams['away'].team.name
1036
+ hometeam = cache_data.dates[0].games[j].teams['home'].team.name
1037
+ }
924
1038
 
925
1039
  let teams = awayteam + " @ " + hometeam
926
1040
  let pitchers = ""
@@ -996,6 +1110,9 @@ app.get('/', async function(req, res) {
996
1110
  } else if ( cache_data.dates[0].games[j].gameUtils.isDelayed ) {
997
1111
  state += "<br/>" + detailedState
998
1112
  }
1113
+ if ( cache_data.dates[0].games[j].teams['home'].team.sport.name == 'Winter Leagues' ) {
1114
+ state += "<br/>" + cache_data.dates[0].games[j].teams['home'].team.league.name
1115
+ }
999
1116
 
1000
1117
  if ( cache_data.dates[0].games[j].doubleHeader != 'N' ) {
1001
1118
  state += "<br/>Game " + cache_data.dates[0].games[j].gameNumber
@@ -1049,103 +1166,139 @@ app.get('/', async function(req, res) {
1049
1166
 
1050
1167
  body += "<tr><td>" + teams + pitchers + state + "</td>"
1051
1168
 
1052
- if ( ((typeof cache_data.dates[0].games[j].content.media) == 'undefined') || ((typeof cache_data.dates[0].games[j].content.media.epg) == 'undefined') ) {
1053
- body += "<td></td>"
1054
- } else {
1169
+ // Check if Winter League game first
1170
+ if ( cache_data.dates[0].games[j].teams['home'].team.sport.name == 'Winter Leagues' ) {
1055
1171
  body += "<td>"
1056
- for (var k = 0; k < cache_data.dates[0].games[j].content.media.epg.length; k++) {
1057
- let epgTitle = cache_data.dates[0].games[j].content.media.epg[k].title
1058
- if ( epgTitle == mediaType ) {
1059
- let lastStation
1060
- for (var x = 0; x < cache_data.dates[0].games[j].content.media.epg[k].items.length; x++) {
1061
- // check that pay TV authentication isn't required
1062
- if ( (mediaType == 'MLBTV') && ((cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState != 'MEDIA_ARCHIVE') || !cache_data.dates[0].games[j].gameUtils.isFinal) && (cache_data.dates[0].games[j].content.media.epg[k].items[x].foxAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].tbsAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].espnAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].fs1AuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].mlbnAuthRequired) ) {
1063
- continue
1172
+ if ( cache_data.dates[0].games[j].broadcasts ) {
1173
+ for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
1174
+ if ( (mediaType == 'MLBTV') && (cache_data.dates[0].games[j].broadcasts[k].name == 'MLB.TV') ) {
1175
+ // Check if game should be live
1176
+ let currentTime = new Date()
1177
+ let startTime = new Date(cache_data.dates[0].games[j].gameDate)
1178
+ startTime.setMinutes(startTime.getMinutes()-30)
1179
+ let endTime = new Date(cache_data.dates[0].games[j].gameDate)
1180
+ endTime.setHours(endTime.getHours()+4)
1181
+ if ( (currentTime >= startTime) && (currentTime < endTime) ) {
1182
+ let querystring
1183
+ querystring = '?event=' + cache_data.dates[0].games[j].teams['home'].team.clubName.toLowerCase()
1184
+ let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
1185
+ if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1186
+ if ( audio_track != VALID_AUDIO_TRACKS[0] ) querystring += '&audio_track=' + encodeURIComponent(audio_track)
1187
+ querystring += content_protect_b
1188
+ multiviewquerystring += content_protect_b
1189
+ body += '<a href="' + thislink + querystring + '">' + cache_data.dates[0].games[j].broadcasts[k].name + '</a>'
1190
+ body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1191
+ } else {
1192
+ body += cache_data.dates[0].games[j].broadcasts[k].name
1064
1193
  }
1065
- 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) ) {
1066
- 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) ) {
1067
- let teamabbr
1068
- 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) ) {
1069
- teamabbr = 'NATIONAL'
1070
- } else {
1071
- teamabbr = hometeam
1072
- if ( cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'AWAY' ) {
1073
- teamabbr = awayteam
1074
- }
1075
- }
1076
- let station = cache_data.dates[0].games[j].content.media.epg[k].items[x].callLetters
1077
- if ( (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE') || cache_data.dates[0].games[j].gameUtils.isFinal ) {
1078
- game_started = true
1079
- let mediaId = cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaId
1080
- if ( (mediaType == 'MLBTV') && session.cache.media && session.cache.media[mediaId] && session.cache.media[mediaId].blackout && session.cache.media[mediaId].blackoutExpiry && (new Date(session.cache.media[mediaId].blackoutExpiry) > new Date()) ) {
1081
- body += teamabbr + ': <s>' + station + '</s>'
1194
+ break
1195
+ }
1196
+ }
1197
+ }
1198
+ body += "</td>"
1199
+ } else {
1200
+ // Begin MLB games
1201
+ if ( ((typeof cache_data.dates[0].games[j].content.media) == 'undefined') || ((typeof cache_data.dates[0].games[j].content.media.epg) == 'undefined') ) {
1202
+ body += "<td></td>"
1203
+ } else {
1204
+ body += "<td>"
1205
+ for (var k = 0; k < cache_data.dates[0].games[j].content.media.epg.length; k++) {
1206
+ let epgTitle = cache_data.dates[0].games[j].content.media.epg[k].title
1207
+ if ( epgTitle == mediaType ) {
1208
+ let lastStation
1209
+ for (var x = 0; x < cache_data.dates[0].games[j].content.media.epg[k].items.length; x++) {
1210
+ // check that pay TV authentication isn't required
1211
+ if ( (mediaType == 'MLBTV') && ((cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState != 'MEDIA_ARCHIVE') || !cache_data.dates[0].games[j].gameUtils.isFinal) && (cache_data.dates[0].games[j].content.media.epg[k].items[x].foxAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].tbsAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].espnAuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].fs1AuthRequired || cache_data.dates[0].games[j].content.media.epg[k].items[x].mlbnAuthRequired) ) {
1212
+ continue
1213
+ }
1214
+ 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) ) {
1215
+ 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) ) {
1216
+ let teamabbr
1217
+ 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) ) {
1218
+ teamabbr = 'NATIONAL'
1082
1219
  } else {
1083
- let querystring
1084
- querystring = '?mediaId=' + mediaId
1085
- let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
1086
- if ( linkType == 'embed' ) {
1087
- if ( startFrom != 'Beginning' ) querystring += '&startFrom=' + startFrom
1220
+ teamabbr = hometeam
1221
+ if ( cache_data.dates[0].games[j].content.media.epg[k].items[x][mediaFeedType] == 'AWAY' ) {
1222
+ teamabbr = awayteam
1088
1223
  }
1089
- if ( mediaType == 'MLBTV' ) {
1090
- if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
1091
- if ( inning_number != '' ) querystring += '&inning_number=' + relative_inning
1092
- if ( skip != 'off' ) querystring += '&skip=' + skip
1093
- if ( skip_adjust != '0' ) querystring += '&skip_adjust=' + skip_adjust
1094
- if ( (inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0]) ) {
1095
- let contentId = cache_data.dates[0].games[j].content.media.epg[k].items[x].contentId
1096
- querystring += '&contentId=' + contentId
1224
+ }
1225
+ let station = cache_data.dates[0].games[j].content.media.epg[k].items[x].callLetters
1226
+ if ( cache_data.dates[0].games[j].content.media.freeGame ) {
1227
+ station += '*'
1228
+ }
1229
+ if ( (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE') || cache_data.dates[0].games[j].gameUtils.isFinal ) {
1230
+ game_started = true
1231
+ let mediaId = cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaId
1232
+ if ( (mediaType == 'MLBTV') && session.cache.media && session.cache.media[mediaId] && session.cache.media[mediaId].blackout && session.cache.media[mediaId].blackoutExpiry && (new Date(session.cache.media[mediaId].blackoutExpiry) > new Date()) ) {
1233
+ body += teamabbr + ': <s>' + station + '</s>'
1234
+ } else {
1235
+ let querystring
1236
+ querystring = '?mediaId=' + mediaId
1237
+ let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
1238
+ if ( linkType == 'embed' ) {
1239
+ if ( startFrom != 'Beginning' ) querystring += '&startFrom=' + startFrom
1097
1240
  }
1098
- if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1099
- if ( audio_track != VALID_AUDIO_TRACKS[0] ) querystring += '&audio_track=' + encodeURIComponent(audio_track)
1100
- // audio_url is disabled here, now used in multiview instead
1101
- //if ( audio_url != '' ) querystring += '&audio_url=' + encodeURIComponent(audio_url)
1102
- }
1103
- if ( linkType == 'stream' ) {
1104
- if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
1105
- if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
1241
+ if ( mediaType == 'MLBTV' ) {
1242
+ if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
1243
+ if ( inning_number != '' ) querystring += '&inning_number=' + relative_inning
1244
+ if ( skip != 'off' ) querystring += '&skip=' + skip
1245
+ if ( skip_adjust != '0' ) querystring += '&skip_adjust=' + skip_adjust
1246
+ if ( (inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0]) ) {
1247
+ let contentId = cache_data.dates[0].games[j].content.media.epg[k].items[x].contentId
1248
+ querystring += '&contentId=' + contentId
1249
+ }
1250
+ if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1251
+ if ( audio_track != VALID_AUDIO_TRACKS[0] ) querystring += '&audio_track=' + encodeURIComponent(audio_track)
1252
+ // audio_url is disabled here, now used in multiview instead
1253
+ //if ( audio_url != '' ) querystring += '&audio_url=' + encodeURIComponent(audio_url)
1106
1254
  }
1107
- }
1108
- querystring += content_protect_b
1109
- multiviewquerystring += content_protect_b
1110
- body += teamabbr + ': <a href="' + thislink + querystring + '">' + station + '</a>'
1111
- if ( mediaType == 'MLBTV' ) {
1112
- body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1113
- }
1114
- if ( resumeStatus ) {
1115
- if ( resumeStatus == 'live' ) {
1116
- if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE' ) {
1117
- body += '(1)'
1118
- } else if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
1119
- body += '(2)'
1255
+ if ( linkType == 'stream' ) {
1256
+ if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
1257
+ if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
1120
1258
  }
1121
- } else {
1122
- if ( station == lastStation ) {
1123
- body += '(2)'
1259
+ }
1260
+ querystring += content_protect_b
1261
+ multiviewquerystring += content_protect_b
1262
+ body += teamabbr + ': <a href="' + thislink + querystring + '">' + station + '</a>'
1263
+ if ( mediaType == 'MLBTV' ) {
1264
+ body += '<input type="checkbox" value="' + server + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1265
+ }
1266
+ if ( resumeStatus ) {
1267
+ if ( resumeStatus == 'live' ) {
1268
+ if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE' ) {
1269
+ body += '(1)'
1270
+ } else if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
1271
+ body += '(2)'
1272
+ }
1124
1273
  } else {
1125
- body += '(1)'
1126
- lastStation = station
1274
+ if ( station == lastStation ) {
1275
+ body += '(2)'
1276
+ } else {
1277
+ body += '(1)'
1278
+ lastStation = station
1279
+ }
1127
1280
  }
1128
1281
  }
1282
+ body += ', '
1129
1283
  }
1130
- body += ', '
1284
+ } else {
1285
+ body += teamabbr + ': ' + station + ', '
1131
1286
  }
1132
- } else {
1133
- body += teamabbr + ': ' + station + ', '
1134
1287
  }
1135
1288
  }
1136
1289
  }
1290
+ break
1137
1291
  }
1138
- break
1139
1292
  }
1140
- }
1141
- if ( body.substr(-2) == ', ' ) {
1142
- body = body.slice(0, -2)
1143
- }
1144
- if ( (mediaType == 'MLBTV') && (game_started) ) {
1145
- body += '<br/><a href="javascript:showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\')">Highlights</a>'
1146
- }
1147
- if ( body.substr(-2) == ', ' ) {
1148
- body = body.slice(0, -2)
1293
+ if ( body.substr(-2) == ', ' ) {
1294
+ body = body.slice(0, -2)
1295
+ }
1296
+ if ( (mediaType == 'MLBTV') && (game_started) ) {
1297
+ body += '<br/><a href="javascript:showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\')">Highlights</a>'
1298
+ }
1299
+ if ( body.substr(-2) == ', ' ) {
1300
+ body = body.slice(0, -2)
1301
+ }
1149
1302
  }
1150
1303
  body += "</td>"
1151
1304
  body += "</tr>" + "\n"
@@ -1237,7 +1390,9 @@ app.get('/', async function(req, res) {
1237
1390
 
1238
1391
  body += '<p><span class="tooltip">Exclude a team + national<span class="tooltiptext">This is useful for excluding games you may be blacked out from. Excluding a team will exclude every game involving that team. National refers to USA national TV broadcasts.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&excludeTeams=ari,national' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&excludeTeams=ari,national' + content_protect_b + '">guide.xml</a></p>' + "\n"
1239
1392
 
1240
- body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">guide.xml</a></p>' + "\n"
1393
+ body += '<p><span class="tooltip">Include (or exclude) LIDOM<span class="tooltiptext">Dominican Winter League, aka Liga de Beisbol Dominicano. Live stream only, does not support starting from the beginning or certain innings, skip options, etc.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=lidom' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=lidom' + content_protect_b + '">guide.xml</a></p>' + "\n"
1394
+
1395
+ body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. Live stream only, does not support starting from the beginning.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">guide.xml</a></p>' + "\n"
1241
1396
 
1242
1397
  body += '<p><span class="tooltip">Include (or exclude) Multiview<span class="tooltiptext">Requires starting and stopping the multiview stream from the web interface.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=multiview' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">guide.xml</a></p>' + "\n"
1243
1398
 
@@ -1280,6 +1435,9 @@ app.get('/', async function(req, res) {
1280
1435
 
1281
1436
  body += '<p><span class="tooltip">Bookmarklets for MLB.com<span class="tooltiptext">If you watch at MLB.com, drag these bookmarklets to your bookmarks toolbar and use them to hide parts of the interface.</span></span>: <a href="javascript:(function(){let x=document.querySelector(\'#mlbtv-stats-panel\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}})();">Boxscore</a> | <a href="javascript:(function(){let x=document.querySelector(\'.mlbtv-header-container\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">Scoreboard</a> | <a href="javascript:(function(){let x=document.querySelector(\'.mlbtv-container--footer\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">Linescore</a> | <a href="javascript:(function(){let x=document.querySelector(\'#mlbtv-stats-panel\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}x=document.querySelector(\'.mlbtv-header-container\');if(x.style.display==\'none\'){x.style.display=\'initial\';}else{x.style.display=\'none\';}x=document.querySelector(\'.mlbtv-container--footer\');if(x.style.display==\'none\'){let y=document.querySelector(\'.mlbtv-players-container\');y.style.display=\'none\';x.style.display=\'initial\';setTimeout(function(){y.style.display=\'initial\';},15);}else{x.style.display=\'none\';}})();">All</a></p>' + "\n"
1282
1437
 
1438
+ // Print version
1439
+ body += '<p class="tinytext">Version ' + version + '</p>' + "\n"
1440
+
1283
1441
  // Datepicker functions
1284
1442
  body += '<script>var datePicker=document.getElementById("gameDate");function changeDate(e){date=datePicker.value;reload()}function removeDate(e){datePicker.removeEventListener("change",changeDate,false);datePicker.addEventListener("blur",changeDate,false);if(e.keyCode===13){date=datePicker.value;reload()}}datePicker.addEventListener("change",changeDate,false);datePicker.addEventListener("keypress",removeDate,false)</script>' + "\n"
1285
1443
 
@@ -1749,7 +1907,6 @@ function start_multiview_stream(streams, sync, dvr, faster, audio_url, audio_url
1749
1907
  ffmpeg_command.addOutputOption('-c:v', 'copy')
1750
1908
  }
1751
1909
  ffmpeg_command.addOutputOption('-c:a', 'aac')
1752
- .addOutputOption('-strict', 'experimental')
1753
1910
  .addOutputOption('-sn')
1754
1911
  .addOutputOption('-t', '6:00:00')
1755
1912
  .addOutputOption('-f', 'hls')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2021.10.11",
3
+ "version": "2022.03.22",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",