mlbserver 2025.3.2-9.2 → 2025.3.3-0.2

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 +84 -19
  2. package/package.json +1 -1
  3. package/session.js +155 -19
package/index.js CHANGED
@@ -1041,7 +1041,7 @@ app.get('/gamechanger.m3u8', async function(req, res) {
1041
1041
 
1042
1042
  for ( gamechanger_resolution in GAMECHANGER_RESOLUTIONS ) {
1043
1043
  if ( resolution == gamechanger_resolution ) {
1044
- body += '#EXT-X-STREAM-INF:BANDWIDTH=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].bandwidth + '000,RESOLUTION=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].resolution + ',FRAME-RATE=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].frame_rate + ',CODECS="mp4a.40.2,avc1.' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].codec + '",CLOSED-CAPTIONS="cc",AUDIO="aac"' + '\n' + '/gamechangerplaylist?id=' + id + '&resolution=' + gamechanger_resolution + includeTeams + excludeTeams + content_protect + '\n'
1044
+ body += '#EXT-X-STREAM-INF:BANDWIDTH=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].bandwidth + '000,RESOLUTION=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].resolution + ',FRAME-RATE=' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].frame_rate + ',CODECS="mp4a.40.2,avc1.' + GAMECHANGER_RESOLUTIONS[gamechanger_resolution].codec + '",CLOSED-CAPTIONS="cc",AUDIO="aac"' + '\n' + '/gamechangerplaylist.m3u8?id=' + id + '&resolution=' + gamechanger_resolution + includeTeams + excludeTeams + content_protect + '\n'
1045
1045
  break
1046
1046
  }
1047
1047
  }
@@ -1055,10 +1055,10 @@ app.get('/gamechanger.m3u8', async function(req, res) {
1055
1055
 
1056
1056
 
1057
1057
  // Listen for gamechanger playlist requests
1058
- app.get('/gamechangerplaylist', async function(req, res) {
1058
+ app.get('/gamechangerplaylist.m3u8', async function(req, res) {
1059
1059
  if ( ! (await protect(req, res)) ) return
1060
1060
 
1061
- session.requestlog('gamechangerplaylist', req, true)
1061
+ session.requestlog('gamechangerplaylist.m3u8', req, true)
1062
1062
 
1063
1063
  let gamechangerAccess = new Date()
1064
1064
 
@@ -1562,11 +1562,11 @@ app.get('/', async function(req, res) {
1562
1562
 
1563
1563
  let currentDate = new Date()
1564
1564
 
1565
+ let entitlements = await session.getEntitlements()
1565
1566
  // MLB Network live stream for eligible USA subscribers
1566
1567
  try {
1567
- let entitlements = await session.getEntitlements()
1568
1568
  if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
1569
- body += '<tr><td><span class="tooltip">MLB Network<span class="tooltiptext">MLB Network live stream is now available in the USA for paid MLBTV subscribers or as a paid add-on, in addition to authenticated TV subscribers. <a href="https://www.mlb.com/news/mlb-network-launches-direct-to-consumer-streaming-option">See here for more information</a>.</span></span></td><td>'
1569
+ body += '<tr><td><span class="tooltip">MLB Network<span class="tooltiptext">MLB Network live stream is now available in the USA for paid MLBTV subscribers or as a paid add-on, in addition to authenticated TV subscribers. <a href="https://support.mlb.com/s/article/MLB-Network-Streaming-FAQ">See here for more information</a>.</span></span></td><td>'
1570
1570
  let querystring = '?event=MLBN'
1571
1571
  let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
1572
1572
  if ( linkType == VALID_LINK_TYPES[0] ) {
@@ -1589,6 +1589,58 @@ app.get('/', async function(req, res) {
1589
1589
  session.debuglog('MLB Network detect error : ' + e.message)
1590
1590
  }
1591
1591
 
1592
+ // SNLA live stream for entitled subscribers
1593
+ try {
1594
+ if ( entitlements.includes('SNLA_119') ) {
1595
+ body += '<tr><td><span class="tooltip">SportsNet LA<span class="tooltiptext">SNLA live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNLA-Plus-Subscription-Packages">See here for more information</a>.</span></span></td><td>'
1596
+ let querystring = '?event=SNLA'
1597
+ let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
1598
+ if ( linkType == VALID_LINK_TYPES[0] ) {
1599
+ if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
1600
+ if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
1601
+ }
1602
+ if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1603
+ if ( linkType == VALID_LINK_TYPES[1] ) {
1604
+ if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
1605
+ } else if ( linkType == VALID_LINK_TYPES[4] ) {
1606
+ querystring += '&filename=' + gameDate + ' SNLA'
1607
+ }
1608
+ querystring += content_protect_b
1609
+ multiviewquerystring += content_protect_b
1610
+ body += '<a href="' + thislink + querystring + '">SNLA</a>'
1611
+ body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1612
+ body += '</td></tr>' + "\n"
1613
+ } // end entitlements check
1614
+ } catch (e) {
1615
+ session.debuglog('SNLA detect error : ' + e.message)
1616
+ }
1617
+
1618
+ // SNY live stream for entitled subscribers
1619
+ try {
1620
+ if ( entitlements.includes('SNY_121') ) {
1621
+ body += '<tr><td><span class="tooltip">SNY<span class="tooltiptext">SNY live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNY-In-Market-Offering">See here for more information</a>.</span></span></td><td>'
1622
+ let querystring = '?event=SNY'
1623
+ let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
1624
+ if ( linkType == VALID_LINK_TYPES[0] ) {
1625
+ if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
1626
+ if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
1627
+ }
1628
+ if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
1629
+ if ( linkType == VALID_LINK_TYPES[1] ) {
1630
+ if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
1631
+ } else if ( linkType == VALID_LINK_TYPES[4] ) {
1632
+ querystring += '&filename=' + gameDate + ' SNY'
1633
+ }
1634
+ querystring += content_protect_b
1635
+ multiviewquerystring += content_protect_b
1636
+ body += '<a href="' + thislink + querystring + '">SNY</a>'
1637
+ body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1638
+ body += '</td></tr>' + "\n"
1639
+ } // end entitlements check
1640
+ } catch (e) {
1641
+ session.debuglog('SNY detect error : ' + e.message)
1642
+ }
1643
+
1592
1644
  if ( (mediaType == 'MLBTV') && ((level_ids == levels['MLB']) || level_ids.startsWith(levels['MLB'] + ',')) ) {
1593
1645
  // Recap Rundown beginning in 2023, disabled because it stopped working
1594
1646
  /*if ( (gameDate <= yesterday) && (gameDate >= '2023-03-31') && cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games && (cache_data.dates[0].games.length > 0) ) {
@@ -1609,7 +1661,8 @@ app.get('/', async function(req, res) {
1609
1661
  }
1610
1662
 
1611
1663
  // Big Inning
1612
- var big_inning
1664
+ // disabled Big Inning schedule scraping (March 2025)
1665
+ /*var big_inning
1613
1666
  if ( cache_data.dates && cache_data.dates[0] && (cache_data.dates[0].date >= today) && cache_data.dates[0].games && (cache_data.dates[0].games.length > 1) && cache_data.dates[0].games[0] && (cache_data.dates[0].games[0].seriesDescription == 'Regular Season') ) {
1614
1667
  // Scraped Big Inning schedule
1615
1668
  big_inning = await session.getBigInningSchedule(gameDate)
@@ -1618,12 +1671,13 @@ app.get('/', async function(req, res) {
1618
1671
  //big_inning = await session.generateBigInningSchedule(gameDate)
1619
1672
  }
1620
1673
  if ( big_inning && big_inning.start ) {
1621
- body += '<tr><td><span class="tooltip">' + new Date(big_inning.start).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + new Date(big_inning.end).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://www.mlb.com/live-stream-games/big-inning">See here for more information</a>.</span></span></td><td>'
1674
+ body += '<tr><td><span class="tooltip">' + new Date(big_inning.start).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + new Date(big_inning.end).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://support.mlb.com/s/article/What-Is-MLB-Big-Inning">See here for more information</a>.</span></span></td><td>'
1622
1675
  let compareStart = new Date(big_inning.start)
1623
1676
  compareStart.setMinutes(compareStart.getMinutes()-10)
1624
1677
  let compareEnd = new Date(big_inning.end)
1625
1678
  compareEnd.setHours(compareEnd.getHours()+1)
1626
- if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
1679
+ if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {*/
1680
+ body += '<tr><td><span class="tooltip">Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://support.mlb.com/s/article/What-Is-MLB-Big-Inning">See here for more information</a>.</span></span></td><td>'
1627
1681
  let querystring = '?event=biginning'
1628
1682
  let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
1629
1683
  if ( linkType == VALID_LINK_TYPES[0] ) {
@@ -1640,11 +1694,11 @@ app.get('/', async function(req, res) {
1640
1694
  multiviewquerystring += content_protect_b
1641
1695
  body += '<a href="' + thislink + querystring + '">Big Inning</a>'
1642
1696
  body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
1643
- } else {
1697
+ /*} else {
1644
1698
  body += 'Big Inning'
1645
- }
1699
+ }*/
1646
1700
  body += '</td></tr>' + "\n"
1647
- }
1701
+ //}
1648
1702
 
1649
1703
  // Game Changer
1650
1704
  if ( (gameDate >= today) && cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games && (cache_data.dates[0].games.length > 1) ) {
@@ -1659,15 +1713,15 @@ app.get('/', async function(req, res) {
1659
1713
  compareEnd.setHours(compareEnd.getHours()+4)
1660
1714
  body += '<tr><td><span class="tooltip">' + compareStart.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + compareEnd.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '<span class="tooltiptext">The game changer stream will automatically switch between the highest leverage active live non-blackout games, and should be available whenever there are such games available. Does not support adaptive bitrate switching, will default to 720p60 resolution if not specified.</span></span></td><td>'
1661
1715
  if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
1662
- let streamURL = server + '/gamechanger.m3u8'
1663
- let multiviewquerystring = streamURL + '?resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
1716
+ let streamURL = server + '/gamechanger.m3u8?'
1717
+ let multiviewquerystring = streamURL + 'resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
1664
1718
  streamURL += content_protect_a
1665
- if ( resolution != VALID_RESOLUTIONS[0] ) streamURL += '&resolution=' + resolution
1719
+ if ( resolution != VALID_RESOLUTIONS[0] ) streamURL += 'resolution=' + resolution + '&'
1666
1720
  if ( linkType != VALID_LINK_TYPES[1] ) {
1667
- streamURL = thislink + '?src=' + encodeURIComponent(streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b
1721
+ streamURL = thislink + '?src=' + encodeURIComponent(streamURL) + 'startFrom=' + VALID_START_FROM[1] + content_protect_b + '&'
1668
1722
  }
1669
1723
  if ( linkType == VALID_LINK_TYPES[4] ) {
1670
- streamURL += '&filename=' + gameDate + ' Game Changer'
1724
+ streamURL += 'filename=' + gameDate + ' Game Changer' + '&'
1671
1725
  }
1672
1726
  body += '<a href="' + streamURL + '">Game Changer</a>'
1673
1727
  body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + multiviewquerystring + '" onclick="addmultiview(this, [], excludeTeams)">'
@@ -2196,7 +2250,7 @@ app.get('/', async function(req, res) {
2196
2250
  resolution = 'best'
2197
2251
  }
2198
2252
 
2199
- body += '<p><span class="tooltip">All<span class="tooltiptext">Will include all live MLB broadcasts (all games plus MLB Network, Big Inning, Game Changer, and Multiview). If favorite team(s) have been provided, 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 + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + content_protect_b + '">calendar.ics</a></p>' + "\n"
2253
+ body += '<p><span class="tooltip">All<span class="tooltiptext">Will include all entitled live MLB broadcasts (games plus Big Inning, Game Changer, and Multiview, as well as MLB Network, SNLA, and/or SNY as appropriate). If favorite team(s) have been provided, 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 + content_protect_b + '">channels.m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + content_protect_b + '">guide.xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + content_protect_b + '">calendar.ics</a></p>' + "\n"
2200
2254
 
2201
2255
  let include_teams = 'ath,national'
2202
2256
  if ( session.credentials.fav_teams.length > 0 ) {
@@ -2211,9 +2265,20 @@ app.get('/', async function(req, res) {
2211
2265
 
2212
2266
  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="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=lidom' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=lidom' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=lidom' + content_protect_b + '">ics</a></p>' + "\n"
2213
2267
 
2214
- body += '<p><span class="tooltip">Include (or exclude) MLB Network<span class="tooltiptext">MLB Network live stream is now available in the USA for paid MLBTV subscribers or as a paid add-on, in addition to authenticated TV subscribers. <a href="https://www.mlb.com/news/mlb-network-launches-direct-to-consumer-streaming-option">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=mlbn' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=mlbn' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=mlb' + content_protect_b + '">ics</a></p>' + "\n"
2268
+ if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
2269
+ body += '<p><span class="tooltip">Include (or exclude) MLB Network<span class="tooltiptext">MLB Network live stream is now available in the USA for paid MLBTV subscribers or as a paid add-on, in addition to authenticated TV subscribers. <a href="https://support.mlb.com/s/article/MLB-Network-Streaming-FAQ">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=mlbn' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=mlbn' + content_protect_b + '">xml</a></p>' + "\n"
2270
+ }
2271
+
2272
+ if ( entitlements.includes('SNLA_119') ) {
2273
+ body += '<p><span class="tooltip">Include (or exclude) SportsNet LA<span class="tooltiptext">SNLA live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNLA-Plus-Subscription-Packages">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=snla' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=snla' + content_protect_b + '">xml</a></p>' + "\n"
2274
+ }
2275
+
2276
+ if ( entitlements.includes('SNY_121') ) {
2277
+ body += '<p><span class="tooltip">Include (or exclude) SNY<span class="tooltiptext">SNY live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNLA-Plus-Subscription-Packages">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=sny' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=sny' + content_protect_b + '">xml</a></p>' + "\n"
2278
+ }
2215
2279
 
2216
- body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://www.mlb.com/live-stream-games/big-inning">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">ics</a></p>' + "\n"
2280
+ // disabled Big Inning schedule scraping (March 2025)
2281
+ body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://www.mlb.com/live-stream-games/big-inning">See here for more information</a>.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">xml</a><!-- and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">ics</a>--></p>' + "\n"
2217
2282
 
2218
2283
  let gamechanger_resolution = resolution
2219
2284
  if ( gamechanger_resolution == VALID_RESOLUTIONS[0] ) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2025.03.29.2",
3
+ "version": "2025.03.30.2",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -39,13 +39,15 @@ const BREAK_TYPES = ['Game Advisory', 'Pitching Substitution', 'Offensive Substi
39
39
  // These are the events to keep, in addition to the last event of each at-bat, if we're skipping pitches
40
40
  const ACTION_TYPES = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Error', 'Out', 'Balk', 'Defensive Indiff', 'Other Advance']
41
41
  // These are some idle events to skip
42
- const IDLE_TYPES = ['Mound Visit', 'Batter Timeout', 'Pitcher Step Off']
42
+ const IDLE_TYPES = ['Mound Visit', 'Batter Timeout', 'Pitcher Step Off', 'challenge']
43
43
  const EVENT_START_PADDING = -3
44
44
  const PITCH_END_PADDING = 2
45
45
  const ACTION_END_PADDING = 7
46
46
  const MINIMUM_BREAK_DURATION = 5
47
47
  // extra padding for MLB events (2025)
48
48
  const MLB_PADDING = 39
49
+ // extra Game Changer padding for MLB (2025)
50
+ const MLB_GAMECHANGER_PADDING = 20
49
51
 
50
52
  const LI_TABLE = {
51
53
  1: {
@@ -1613,18 +1615,22 @@ class sessionClass {
1613
1615
  var response = await this.httpPost(reqObj)
1614
1616
  if ( response ) {
1615
1617
  this.debuglog('getStreamURL response : ' + JSON.stringify(response))
1616
- if ( response.data && response.data.initPlaybackSession && response.data.initPlaybackSession.playback && response.data.initPlaybackSession.playback.url && response.data.initPlaybackSession.playback.token && response.data.initPlaybackSession.playback.expiration ) {
1618
+ if ( response.data && response.data.initPlaybackSession && response.data.initPlaybackSession.playback && response.data.initPlaybackSession.playback.url ) {
1617
1619
  let rawStreamURL = response.data.initPlaybackSession.playback.url
1618
1620
  this.debuglog('getStreamURL rawStreamURL : ' + rawStreamURL)
1619
- let streamURL = rawStreamURL.replace(/[\/]([A-Za-z0-9_]+)[\/]/, '/')
1620
- let streamURLToken = response.data.initPlaybackSession.playback.token
1621
- let streamURLExpiration = response.data.initPlaybackSession.playback.expiration
1622
- this.debuglog('getStreamURL streamURL : ' + streamURL)
1623
- this.debuglog('getStreamURL token : ' + streamURLToken)
1624
- this.debuglog('getStreamURL expiration : ' + streamURLExpiration)
1625
- this.cacheStreamURL(mediaId, streamURL, streamURLToken, streamURLExpiration)
1626
- let streamInfo = {streamURL: streamURL, streamURLToken: streamURLToken}
1627
- return streamInfo
1621
+ if ( response.data.initPlaybackSession.playback.token == null ) {
1622
+ return rawStreamURL
1623
+ } else if ( response.data.initPlaybackSession.playback.token && response.data.initPlaybackSession.playback.expiration ) {
1624
+ let streamURL = rawStreamURL.replace(/[\/]([A-Za-z0-9_]+)[\/]/, '/')
1625
+ this.debuglog('getStreamURL streamURL : ' + streamURL)
1626
+ let streamURLToken = response.data.initPlaybackSession.playback.token
1627
+ this.debuglog('getStreamURL token : ' + streamURLToken)
1628
+ let streamURLExpiration = response.data.initPlaybackSession.playback.expiration
1629
+ this.debuglog('getStreamURL expiration : ' + streamURLExpiration)
1630
+ this.cacheStreamURL(mediaId, streamURL, streamURLToken, streamURLExpiration)
1631
+ let streamInfo = {streamURL: streamURL, streamURLToken: streamURLToken}
1632
+ return streamInfo
1633
+ }
1628
1634
  } else {
1629
1635
  this.log('getStreamURL streamURL not found')
1630
1636
  return false
@@ -2590,9 +2596,10 @@ class sessionClass {
2590
2596
  channels = this.sortObj(channels)
2591
2597
  channels = Object.assign(channels, nationalChannels)
2592
2598
 
2599
+
2600
+ let entitlements = await this.getEntitlements()
2593
2601
  // MLB Network live stream for eligible USA subscribers
2594
2602
  try {
2595
- let entitlements = await this.getEntitlements()
2596
2603
  if ( (entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS')) ) {
2597
2604
  if ( (mediaType == 'MLBTV') && ((includeLevels.length == 0) || includeLevels.includes('MLB') || includeLevels.includes('ALL')) ) {
2598
2605
  if ( (excludeTeams.length > 0) && excludeTeams.includes('MLBN') ) {
@@ -2622,6 +2629,70 @@ class sessionClass {
2622
2629
  } catch (e) {
2623
2630
  this.debuglog('getTVData MLB Network detect error : ' + e.message)
2624
2631
  }
2632
+
2633
+ // SNLA live stream for entitled subscribers
2634
+ try {
2635
+ if ( (entitlements.includes('SNLA_119')) ) {
2636
+ if ( (mediaType == 'MLBTV') && ((includeLevels.length == 0) || includeLevels.includes('MLB') || includeLevels.includes('ALL')) ) {
2637
+ if ( (excludeTeams.length > 0) && excludeTeams.includes('SNLA') ) {
2638
+ // do nothing
2639
+ } else if ( (includeTeams.length == 0) || includeTeams.includes('SNLA') ) {
2640
+ this.debuglog('getTVData processing SNLA')
2641
+ let logo = 'https://img.mlbstatic.com/mlb-images/image/upload/t_w640/mlb/fnwk2k0kgn1j8r8vvx3d.png'
2642
+ let channelid = mediaType + '.SNLA'
2643
+ //if ( this.protection.content_protect ) logo += '&amp;content_protect=' + this.protection.content_protect
2644
+ let stream = server + '/stream.m3u8?event=snla&mediaType=Video&resolution=' + resolution
2645
+ if ( this.protection.content_protect ) stream += '&content_protect=' + this.protection.content_protect
2646
+ if ( pipe == 'true' ) stream = await this.convert_stream_to_pipe(stream, channelid)
2647
+ channels[channelid] = await this.create_channel_object(channelid, logo, stream, mediaType)
2648
+
2649
+ let title = 'SportsNet LA'
2650
+ let description = 'Live stream of SNLA'
2651
+
2652
+ let start = this.convertDateToXMLTV(new Date(cache_data.dates[0].date + ' 00:00:00'))
2653
+ let stop = this.convertDateToXMLTV(new Date(cache_data.dates[cache_data.dates.length-1].date + ' 00:00:00'))
2654
+
2655
+ // SNLA guide XML
2656
+ programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertStringToAirDate(cache_data.dates[0].date))
2657
+ this.debuglog('getTVData completed SNLA')
2658
+ } // end includeTeams check
2659
+ } // end mediaType check
2660
+ } // end entitlements check
2661
+ } catch (e) {
2662
+ this.debuglog('getTVData SNLA detect error : ' + e.message)
2663
+ }
2664
+
2665
+ // SNY live stream for entitled subscribers
2666
+ try {
2667
+ if ( (entitlements.includes('SNY_121')) ) {
2668
+ if ( (mediaType == 'MLBTV') && ((includeLevels.length == 0) || includeLevels.includes('MLB') || includeLevels.includes('ALL')) ) {
2669
+ if ( (excludeTeams.length > 0) && excludeTeams.includes('SNY') ) {
2670
+ // do nothing
2671
+ } else if ( (includeTeams.length == 0) || includeTeams.includes('SNY') ) {
2672
+ this.debuglog('getTVData processing SNY')
2673
+ let logo = 'https://img.mlbstatic.com/mlb-images/image/upload/t_w640/mlb/le5jifzo6oylxtnuf0m1.png'
2674
+ let channelid = mediaType + '.SNY'
2675
+ //if ( this.protection.content_protect ) logo += '&amp;content_protect=' + this.protection.content_protect
2676
+ let stream = server + '/stream.m3u8?event=sny&mediaType=Video&resolution=' + resolution
2677
+ if ( this.protection.content_protect ) stream += '&content_protect=' + this.protection.content_protect
2678
+ if ( pipe == 'true' ) stream = await this.convert_stream_to_pipe(stream, channelid)
2679
+ channels[channelid] = await this.create_channel_object(channelid, logo, stream, mediaType)
2680
+
2681
+ let title = 'SNY'
2682
+ let description = 'Live stream of SNY'
2683
+
2684
+ let start = this.convertDateToXMLTV(new Date(cache_data.dates[0].date + ' 00:00:00'))
2685
+ let stop = this.convertDateToXMLTV(new Date(cache_data.dates[cache_data.dates.length-1].date + ' 00:00:00'))
2686
+
2687
+ // SNLA guide XML
2688
+ programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertStringToAirDate(cache_data.dates[0].date))
2689
+ this.debuglog('getTVData completed SNY')
2690
+ } // end includeTeams check
2691
+ } // end mediaType check
2692
+ } // end entitlements check
2693
+ } catch (e) {
2694
+ this.debuglog('getTVData SNY detect error : ' + e.message)
2695
+ }
2625
2696
 
2626
2697
  // Big Inning
2627
2698
  if ( (mediaType == 'MLBTV') && ((includeLevels.length == 0) || includeLevels.includes('MLB') || includeLevels.includes('ALL')) ) {
@@ -2640,7 +2711,8 @@ class sessionClass {
2640
2711
  let title = 'MLB Big Inning'
2641
2712
  let description = 'Live look-ins and big moments from around the league'
2642
2713
 
2643
- for (var i = 0; i < cache_data.dates.length; i++) {
2714
+ // disabled Big Inning schedule scraping March 2025
2715
+ /*for (var i = 0; i < cache_data.dates.length; i++) {
2644
2716
  // Scraped Big Inning schedule
2645
2717
  if ( (cache_data.dates[i].date >= today) && cache_data.dates[i].games && (cache_data.dates[i].games.length > 1) && cache_data.dates[i].games[0] && (cache_data.dates[i].games[0].seriesDescription == 'Regular Season') ) {
2646
2718
  await this.getBigInningSchedule()
@@ -2657,9 +2729,9 @@ class sessionClass {
2657
2729
  let stop = this.convertDateToXMLTV(new Date(this.cache.bigInningSchedule[gameDate].end))
2658
2730
 
2659
2731
  // Generated Big Inning schedule (disabled)
2660
- /*let big_inning = await this.generateBigInningSchedule(gameDate)
2661
- let start = this.convertDateToXMLTV(new Date(big_inning.start))
2662
- let stop = this.convertDateToXMLTV(new Date(big_inning.end))*/
2732
+ //let big_inning = await this.generateBigInningSchedule(gameDate)
2733
+ //let start = this.convertDateToXMLTV(new Date(big_inning.start))
2734
+ //let stop = this.convertDateToXMLTV(new Date(big_inning.end))
2663
2735
 
2664
2736
  // Big Inning calendar ICS
2665
2737
  let prefix = 'Watch'
@@ -2671,7 +2743,13 @@ class sessionClass {
2671
2743
  programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertDateToAirDate(new Date(this.cache.bigInningSchedule[gameDate].start)))
2672
2744
  }
2673
2745
  this.debuglog('getTVData completed Big Inning for date ' + cache_data.dates[i].date)
2674
- }
2746
+ }*/
2747
+
2748
+ // generic Big Inning guide XML
2749
+ let start = this.convertDateToXMLTV(new Date(cache_data.dates[0].date + ' 00:00:00'))
2750
+ let stop = this.convertDateToXMLTV(new Date(cache_data.dates[cache_data.dates.length-1].date + ' 00:00:00'))
2751
+ programs += await this.generate_xml_program(channelid, start, stop, title, description, logo, this.convertStringToAirDate(cache_data.dates[0].date))
2752
+
2675
2753
  this.debuglog('getTVData completed Big Inning')
2676
2754
  }
2677
2755
  }
@@ -3030,7 +3108,7 @@ class sessionClass {
3030
3108
  if ((skip_type == 1) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime) {
3031
3109
  action_index = j
3032
3110
  // skip type 2 (idle time) will look at all non-idle plays with an endTime
3033
- } else if ((skip_type == 2) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime && !IDLE_TYPES.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.description.includes(v))) {
3111
+ } else if ((skip_type == 2) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime && (!cache_data.liveData.plays.allPlays[i].playEvents[j].details || !cache_data.liveData.plays.allPlays[i].playEvents[j].details.description || !IDLE_TYPES.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.description.includes(v)))) {
3034
3112
  action_index = j
3035
3113
  } else if (skip_type == 3) {
3036
3114
  // skip type 3 excludes non-action pitches (events that aren't last in the at-bat and don't fall under action types)
@@ -3385,6 +3463,60 @@ class sessionClass {
3385
3463
  }
3386
3464
  }
3387
3465
 
3466
+ // Get linear channel stream URL
3467
+ async getLinearStreamURL(network) {
3468
+ try {
3469
+ this.debuglog('getLinearStreamURL')
3470
+
3471
+ let reqObj = {
3472
+ url: GRAPHQL_URL,
3473
+ simple: false,
3474
+ headers: {
3475
+ 'accept': 'application/json, text/plain, */*',
3476
+ 'accept-encoding': 'gzip, deflate, br',
3477
+ 'accept-language': 'en-US,en;q=0.5',
3478
+ 'authorization': 'Bearer ' + await this.getLoginToken() || this.halt('missing loginToken'),
3479
+ 'connection': 'keep-alive',
3480
+ 'content-type': 'application/json',
3481
+ 'x-client-name': 'WEB',
3482
+ 'x-client-version': '7.8.1',
3483
+ 'origin': 'https://www.mlb.com',
3484
+ 'referer': 'https://www.mlb.com/',
3485
+ 'user-agent': USER_AGENT
3486
+ },
3487
+ body: {
3488
+ 'operationName': 'contentCollections',
3489
+ 'query': 'query contentCollections(\n $categories: [ContentGroupCategory!]\n $includeRestricted: Boolean = false\n $includeSpoilers: Boolean = false\n $limit: Int = 10,\n $skip: Int = 0\n ) {\n contentCollections(\n categories: $categories\n includeRestricted: $includeRestricted\n includeSpoilers: $includeSpoilers\n limit: $limit\n skip: $skip\n ) {\n title\n category\n contents {\n assetTrackingKey\n contentDate\n contentId\n contentRestrictions\n description\n duration\n language\n mediaId\n officialDate\n title\n mediaState {\n state\n mediaType\n }\n thumbnails {\n thumbnailType\n templateUrl\n thumbnailUrl\n }\n }\n }\n }',
3490
+ 'variables': {
3491
+ 'categories': network,
3492
+ 'limit': '25'
3493
+ }
3494
+ },
3495
+ json: true,
3496
+ gzip: true
3497
+ }
3498
+ var response = await this.httpPost(reqObj)
3499
+ if ( response ) {
3500
+ this.debuglog('getLinearStreamURL response : ' + JSON.stringify(response))
3501
+ if ( response.data && response.data.contentCollections && (response.data.contentCollections.length > 0) && response.data.contentCollections[0].contents ) {
3502
+ for (var i=0; i<response.data.contentCollections[0].contents.length; i++) {
3503
+ try {
3504
+ let streamURL = this.getStreamURL(response.data.contentCollections[0].contents[i].mediaId)
3505
+ if ( streamURL ) {
3506
+ return streamURL
3507
+ }
3508
+ } catch(e) {
3509
+ this.debuglog('getLinearStreamURL getStreamURL error : ' + e.message)
3510
+ }
3511
+ }
3512
+ this.log('getLinearStreamURL stream url not found')
3513
+ }
3514
+ }
3515
+ } catch(e) {
3516
+ this.log('getLinearStreamURL error : ' + e.message)
3517
+ }
3518
+ }
3519
+
3388
3520
  // Get Recap Rundown data
3389
3521
  async getRecapRundownData(dateString) {
3390
3522
  try {
@@ -3478,6 +3610,10 @@ class sessionClass {
3478
3610
  playbackURL = await this.getRecapRundownURL(dateString)
3479
3611
  } else if ( eventName.toUpperCase() == 'MLBN' ) {
3480
3612
  playbackURL = 'https://falcon.mlbinfra.com/api/v1/linear/mlbn'
3613
+ } else if ( eventName.toUpperCase() == 'SNLA' ) {
3614
+ playbackURL = await this.getLinearStreamURL('SNLA_LIVE')
3615
+ } else if ( eventName.toUpperCase() == 'SNY' ) {
3616
+ playbackURL = await this.getLinearStreamURL('SNY_LIVE')
3481
3617
  } else {
3482
3618
  playbackURL = await this.getEventURL(eventName)
3483
3619
  }
@@ -4019,7 +4155,7 @@ class sessionClass {
4019
4155
 
4020
4156
  if ( !this.temp_cache.gamechanger[id].games ) this.temp_cache.gamechanger[id].games = []
4021
4157
  this.temp_cache.gamechanger[id].games.push(best_games)
4022
- let maxlength = (this.gamechanger_delay + 10) / 10
4158
+ let maxlength = (this.gamechanger_delay + 10 + MLB_GAMECHANGER_PADDING) / 10
4023
4159
  while ( this.temp_cache.gamechanger[id].games.length > maxlength ) {
4024
4160
  this.temp_cache.gamechanger[id].games.shift()
4025
4161
  }