mlbserver 2025.7.26-2 → 2025.7.26-3

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 (3) hide show
  1. package/index.js +39 -11
  2. package/package.json +1 -1
  3. package/session.js +25 -12
package/index.js CHANGED
@@ -451,6 +451,32 @@ var requestRetry = function(u, opts, cb) {
451
451
  action()
452
452
  }
453
453
 
454
+ // Listen for master stream requests
455
+ app.get('/master.m3u8', async function(req, res) {
456
+ if ( ! (await protect(req, res)) ) return
457
+
458
+ try {
459
+ session.requestlog('master.m3u8', req)
460
+
461
+ let options = {}
462
+ if ( req.query.streamURL ) {
463
+ let streamURL = decodeURIComponent(req.query.streamURL)
464
+ if ( req.query.streamURLToken ) {
465
+ options.streamURLToken = decodeURIComponent(req.query.streamURLToken)
466
+ }
467
+
468
+ getMasterPlaylist(streamURL, req, res, options)
469
+ } else {
470
+ session.log('failed to find master URL : ' + req.url)
471
+ res.end('')
472
+ return
473
+ }
474
+ } catch (e) {
475
+ session.log('master request error : ' + e.message)
476
+ res.end('')
477
+ }
478
+ })
479
+
454
480
 
455
481
  // Get the master playlist from the stream URL
456
482
  function getMasterPlaylist(streamURL, req, res, options = {}) {
@@ -554,12 +580,12 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
554
580
  }
555
581
 
556
582
  // Omit captions track when TV audio is excluded or no video is specified
557
- if ( line.startsWith('#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,') && ((audio_track != VALID_AUDIO_TRACKS[0]) || (audio_track != VALID_AUDIO_TRACKS[1]) || (resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1])) ) {
583
+ if ( line.startsWith('#EXT-X-MEDIA:') && line.includes('TYPE=CLOSED-CAPTIONS') && ((audio_track != VALID_AUDIO_TRACKS[0]) || (audio_track != VALID_AUDIO_TRACKS[1]) || (resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1])) ) {
558
584
  return
559
585
  }
560
586
 
561
587
  // Parse audio tracks to only include matching one, if specified
562
- if ( line.startsWith('#EXT-X-MEDIA:TYPE=AUDIO') ) {
588
+ if ( line.startsWith('#EXT-X-MEDIA:') && line.includes('TYPE=AUDIO') ) {
563
589
  // if we've already returned our desired audio track, we can skip subsequent ones
564
590
  if ( audio_track_matched ) return
565
591
 
@@ -573,7 +599,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
573
599
  let audio_output = ''
574
600
 
575
601
  // default TV audio, or park sounds
576
- if ( !line.includes(',URI=') ) {
602
+ if ( !line.includes('URI=') ) {
577
603
  // only include default embedded audio track if requested or if filtering for park audio
578
604
  if ( (audio_track == VALID_AUDIO_TRACKS[1]) || (audio_track == VALID_AUDIO_TRACKS[6]) ) {
579
605
  audio_track_matched = true
@@ -600,7 +626,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
600
626
 
601
627
  if ( line.match ) {
602
628
  //var parsed = line.match(/URI="([^"]+)"?$/)
603
- var parsed = line.match(',URI="([^"]+)"')
629
+ var parsed = line.match('URI="([^"]+)"')
604
630
  if ( parsed[1] ) {
605
631
  newurl = http_root + '/playlist.m3u8?url='+encodeURIComponent(url.resolve(streamURL, parsed[1].trim()))
606
632
  if ( force_vod != VALID_FORCE_VOD[0] ) newurl += '&force_vod=on'
@@ -653,8 +679,8 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
653
679
  }
654
680
 
655
681
  // Pass through any remaining caption tracks
656
- if ( line.startsWith('#EXT-X-MEDIA:TYPE=SUBTITLES,') ) {
657
- var parsed = line.match(',URI="([^"]+)"')
682
+ if ( line.startsWith('#EXT-X-MEDIA:') && line.includes('TYPE=SUBTITLES') ) {
683
+ var parsed = line.match('URI="([^"]+)"')
658
684
  if ( parsed[1] ) {
659
685
  newurl = http_root + '/playlist.m3u8?url='+encodeURIComponent(url.resolve(streamURL, parsed[1].trim()))
660
686
  if ( force_vod != VALID_FORCE_VOD[0] ) newurl += '&force_vod=on'
@@ -757,7 +783,7 @@ app.get('/playlist.m3u8', async function(req, res) {
757
783
  requestRetry(u, headers, function(err, response) {
758
784
  if (err) return res.error(err)
759
785
 
760
- //session.debuglog(response.body)
786
+ session.debuglog(response.body)
761
787
 
762
788
  var body = response.body.replace(/^\s+|\s+$/g, '').split('\n')
763
789
 
@@ -1020,12 +1046,12 @@ app.get('/gamechanger.m3u8', async function(req, res) {
1020
1046
 
1021
1047
  var resolution
1022
1048
  if ( req.query.resolution && (req.query.resolution == 'best') ) {
1023
- resolution = VALID_RESOLUTIONS[2]
1049
+ resolution = VALID_RESOLUTIONS[1]
1024
1050
  } else {
1025
1051
  resolution = session.returnValidItem(req.query.resolution, VALID_RESOLUTIONS)
1026
1052
  }
1027
1053
  if ( resolution == VALID_RESOLUTIONS[0] ) {
1028
- resolution = VALID_RESOLUTIONS[2]
1054
+ resolution = VALID_RESOLUTIONS[1]
1029
1055
  }
1030
1056
 
1031
1057
  var streamFinder = ''
@@ -1051,7 +1077,9 @@ app.get('/gamechanger.m3u8', async function(req, res) {
1051
1077
  content_protect = '&content_protect=' + session.protection.content_protect
1052
1078
  }
1053
1079
 
1054
- var body = '#EXTM3U' + '\n' + '#EXT-X-INDEPENDENT-SEGMENTS' + '\n' + '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES' + '\n' + '#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc",LANGUAGE="en",NAME="English",INSTREAM-ID="CC1",AUTOSELECT=YES,DEFAULT=YES' + '\n'
1080
+ var body = '#EXTM3U' + '\n' + '#EXT-X-INDEPENDENT-SEGMENTS' + '\n' + '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="program_audio",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES' + '\n'
1081
+ // disable captions
1082
+ // + '#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc",LANGUAGE="en",NAME="English",INSTREAM-ID="CC1",AUTOSELECT=YES,DEFAULT=YES' + '\n'
1055
1083
 
1056
1084
  for ( gamechanger_resolution in GAMECHANGER_RESOLUTIONS ) {
1057
1085
  if ( resolution == gamechanger_resolution ) {
@@ -2259,7 +2287,7 @@ app.get('/', async function(req, res) {
2259
2287
  }
2260
2288
  body += '</p>' + "\n"
2261
2289
 
2262
- body += '<p><span class="tooltip">Skip<span class="tooltiptext">For video streams only (use the video "none" option above to apply it to audio streams): you can remove all breaks, idle time, non-action pitches, or only commercial breaks from the stream (useful to make your own "condensed games").<br/><br/>NOTE: skip timings are only generated when the stream is loaded -- so for live games, it will only skip up to the time you loaded the stream.</span></span>: '
2290
+ body += '<p><span class="tooltip">Skip<span class="tooltiptext">For video streams only (use the video "none" option above to apply it to audio streams): you can remove all breaks, idle time, non-action pitches, or only commercial breaks from the stream (useful to make your own "condensed games").<br/><br/>NOTES: skip timings are only generated when the stream is loaded -- so for live games, it will only skip up to the time you loaded the stream. Also, commercial skip will not work on pre-2024 games, or on MiLB games -- use skip breaks instead.</span></span>: '
2263
2291
  for (var i = 0; i < VALID_SKIP.length; i++) {
2264
2292
  body += '<button '
2265
2293
  if ( skip == VALID_SKIP[i] ) body += 'class="default" '
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2025.07.26-2",
3
+ "version": "2025.07.26-3",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -3137,28 +3137,41 @@ class sessionClass {
3137
3137
  // Get variant playlist
3138
3138
  async getVariantPlaylist(streamURL, streamURLToken) {
3139
3139
  try {
3140
- this.debuglog('getVariantPlaylist')
3141
-
3142
- // MLB version
3143
- let variant = '_5600K'
3144
- if ( streamURL.includes('milb.com') ) {
3145
- variant = '_1280x720_59_5472K'
3146
- }
3140
+ this.debuglog('getVariantPlaylist from ' + streamURL)
3147
3141
 
3148
- let variant_url = 'http://localhost:' + this.data.port + '/playlist.m3u8?url=' + encodeURIComponent(streamURL.substr(0,streamURL.length-5) + variant + '.m3u8')
3142
+ let master_url = 'http://localhost:' + this.data.port + '/master.m3u8?streamURL=' + encodeURIComponent(streamURL)
3149
3143
  if ( streamURLToken ) {
3150
- variant_url += '&streamURLToken=' + encodeURIComponent(streamURLToken)
3144
+ master_url += '&streamURLToken=' + encodeURIComponent(streamURLToken)
3151
3145
  }
3152
3146
  if ( this.protection.content_protect ) {
3153
- variant_url += '&content_protect=' + this.protection.content_protect
3147
+ master_url += '&content_protect=' + this.protection.content_protect
3154
3148
  }
3149
+ this.debuglog('getVariantPlaylist checking master ' + master_url)
3155
3150
  let reqObj = {
3156
- url: variant_url
3151
+ url: master_url
3157
3152
  }
3158
3153
  var response = await this.httpGet(reqObj, false)
3159
3154
  var body = response.replace(/^\s+|\s+$/g, '').split('\n')
3160
3155
 
3161
- return body
3156
+ let variant_url
3157
+ for (var i=0; i<body.length; i++) {
3158
+ let line = body[i];
3159
+ if ( line.includes('RESOLUTION=1280x720') && line.includes('FRAME-RATE=59.94') ) {
3160
+ variant_url = 'http://localhost:' + this.data.port + body[i+1]
3161
+ break
3162
+ }
3163
+ }
3164
+
3165
+ if (variant_url) {
3166
+ this.debuglog('getVariantPlaylist found variant ' + variant_url)
3167
+ reqObj = {
3168
+ url: variant_url
3169
+ }
3170
+ response = await this.httpGet(reqObj, false)
3171
+ body = response.replace(/^\s+|\s+$/g, '').split('\n')
3172
+
3173
+ return body
3174
+ }
3162
3175
  } catch(e) {
3163
3176
  this.log('getVariantPlaylist error : ' + e.message)
3164
3177
  }