mlbserver 2025.8.16 → 2025.8.17

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 +62 -4
  2. package/package.json +1 -1
  3. package/session.js +29 -1
package/index.js CHANGED
@@ -295,7 +295,7 @@ app.get('/stream.m3u8', async function(req, res) {
295
295
  options.resolution = req.query.resolution
296
296
  options.audio_track = VALID_AUDIO_TRACKS[0]
297
297
  if ( req.query.audio_track ) {
298
- options.audio_track = session.returnValidItem(req.query.audio_track.split(' ')[0], VALID_AUDIO_TRACKS)
298
+ options.audio_track = req.query.audio_track.split(' ')[0]
299
299
  }
300
300
  options.captions = req.query.captions || VALID_CAPTIONS[0]
301
301
  options.force_vod = req.query.force_vod || VALID_FORCE_VOD[0]
@@ -339,6 +339,17 @@ app.get('/stream.m3u8', async function(req, res) {
339
339
  } else {
340
340
  mediaId = mediaInfo.mediaId
341
341
  gamePk = mediaInfo.gamePk
342
+ if ( mediaInfo.teamType ) {
343
+ if ( options.audio_track.toLowerCase() == 'radio' ) {
344
+ options.audio_track = mediaInfo.teamType
345
+ } else if ( options.audio_track.toLowerCase() == 'spanish' ) {
346
+ if ( mediaInfo.teamType == 'Away' ) {
347
+ options.audio_track = VALID_AUDIO_TRACKS[5]
348
+ } else {
349
+ options.audio_track = VALID_AUDIO_TRACKS[3]
350
+ }
351
+ }
352
+ }
342
353
  }
343
354
  } else {
344
355
  session.log('no matching game found ' + req.url)
@@ -520,6 +531,34 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
520
531
 
521
532
  let resolution = options.resolution || VALID_RESOLUTIONS[0]
522
533
  let audio_track = options.audio_track || VALID_AUDIO_TRACKS[0]
534
+ // if specific audio track is requested, check if master playlist contains it
535
+ if ( audio_track != VALID_AUDIO_TRACKS[0] ) {
536
+ if ( !response.body.includes(',NAME="' + audio_track) ) {
537
+ session.debuglog('requested ' + audio_track + ' audio track not available')
538
+ // fallback check other team's radio feed
539
+ if ( VALID_AUDIO_TRACKS.slice(2, 6).includes(audio_track) ) {
540
+ if ( audio_track == VALID_AUDIO_TRACKS[5] ) {
541
+ audio_track = VALID_AUDIO_TRACKS[3]
542
+ } else if ( audio_track == VALID_AUDIO_TRACKS[3] ) {
543
+ audio_track = VALID_AUDIO_TRACKS[5]
544
+ } else if ( audio_track == VALID_AUDIO_TRACKS[4] ) {
545
+ audio_track = VALID_AUDIO_TRACKS[2]
546
+ } else if ( audio_track == VALID_AUDIO_TRACKS[2] ) {
547
+ audio_track = VALID_AUDIO_TRACKS[4]
548
+ }
549
+ if ( !response.body.includes(',NAME="' + audio_track) ) {
550
+ // if fallback feed is also not available, fall back to default
551
+ session.debuglog('fallback ' + audio_track + ' audio track also not available')
552
+ audio_track = VALID_AUDIO_TRACKS[0]
553
+ } else {
554
+ session.debuglog('falling back to ' + audio_track + ' audio track')
555
+ }
556
+ } else {
557
+ // fall back to default
558
+ audio_track = VALID_AUDIO_TRACKS[0]
559
+ }
560
+ }
561
+ }
523
562
  let captions = options.captions || VALID_CAPTIONS[0]
524
563
  let force_vod = options.force_vod || VALID_FORCE_VOD[0]
525
564
 
@@ -2388,6 +2427,10 @@ app.get('/', async function(req, res) {
2388
2427
  include_teams = session.credentials.fav_teams.toString()
2389
2428
  }
2390
2429
  body += '<p><span class="tooltip">By team<span class="tooltiptext">Including a team (MLB only, by abbreviation, in a comma-separated list if more than 1) will include all of its broadcasts, or if that team is not broadcasting the game, it will include the national broadcast or opponent\'s broadcast if available. It will also include affiliate games for those organizations. Channels/games subject to blackout will be omitted by default. See below for an additional option to override that.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + content_protect_b + '">calendar.ics</a></p>' + "\n"
2430
+
2431
+ body += '<p><span class="tooltip">By team w/ radio<span class="tooltiptext">Same as above, but defaults to that team\'s radio track, if available.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">calendar.ics</a></p>' + "\n"
2432
+
2433
+ body += '<p><span class="tooltip">By team w/ Spanish<span class="tooltiptext">Same as above, but defaults to that team\'s Spanish radio track, if available.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">calendar.ics</a></p>' + "\n"
2391
2434
 
2392
2435
  body += '<p><span class="tooltip">Include blackouts<span class="tooltiptext">An optional parameter added to the URL will include channels/games subject to blackout (although you may not be able to play those games).</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">calendar.ics</a></p>' + "\n"
2393
2436
 
@@ -2820,6 +2863,11 @@ app.get('/channels.m3u', async function(req, res) {
2820
2863
  startingChannelNumber = req.query.startingChannelNumber
2821
2864
  }
2822
2865
 
2866
+ let audio_track = false
2867
+ if ( req.query.audio_track ) {
2868
+ audio_track = req.query.audio_track
2869
+ }
2870
+
2823
2871
  let includeBlackouts = 'false'
2824
2872
  if ( req.query.includeBlackouts ) {
2825
2873
  includeBlackouts = req.query.includeBlackouts
@@ -2835,7 +2883,7 @@ app.get('/channels.m3u', async function(req, res) {
2835
2883
  includeOrgs = req.query.includeOrgs.toUpperCase().split(',')
2836
2884
  }
2837
2885
 
2838
- var body = await session.getTVData('channels', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, 'false', 'false', resolution, pipe, startingChannelNumber)
2886
+ var body = await session.getTVData('channels', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, 'false', audio_track, 'false', resolution, pipe, startingChannelNumber)
2839
2887
 
2840
2888
  res.writeHead(200, {'Content-Type': 'audio/x-mpegurl'})
2841
2889
  res.end(body)
@@ -2861,6 +2909,11 @@ app.get('/calendar.ics', async function(req, res) {
2861
2909
  if ( req.query.excludeTeams ) {
2862
2910
  excludeTeams = req.query.excludeTeams.toUpperCase().split(',')
2863
2911
  }
2912
+
2913
+ let audio_track = false
2914
+ if ( req.query.audio_track ) {
2915
+ audio_track = req.query.audio_track
2916
+ }
2864
2917
 
2865
2918
  let includeBlackouts = 'false'
2866
2919
  if ( req.query.includeBlackouts ) {
@@ -2884,7 +2937,7 @@ app.get('/calendar.ics', async function(req, res) {
2884
2937
 
2885
2938
  let server = (req.headers['x-forwarded-proto'] ? req.headers['x-forwarded-proto'] : 'http') + '://' + req.headers.host + http_root
2886
2939
 
2887
- var body = await session.getTVData('calendar', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles)
2940
+ var body = await session.getTVData('calendar', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles, audio_track)
2888
2941
 
2889
2942
  res.writeHead(200, {'Content-Type': 'text/calendar'})
2890
2943
  res.end(body)
@@ -2915,6 +2968,11 @@ app.get('/guide.xml', async function(req, res) {
2915
2968
  excludeTeams = req.query.excludeTeams.toUpperCase().split(',')
2916
2969
  }
2917
2970
 
2971
+ let audio_track = false
2972
+ if ( req.query.audio_track ) {
2973
+ audio_track = req.query.audio_track
2974
+ }
2975
+
2918
2976
  let includeBlackouts = 'false'
2919
2977
  if ( req.query.includeBlackouts ) {
2920
2978
  includeBlackouts = req.query.includeBlackouts
@@ -2942,7 +3000,7 @@ app.get('/guide.xml', async function(req, res) {
2942
3000
 
2943
3001
  let server = (req.headers['x-forwarded-proto'] ? req.headers['x-forwarded-proto'] : 'http') + '://' + req.headers.host + http_root
2944
3002
 
2945
- var body = await session.getTVData('guide', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles, offAir)
3003
+ var body = await session.getTVData('guide', mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles, audio_track, offAir)
2946
3004
 
2947
3005
  res.end(body)
2948
3006
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2025.08.16",
3
+ "version": "2025.08.17",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -1886,6 +1886,7 @@ class sessionClass {
1886
1886
  if ( (nationalArray.length == 2) && (nationalArray[1] == nationalCount) ) {
1887
1887
  this.debuglog('matched national event')
1888
1888
  mediaInfo = await this.check_media_state(broadcast, cache_data.dates[0].games[j].status.abstractGameState, mediaDate, cache_data.dates[0].games[j].gamePk)
1889
+ mediaInfo.teamType = 'Home'
1889
1890
  break
1890
1891
  }
1891
1892
 
@@ -1897,6 +1898,7 @@ class sessionClass {
1897
1898
  if ( (freeArray.length == 2) && (freeArray[1] == freeCount) ) {
1898
1899
  this.debuglog('matched free event')
1899
1900
  mediaInfo = await this.check_media_state(broadcast, cache_data.dates[0].games[j].status.abstractGameState, mediaDate, cache_data.dates[0].games[j].gamePk)
1901
+ mediaInfo.teamType = 'Home'
1900
1902
  break
1901
1903
  }
1902
1904
 
@@ -1910,6 +1912,11 @@ class sessionClass {
1910
1912
  } else {
1911
1913
  this.debuglog('matched team for event')
1912
1914
  mediaInfo = await this.check_media_state(broadcast, cache_data.dates[0].games[j].status.abstractGameState, mediaDate, cache_data.dates[0].games[j].gamePk)
1915
+ if ( team.toUpperCase() == away_team ) {
1916
+ mediaInfo.teamType = 'Away'
1917
+ } else {
1918
+ mediaInfo.teamType = 'Home'
1919
+ }
1913
1920
  break
1914
1921
  }
1915
1922
  }
@@ -2193,7 +2200,7 @@ class sessionClass {
2193
2200
  }
2194
2201
 
2195
2202
  // get TV data (channels or guide)
2196
- async getTVData(dataType, mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles='false', offAir='false', resolution='best', pipe='false', startingChannelNumber=1) {
2203
+ async getTVData(dataType, mediaType, includeTeams, excludeTeams, includeLevels, includeOrgs, server, includeBlackouts, includeTeamsInTitles='false', audio_track=false, offAir='false', resolution='best', pipe='false', startingChannelNumber=1) {
2197
2204
  try {
2198
2205
  this.debuglog('getTVData for ' + dataType)
2199
2206
 
@@ -2531,6 +2538,9 @@ class sessionClass {
2531
2538
  if ( streamMediaType == 'Video' ) {
2532
2539
  stream += '&resolution=' + resolution
2533
2540
  }
2541
+ if ( audio_track ) {
2542
+ stream += '&audio_track=' + audio_track
2543
+ }
2534
2544
  if ( includeBlackouts == 'true' ) stream += '&includeBlackouts=' + includeBlackouts
2535
2545
  if ( this.protection.content_protect ) stream += '&content_protect=' + this.protection.content_protect
2536
2546
  if ( pipe == 'true' ) stream = await this.convert_stream_to_pipe(stream, channelid)
@@ -2550,6 +2560,13 @@ class sessionClass {
2550
2560
  title = this.channelsFormattedTitle(subtitle, cache_data.dates[i].games[j].gameDate)
2551
2561
  } else {
2552
2562
  title = 'MLB: ' + subtitle + ' (' + station
2563
+ if ( audio_track ) {
2564
+ title += ' with'
2565
+ if ( audio_track.toLowerCase() == 'spanish' ) {
2566
+ title += ' Spanish'
2567
+ }
2568
+ title += ' Radio'
2569
+ }
2553
2570
  if ( language == 'es' ) {
2554
2571
  title += ' Spanish'
2555
2572
  }
@@ -2566,6 +2583,14 @@ class sessionClass {
2566
2583
  description += ' Spanish'
2567
2584
  }
2568
2585
  description += ' Radio'
2586
+ } else {
2587
+ if ( audio_track ) {
2588
+ description += ' with'
2589
+ if ( audio_track.toLowerCase() == 'spanish' ) {
2590
+ description += ' Spanish'
2591
+ }
2592
+ description += ' Radio, if available'
2593
+ }
2569
2594
  }
2570
2595
  description += '. '
2571
2596
  if ( cache_data.dates[i].games[j].seriesDescription != 'Regular Season' ) {
@@ -2628,6 +2653,9 @@ class sessionClass {
2628
2653
  if ( streamMediaType == 'Video' ) {
2629
2654
  location += '&resolution=' + resolution
2630
2655
  }
2656
+ if ( audio_track ) {
2657
+ location += '&audio_track=' + audio_track
2658
+ }
2631
2659
  if ( includeBlackouts == 'true' ) location += '&includeBlackouts=' + includeBlackouts
2632
2660
  if ( this.protection.content_protect ) location += '&content_protect=' + this.protection.content_protect
2633
2661
  calendar += await this.generate_ics_event(prefix, calendar_start, calendar_stop, subtitle, description, location)