mlbserver 2026.5.27 → 2026.7.22-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.
- package/index.js +1089 -396
- package/package.json +1 -1
- package/session.js +15 -0
- package/style.css +1119 -0
package/index.js
CHANGED
|
@@ -251,6 +251,24 @@ function corsMiddleware(req, res, next) {
|
|
|
251
251
|
httpAttach(multiview_app, corsMiddleware)
|
|
252
252
|
multiview_app.listen(multiview_port)
|
|
253
253
|
|
|
254
|
+
// Listen for stylesheet requests
|
|
255
|
+
app.get('/style.css', async function (req, res) {
|
|
256
|
+
if (!(await protect(req, res))) return
|
|
257
|
+
|
|
258
|
+
session.requestlog('style.css', req, true)
|
|
259
|
+
|
|
260
|
+
var body = await session.getCSS()
|
|
261
|
+
|
|
262
|
+
if (!body) {
|
|
263
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
|
|
264
|
+
res.end('style.css not found')
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
res.writeHead(200, { 'Content-Type': 'text/css; charset=utf-8' })
|
|
269
|
+
res.end(body)
|
|
270
|
+
})
|
|
271
|
+
|
|
254
272
|
// Listen for clear cache requests
|
|
255
273
|
app.get('/clearcache', async function(req, res) {
|
|
256
274
|
if ( ! (await protect(req, res)) ) return
|
|
@@ -1248,6 +1266,7 @@ app.get('/gamechangerplaylist.m3u8', async function(req, res) {
|
|
|
1248
1266
|
session.debuglog(game_changer_title + 'checking for new segments')
|
|
1249
1267
|
let u = streamURL + '_' + GAMECHANGER_RESOLUTIONS[resolution].url_bandwidth + 'K.m3u8'
|
|
1250
1268
|
headers['x-cdn-token'] = streamURLToken
|
|
1269
|
+
headers.gzip = true
|
|
1251
1270
|
requestRetry(u, headers, function(err, response) {
|
|
1252
1271
|
session.debuglog(game_changer_title + 'requested ' + u)
|
|
1253
1272
|
if (err) return res.error(err)
|
|
@@ -1472,7 +1491,7 @@ app.get('/login', async function(req, res) {
|
|
|
1472
1491
|
const redirect = req.query.redirect || '/';
|
|
1473
1492
|
const error = req.query.error ? '<p style="color:red;">Invalid username or password</p>' : '';
|
|
1474
1493
|
res.writeHead(200, {'Content-Type': 'text/html'});
|
|
1475
|
-
res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Login - ${appname}</title><style>body{
|
|
1494
|
+
res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Login - ${appname}</title><style>html,body{background-color:#0e0e0e;color:LightGray;font-family:Verdana,Tahoma,Roboto,sans-serif}h1{margin:0;;color:goldenrod}button{color:#000;background-color:lightgray;min-width:3rem;border:none;padding:1px 5px 1px 5px;text-align:center;text-decoration:none;display:inline-block;font-size:.9rem;border-radius:4px;cursor:pointer;margin:2px}</style></head><body><h1>Login to ${appname}</h1>${error}<form method="POST" action="${http_root}/login"><input type="hidden" name="redirect" value="${redirect}"><p>Username: <input type="text" name="username" required></p><p>Password: <input type="password" name="password" required></p><p><button type="submit">Login</button></p></form></body></html>`);
|
|
1476
1495
|
} catch (e) {
|
|
1477
1496
|
session.log('login get request error : ' + e.message);
|
|
1478
1497
|
res.end('login get request error, check log');
|
|
@@ -1643,6 +1662,7 @@ app.get('/', async function(req, res) {
|
|
|
1643
1662
|
if ( req.query.mediaType ) {
|
|
1644
1663
|
mediaType = req.query.mediaType
|
|
1645
1664
|
}
|
|
1665
|
+
var mediaTypeTV = mediaType
|
|
1646
1666
|
var resolution = VALID_RESOLUTIONS[0]
|
|
1647
1667
|
if ( req.query.resolution ) {
|
|
1648
1668
|
resolution = req.query.resolution
|
|
@@ -1695,15 +1715,9 @@ app.get('/', async function(req, res) {
|
|
|
1695
1715
|
content_protect_b = '&content_protect=' + content_protect
|
|
1696
1716
|
}
|
|
1697
1717
|
|
|
1698
|
-
var body = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>' + appname + '</title><link rel="icon" href="favicon.svg' + content_protect_a + '"><
|
|
1699
|
-
|
|
1700
|
-
// Highlights CSS
|
|
1701
|
-
body += '.modal{display:none;position:fixed;z-index:1;left:0;top:0;width:100%;height:100%;overflow:hidden;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)}.modal-content{position:absolute;top:100px;bottom:20px;left:50%;transform:translateX(-50%);background-color:#fefefe;padding:10px;border:1px solid #888;width:360px;overflow-y:auto;color:black}#highlights{overflow-y:auto;}#highlights a{color:black}.close{color:black;float:right;font-size:28px;font-weight:bold;}#highlights a:hover,#highlights a:focus,.close:hover,.close:focus{color:gray;text-decoration:none;cursor:pointer;}'
|
|
1702
|
-
|
|
1703
|
-
// Tooltip CSS
|
|
1704
|
-
body += '.tooltip{position:relative;display:inline-block;border-bottom: 1px dotted gray;}.tooltip .tooltiptext{font-size:.8em;visibility:hidden;width:360px;background-color:gray;color:white;text-align:left;padding:5px;border-radius:6px;position:absolute;z-index:1;top:100%;left:75%;margin-left:-30px;}.tooltip:hover .tooltiptext{visibility:visible;}'
|
|
1718
|
+
var body = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>' + appname + '</title><link rel="icon" href="favicon.svg' + content_protect_a + '"><link rel="stylesheet" href="/style.css' + content_protect_a + '">'
|
|
1705
1719
|
|
|
1706
|
-
body += '
|
|
1720
|
+
body += '<script type="text/javascript">' + "\n";
|
|
1707
1721
|
|
|
1708
1722
|
// Define option variables in page
|
|
1709
1723
|
body += 'var date="' + gameDate + '";var level="' + level + '";var org="' + org + '";var mediaType="' + mediaType + '";var resolution="' + resolution + '";var audio_track="' + audio_track + '";var captions="' + captions + '";var force_vod="' + force_vod + '";var inning_half="' + inning_half + '";var inning_number="' + inning_number + '";var skip="' + skip + '";var skip_adjust="' + skip_adjust + '";var pad="' + pad + '";var linkType="' + linkType + '";var startFrom="' + startFrom + '";var scores="' + scores + '";var controls="' + controls + '";var scan_mode="' + scan_mode + '";var content_protect="' + content_protect + '";' + "\n"
|
|
@@ -1726,28 +1740,80 @@ app.get('/', async function(req, res) {
|
|
|
1726
1740
|
body += '</script></head><body><h1>' + appname + '</h1>' + "\n"
|
|
1727
1741
|
|
|
1728
1742
|
if (argv.login_page) {
|
|
1729
|
-
body += '<p><
|
|
1743
|
+
body += '<p><form action="' + http_root + '/logout" method="GET"><button type="submit">Logout</button></form></p>' + "\n"
|
|
1730
1744
|
}
|
|
1731
|
-
|
|
1732
|
-
body += '<
|
|
1745
|
+
body +=`<div class="section">`
|
|
1746
|
+
body += '<div class="menuContainer settingsContainer">'
|
|
1747
|
+
body += '<div class="cardMenuHeader">Options</div>'
|
|
1748
|
+
body += '<div class="menuContent settingsContent">'
|
|
1733
1749
|
|
|
1734
1750
|
todayUTCHours -= 4
|
|
1735
|
-
|
|
1751
|
+
const settingsInfoText = {
|
|
1752
|
+
dateInfo: '"today" lasts until ' + todayUTCHours + ' AM EST. Home page will default to yesterday between ' + todayUTCHours + ' AM - ' + (YESTERDAY_UTC_HOURS - 4) + ' AM EST.',
|
|
1753
|
+
levelInfo: 'Major or minor league level.',
|
|
1754
|
+
orgInfo: 'Major league parent organization.',
|
|
1755
|
+
mediaTypeInfo: 'Video is TV broadcasts, Audio is English radio, and Spanish is Spanish radio (not available for all games).',
|
|
1756
|
+
linkTypeInfo: 'Embed will play in your browser (with AirPlay support), Stream will give you a stream URL to open directly in media players like Kodi or VLC, Chromecast is a desktop browser-based casting site, Advanced will play in your desktop browser with some extra tools and debugging information (Advanced may require you to disable insecure / mixed content blocking in your browser), and Download will prompt your browser to save the stream to a TS (Transport Stream) file.<br><br>NOTE: Chromecast may not be able to resolve local domain names; if so, you can simply access this page (and thus the streams) using an IP address instead.',
|
|
1757
|
+
videoControlsInfo: 'Choose whether to show or hide controls on the embedded video page. Helpful to avoid timeline spoilers.',
|
|
1758
|
+
startFromInfo: 'For the embedded player only: Beginning will start playback at the beginning of the stream (may be 1 hour before game time for live games), and Live will start at the live point (if the event is live -- archive games should always start at the beginning). You can still seek anywhere.',
|
|
1759
|
+
inningInfo: 'For video streams only: choose the inning to start with (and the score to display, if applicable). Inning number is relative -- for example, selecting inning 7 here will show inning 7 for scheduled 9-inning games, but inning 5 for scheduled 7-inning games, for example. If an inning number is specified, seeking to an earlier point will not be possible. Default is the beginning of the stream. To use with radio, set the video track to "None".',
|
|
1760
|
+
scoresInfo: 'Choose whether to show scores on this web page. Combine this with the inning option to only show scores through the specified inning.',
|
|
1761
|
+
videoInfo: 'For video streams only: you can manually specifiy a video track (resolution) to use. Adaptive will let your client choose. Best will select either 1080p60 (MLB) or 720p60 (MiLB). 504p is default for multiview (see below).<br/><br/>None will allow to remove the video tracks, if you just want to listen to the audio while using the "start at inning" or "skip breaks" options enabled.',
|
|
1762
|
+
audioInfo: 'For video streams only: you can manually specifiy which audio track to include. Some media players can accept them all and let you choose. Not all tracks are available for all games, and injected tracks may not work with skip options below.<br/><br/>If you select "none" for video above, picking an audio track here will make it an audio-only feed that supports the inning start and skip breaks options.',
|
|
1763
|
+
captionsInfo: 'For video streams only: you can disable the caption track, if one is present. This is handy if you do not want to disable it in your player each time.',
|
|
1764
|
+
skipInfo: '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.',
|
|
1765
|
+
skipAdjustInfo: 'Seconds to adjust the skip time video segments, if necessary. Try a negative number if the plays are ending before the video segments begin; use a positive number if the video segments are ending before the play happens.',
|
|
1766
|
+
padInfo: 'You can pad archive streams with random extra time at the end, to help conceal timeline spoilers.',
|
|
1767
|
+
forceVodInfo: 'For streams only: if your client does not support seeking in mlbserver live streams, turning this on will make the stream look like a VOD stream instead, allowing the client to start at the beginning and allowing the user to seek within it. You will need to reload the stream to watch/view past the current time, though.'
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
function settingInfoLabel(label, id) {
|
|
1771
|
+
return '<div class="settingLabel is-flex"><span>' + label + '</span><span class="info infoPadding" data-target="#' + id + '">?</span></div>'
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function settingInlineInfoLabel(label, id) {
|
|
1775
|
+
return '<span class="settingInlineLabel is-flex"><span>' + label + '</span><span class="info infoPadding" data-target="#' + id + '">?</span></span>'
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function settingInfo(id) {
|
|
1779
|
+
return '<div id="' + id + '" class="infoContainer"><div class="infoContent">' + settingsInfoText[id] + '</div></div>'
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
body += '<div class="settingsGroup">'
|
|
1783
|
+
body += '<div class="settingRow">' + settingInfoLabel('Date', 'dateInfo') + '<div class="settingControl"><input type="date" id="gameDate" value="' + gameDate + '"/> '
|
|
1736
1784
|
for (var i = 0; i < VALID_DATES.length; i++) {
|
|
1737
1785
|
body += '<button '
|
|
1738
1786
|
if ( ((VALID_DATES[i] == VALID_DATES[0]) && (gameDate == today)) || ((VALID_DATES[i] == VALID_DATES[1]) && (gameDate == yesterday)) ) body += 'class="default" '
|
|
1739
1787
|
body += 'onclick="date=\'' + VALID_DATES[i] + '\';reload()">' + VALID_DATES[i] + '</button> '
|
|
1740
1788
|
}
|
|
1741
|
-
body += '</
|
|
1789
|
+
body += '</div></div>' + "\n" + settingInfo('dateInfo') + '<div class="settingsMeta"><span>Updated ' + session.getCacheUpdatedDate(cache_name) + '</span></div>' + "\n"
|
|
1742
1790
|
|
|
1743
|
-
body += '<
|
|
1791
|
+
body += '<div class="settingRow">' + settingInfoLabel('Score', 'scoresInfo') + '<div class="settingControl">'
|
|
1792
|
+
for (var i = 0; i < VALID_SCORES.length; i++) {
|
|
1793
|
+
body += '<button '
|
|
1794
|
+
if ( scores == VALID_SCORES[i] ) body += 'class="default" '
|
|
1795
|
+
body += 'onclick="scores=\'' + VALID_SCORES[i] + '\';reload()">' + VALID_SCORES[i] + '</button> '
|
|
1796
|
+
}
|
|
1797
|
+
body += '</div></div>' + settingInfo('scoresInfo')
|
|
1798
|
+
|
|
1799
|
+
body += '<div class="settingRow">' + settingInfoLabel('Media Type', 'mediaTypeInfo') + '<div class="settingControl">'
|
|
1800
|
+
for (var i = 0; i < VALID_MEDIA_TYPES.length; i++) {
|
|
1801
|
+
body += '<button '
|
|
1802
|
+
if ( mediaType == VALID_MEDIA_TYPES[i] ) body += 'class="default" '
|
|
1803
|
+
body += 'onclick="mediaType=\'' + VALID_MEDIA_TYPES[i] + '\';reload()">' + VALID_MEDIA_TYPES[i] + '</button> '
|
|
1804
|
+
}
|
|
1805
|
+
body += '</div></div>' + "\n" + settingInfo('mediaTypeInfo')
|
|
1806
|
+
|
|
1807
|
+
body += '<details class="settingsDisclosure"><summary>Show more filters</summary><div class="settingsDisclosureContent">'
|
|
1808
|
+
body += '<div class="settingRow">' + settingInfoLabel('Level', 'levelInfo') + '<div class="settingControl">'
|
|
1744
1809
|
for (const [key, value] of Object.entries(levels)) {
|
|
1745
1810
|
body += '<button '
|
|
1746
1811
|
if ( level == key ) body += 'class="default" '
|
|
1747
1812
|
body += 'onclick="org=\'' + default_org + '\';level=\'' + key + '\';reload()">' + key + '</button> '
|
|
1748
1813
|
}
|
|
1749
1814
|
|
|
1750
|
-
body += '
|
|
1815
|
+
body += '<span class="settingsDivider">or</span>'
|
|
1816
|
+
body += settingInlineInfoLabel('Org', 'orgInfo') + ' '
|
|
1751
1817
|
body += '<select id="org" onchange="level=\'' + default_org + '\';org=this.value;reload()">'
|
|
1752
1818
|
body += '<option value="' + default_org + '">' + default_org + '</option>'
|
|
1753
1819
|
var orgs = session.getOrgs()
|
|
@@ -1756,48 +1822,22 @@ app.get('/', async function(req, res) {
|
|
|
1756
1822
|
if ( org == orgs[i] ) body += ' selected'
|
|
1757
1823
|
body += '>' + orgs[i] + '</option> '
|
|
1758
1824
|
}
|
|
1759
|
-
body += '</select></
|
|
1825
|
+
body += '</select></div></div>' + settingInfo('levelInfo') + settingInfo('orgInfo') + '</div></details></div>' + "\n"
|
|
1760
1826
|
|
|
1761
|
-
body += '<
|
|
1762
|
-
for (var i = 0; i < VALID_MEDIA_TYPES.length; i++) {
|
|
1763
|
-
body += '<button '
|
|
1764
|
-
if ( mediaType == VALID_MEDIA_TYPES[i] ) body += 'class="default" '
|
|
1765
|
-
body += 'onclick="mediaType=\'' + VALID_MEDIA_TYPES[i] + '\';reload()">' + VALID_MEDIA_TYPES[i] + '</button> '
|
|
1766
|
-
}
|
|
1767
|
-
body += '</p>' + "\n"
|
|
1768
|
-
|
|
1769
|
-
body += '<p><span class="tooltip">Link Type<span class="tooltiptext">Embed will play in your browser (with AirPlay support), Stream will give you a stream URL to open directly in media players like Kodi or VLC, Chromecast is a desktop browser-based casting site, Advanced will play in your desktop browser with some extra tools and debugging information (Advanced may require you to disable insecure / mixed content blocking in your browser), and Download will prompt your browser to save the stream to a TS (Transport Stream) file.<br><br>NOTE: Chromecast may not be able to resolve local domain names; if so, you can simply access this page (and thus the streams) using an IP address instead.</span></span>: '
|
|
1770
|
-
for (var i = 0; i < VALID_LINK_TYPES.length; i++) {
|
|
1771
|
-
body += '<button '
|
|
1772
|
-
if ( linkType == VALID_LINK_TYPES[i] ) body += 'class="default" '
|
|
1773
|
-
body += 'onclick="linkType=\'' + VALID_LINK_TYPES[i] + '\';reload()">' + VALID_LINK_TYPES[i] + '</button> '
|
|
1774
|
-
}
|
|
1775
|
-
body += '</p>' + "\n"
|
|
1827
|
+
body += '<div class="settingsGroup">'
|
|
1776
1828
|
|
|
1777
|
-
body += '<p>'
|
|
1778
1829
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1779
|
-
body += '<
|
|
1780
|
-
for (var i = 0; i < VALID_CONTROLS.length; i++) {
|
|
1781
|
-
body += '<button '
|
|
1782
|
-
if ( controls == VALID_CONTROLS[i] ) body += 'class="default" '
|
|
1783
|
-
body += 'onclick="controls=\'' + VALID_CONTROLS[i] + '\';reload()">' + VALID_CONTROLS[i] + '</button> '
|
|
1784
|
-
}
|
|
1785
|
-
body += '</p>' + "\n"
|
|
1786
|
-
|
|
1787
|
-
body += '<p><span class="tooltip">Start From<span class="tooltiptext">For the embedded player only: Beginning will start playback at the beginning of the stream (may be 1 hour before game time for live games), and Live will start at the live point (if the event is live -- archive games should always start at the beginning). You can still seek anywhere.</span></span>: '
|
|
1830
|
+
body += '<div class="settingRow">' + settingInfoLabel('Start From', 'startFromInfo') + '<div class="settingControl">'
|
|
1788
1831
|
for (var i = 0; i < VALID_START_FROM.length; i++) {
|
|
1789
1832
|
body += '<button '
|
|
1790
1833
|
if ( startFrom == VALID_START_FROM[i] ) body += 'class="default" '
|
|
1791
1834
|
body += 'onclick="startFrom=\'' + VALID_START_FROM[i] + '\';reload()">' + VALID_START_FROM[i] + '</button> '
|
|
1792
1835
|
}
|
|
1793
|
-
body +=
|
|
1794
|
-
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1795
|
-
body += 'or '
|
|
1796
|
-
}
|
|
1836
|
+
body += '</div></div>' + settingInfo('startFromInfo')
|
|
1797
1837
|
}
|
|
1798
1838
|
|
|
1799
1839
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1800
|
-
body += '<
|
|
1840
|
+
body += '<div class="settingRow">' + settingInfoLabel('Inning', 'inningInfo') + '<div class="settingControl">'
|
|
1801
1841
|
body += '<select id="inning_half" onchange="inning_half=this.value;reload()">'
|
|
1802
1842
|
for (var i = 0; i < VALID_INNING_HALF.length; i++) {
|
|
1803
1843
|
body += '<option value="' + VALID_INNING_HALF[i] + '"'
|
|
@@ -1815,23 +1855,119 @@ app.get('/', async function(req, res) {
|
|
|
1815
1855
|
}
|
|
1816
1856
|
body += '</select>'
|
|
1817
1857
|
}
|
|
1818
|
-
|
|
1858
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1859
|
+
body += '</div></div>' + "\n"
|
|
1860
|
+
body += settingInfo('inningInfo')
|
|
1861
|
+
}
|
|
1819
1862
|
|
|
1820
|
-
body += '<
|
|
1821
|
-
|
|
1863
|
+
body += '<details class="settingsDisclosure"><summary>Show more stream options</summary><div class="settingsDisclosureContent">'
|
|
1864
|
+
|
|
1865
|
+
body += '<div class="settingRow">' + settingInfoLabel('Link Type', 'linkTypeInfo') + '<div class="settingControl">'
|
|
1866
|
+
for (var i = 0; i < VALID_LINK_TYPES.length; i++) {
|
|
1822
1867
|
body += '<button '
|
|
1823
|
-
if (
|
|
1824
|
-
body += 'onclick="
|
|
1868
|
+
if ( linkType == VALID_LINK_TYPES[i] ) body += 'class="default" '
|
|
1869
|
+
body += 'onclick="linkType=\'' + VALID_LINK_TYPES[i] + '\';reload()">' + VALID_LINK_TYPES[i] + '</button> '
|
|
1870
|
+
}
|
|
1871
|
+
body += '</div></div>' + "\n" + settingInfo('linkTypeInfo')
|
|
1872
|
+
|
|
1873
|
+
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1874
|
+
body += '<div class="settingRow">' + settingInfoLabel('Video Controls', 'videoControlsInfo') + '<div class="settingControl">'
|
|
1875
|
+
for (var i = 0; i < VALID_CONTROLS.length; i++) {
|
|
1876
|
+
body += '<button '
|
|
1877
|
+
if ( controls == VALID_CONTROLS[i] ) body += 'class="default" '
|
|
1878
|
+
body += 'onclick="controls=\'' + VALID_CONTROLS[i] + '\';reload()">' + VALID_CONTROLS[i] + '</button> '
|
|
1879
|
+
}
|
|
1880
|
+
body += '</div></div>' + "\n" + settingInfo('videoControlsInfo')
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1884
|
+
body += '<div class="settingRow">' + settingInfoLabel('Video', 'videoInfo') + '<div class="settingControl">'
|
|
1885
|
+
body += '<button '
|
|
1886
|
+
if ( resolution == 'best' ) body += 'class="default" '
|
|
1887
|
+
body += 'onclick="resolution=\'best\';reload()">best</button> '
|
|
1888
|
+
for (var i = 0; i < VALID_RESOLUTIONS.length; i++) {
|
|
1889
|
+
body += '<button '
|
|
1890
|
+
if ( resolution == VALID_RESOLUTIONS[i] ) body += 'class="default" '
|
|
1891
|
+
body += 'onclick="resolution=\'' + VALID_RESOLUTIONS[i] + '\';reload()">' + VALID_RESOLUTIONS[i]
|
|
1892
|
+
if ( DISPLAY_BANDWIDTHS[i] != '' ) {
|
|
1893
|
+
body += '<br/><span>' + DISPLAY_BANDWIDTHS[i] + '</span>'
|
|
1894
|
+
}
|
|
1895
|
+
body += '</button> '
|
|
1896
|
+
}
|
|
1897
|
+
body += '</div></div>' + settingInfo('videoInfo')
|
|
1898
|
+
|
|
1899
|
+
body += '<div class="settingRow">' + settingInfoLabel('Audio', 'audioInfo') + '<div class="settingControl">'
|
|
1900
|
+
for (var i = 0; i < VALID_AUDIO_TRACKS.length; i++) {
|
|
1901
|
+
body += '<button '
|
|
1902
|
+
if ( audio_track == VALID_AUDIO_TRACKS[i] ) body += 'class="default" '
|
|
1903
|
+
body += 'onclick="audio_track=\'' + VALID_AUDIO_TRACKS[i] + '\';reload()">' + DISPLAY_AUDIO_TRACKS[i] + '</button> '
|
|
1904
|
+
}
|
|
1905
|
+
body += '</div></div>' + "\n" + settingInfo('audioInfo')
|
|
1906
|
+
|
|
1907
|
+
body += '<div class="settingRow">' + settingInfoLabel('Captions', 'captionsInfo') + '<div class="settingControl">'
|
|
1908
|
+
for (var i = 0; i < VALID_CAPTIONS.length; i++) {
|
|
1909
|
+
body += '<button '
|
|
1910
|
+
if ( captions == VALID_CAPTIONS[i] ) body += 'class="default" '
|
|
1911
|
+
body += 'onclick="captions=\'' + VALID_CAPTIONS[i] + '\';reload()">' + VALID_CAPTIONS[i] + '</button> '
|
|
1912
|
+
}
|
|
1913
|
+
body += '</div></div>' + settingInfo('captionsInfo')
|
|
1914
|
+
|
|
1915
|
+
body += '<div class="settingRow">' + settingInfoLabel('Skip', 'skipInfo') + '<div class="settingControl">'
|
|
1916
|
+
for (var i = 0; i < VALID_SKIP.length; i++) {
|
|
1917
|
+
body += '<button '
|
|
1918
|
+
if ( skip == VALID_SKIP[i] ) body += 'class="default" '
|
|
1919
|
+
body += 'onclick="skip=\'' + VALID_SKIP[i] + '\';reload()">' + VALID_SKIP[i] + '</button> '
|
|
1920
|
+
}
|
|
1921
|
+
if ( skip != VALID_SKIP[0] ) {
|
|
1922
|
+
body += settingInlineInfoLabel('Skip Adjust', 'skipAdjustInfo') + ' <input type="number" id="skip_adjust" value="' + skip_adjust + '" step="1" onchange="setTimeout(function(){skip_adjust=document.getElementById(\'skip_adjust\').value;reload()},750)" onblur="skip_adjust=this.value;reload()" style="vertical-align:top;font-size:.8em;width:3em"/>'
|
|
1923
|
+
}
|
|
1924
|
+
body += '</div></div>' + "\n" + settingInfo('skipInfo')
|
|
1925
|
+
if ( skip != VALID_SKIP[0] ) body += settingInfo('skipAdjustInfo')
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
body += '<div class="settingRow">' + settingInfoLabel('Pad', 'padInfo') + '<div class="settingControl">'
|
|
1929
|
+
for (var i = 0; i < VALID_PAD.length; i++) {
|
|
1930
|
+
body += '<button '
|
|
1931
|
+
if ( pad == VALID_PAD[i] ) body += 'class="default" '
|
|
1932
|
+
body += 'onclick="pad=\'' + VALID_PAD[i] + '\';reload()">' + VALID_PAD[i] + '</button> '
|
|
1933
|
+
}
|
|
1934
|
+
body += '</div></div>' + "\n" + settingInfo('padInfo')
|
|
1935
|
+
|
|
1936
|
+
if ( (linkType == VALID_LINK_TYPES[1]) && (gameDate == today) ) {
|
|
1937
|
+
body += '<div class="settingRow">' + settingInfoLabel('VOD', 'forceVodInfo') + '<div class="settingControl">'
|
|
1938
|
+
for (var i = 0; i < VALID_FORCE_VOD.length; i++) {
|
|
1939
|
+
body += '<button '
|
|
1940
|
+
if ( force_vod == VALID_FORCE_VOD[i] ) body += 'class="default" '
|
|
1941
|
+
body += 'onclick="force_vod=\'' + VALID_FORCE_VOD[i] + '\';reload()">' + VALID_FORCE_VOD[i] + '</button> '
|
|
1942
|
+
}
|
|
1943
|
+
body += '<span>(if client does not support seeking in live streams)</span></div></div>' + "\n" + settingInfo('forceVodInfo')
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
body += '</div></details></div></div></div></div>'
|
|
1947
|
+
|
|
1948
|
+
const subscriptionInfoText = {
|
|
1949
|
+
masn: 'MASN live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/MASN-In-Market-Offering">See here for more information</a>.',
|
|
1950
|
+
mlbn: '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>.',
|
|
1951
|
+
snla: 'SNLA live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNLA-Plus-Subscription-Packages">See here for more information</a>.',
|
|
1952
|
+
sny: 'SNY live stream for entitled subscribers. <a href="https://support.mlb.com/s/article/SNY-In-Market-Offering">See here for more information</a>.',
|
|
1953
|
+
bigInning: '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>.',
|
|
1954
|
+
gameChanger: '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.',
|
|
1955
|
+
streamFinder: 'The stream finder stream will automatically switch between games according to your uploaded preferences. This stream is not affiliated with Baseball Reference, do not contact them for support. Visit <a href="http://bit.ly/bbrefsf">http://bit.ly/bbrefsf</a> to create and export your preferences, then upload and save them to mlbserver <a href="#streamfinder">below</a>. Does not support adaptive bitrate switching, will default to 720p60 resolution if not specified.'
|
|
1825
1956
|
}
|
|
1826
|
-
body += '</p>' + "\n"
|
|
1827
1957
|
|
|
1828
|
-
|
|
1958
|
+
function subscriptionInfoLabel(label, id) {
|
|
1959
|
+
return '<span class="subscriptionLabel"><span>' + label + '</span><span class="info infoPadding" data-target="#' + id + '">?</span></span>'
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function subscriptionInfo(id, textKey) {
|
|
1963
|
+
return '<div id="' + id + '" class="infoContainer subscriptionInfoContainer"><div class="infoContent">' + subscriptionInfoText[textKey] + '</div></div>'
|
|
1964
|
+
}
|
|
1829
1965
|
|
|
1830
1966
|
// Rename some parameters before display links
|
|
1831
1967
|
var mediaFeedType = 'mediaFeedType'
|
|
1832
1968
|
var language = 'en'
|
|
1833
1969
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1834
|
-
|
|
1970
|
+
mediaTypeTV = 'MLBTV'
|
|
1835
1971
|
} else if ( mediaType == VALID_MEDIA_TYPES[2] ) {
|
|
1836
1972
|
//mediaType = VALID_MEDIA_TYPES[1]
|
|
1837
1973
|
language = 'es'
|
|
@@ -1857,10 +1993,107 @@ app.get('/', async function(req, res) {
|
|
|
1857
1993
|
|
|
1858
1994
|
let entitlements = await session.getEntitlements()
|
|
1859
1995
|
|
|
1996
|
+
body += `<script>
|
|
1997
|
+
document.addEventListener("DOMContentLoaded", function () {
|
|
1998
|
+
var infoButtons = document.getElementsByClassName("info");
|
|
1999
|
+
|
|
2000
|
+
for (var i = 0; i < infoButtons.length; i++) {
|
|
2001
|
+
infoButtons[i].addEventListener("click", function () {
|
|
2002
|
+
var target = this.getAttribute("data-target");
|
|
2003
|
+
|
|
2004
|
+
if (!target) {
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
var content = document.querySelector(target);
|
|
2009
|
+
|
|
2010
|
+
if (!content) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
this.classList.toggle("active");
|
|
2015
|
+
|
|
2016
|
+
if (content.style.maxHeight) {
|
|
2017
|
+
content.style.maxHeight = null;
|
|
2018
|
+
} else {
|
|
2019
|
+
content.style.maxHeight = content.scrollHeight + "px";
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
var closeButtons = document.getElementsByClassName("infoClose");
|
|
2025
|
+
|
|
2026
|
+
for (var j = 0; j < closeButtons.length; j++) {
|
|
2027
|
+
closeButtons[j].addEventListener("click", function () {
|
|
2028
|
+
var target = this.getAttribute("data-target");
|
|
2029
|
+
|
|
2030
|
+
if (!target) {
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
var content = document.querySelector(target);
|
|
2035
|
+
|
|
2036
|
+
if (!content) {
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
content.style.maxHeight = null;
|
|
2041
|
+
|
|
2042
|
+
var opener = document.querySelector('.info[data-target="' + target + '"]');
|
|
2043
|
+
|
|
2044
|
+
if (opener) {
|
|
2045
|
+
opener.classList.remove("active");
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
});
|
|
2050
|
+
</script>`
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
let entitledMASN = (entitlements.includes('MASN_110'))
|
|
2054
|
+
|
|
2055
|
+
let entitledMLBN = (entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS'))
|
|
2056
|
+
|
|
2057
|
+
let entitledSNLA = (entitlements.includes('SNLA_119'))
|
|
2058
|
+
|
|
2059
|
+
let entitledSNY = (entitlements.includes('SNY_121'))
|
|
2060
|
+
|
|
2061
|
+
let entitledBigInning = ( (entitlements.length > 0) && 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') )
|
|
2062
|
+
|
|
2063
|
+
let entitledChangerFinder = ((gameDate >= today) && cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games && (cache_data.dates[0].games.length > 1))
|
|
2064
|
+
|
|
2065
|
+
let hasEntitledStreams = entitledMASN || entitledMLBN || entitledSNLA || entitledSNY || entitledBigInning || entitledChangerFinder
|
|
2066
|
+
|
|
2067
|
+
let subscriptionCardVis
|
|
2068
|
+
if (hasEntitledStreams) {subscriptionCardVis = ''} else {subscriptionCardVis ='is-invisible'}
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
body += `<script>
|
|
2072
|
+
function toast() {
|
|
2073
|
+
var x = document.getElementById("snackbar");
|
|
2074
|
+
x.className = "show";
|
|
2075
|
+
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
|
|
2076
|
+
}
|
|
2077
|
+
</script>`
|
|
2078
|
+
body +=`<div class="section">`
|
|
2079
|
+
body += `<div class="cardContainer ${subscriptionCardVis}">
|
|
2080
|
+
<div class="cardHeader flex-between">
|
|
2081
|
+
<div class="">
|
|
2082
|
+
<span class="time">Subscription streams</span>
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
<div class="gameContent space-around">
|
|
2086
|
+
<div class="prePostLegend">
|
|
2087
|
+
<span><span class="prePostIndicator prePostLegendIndicator" title="Pregame show"><span class="prePostCircle prePostCircleActive"></span><span class="prePostCircle prePostCircleInactive"></span></span>Pregame show</span>
|
|
2088
|
+
<span><span class="prePostIndicator prePostLegendIndicator" title="Postgame show"><span class="prePostCircle prePostCircleInactive"></span><span class="prePostCircle prePostCircleActive"></span></span>Postgame show</span>
|
|
2089
|
+
</div>`
|
|
2090
|
+
|
|
2091
|
+
|
|
2092
|
+
|
|
1860
2093
|
// MASN live stream for entitled subscribers
|
|
1861
2094
|
try {
|
|
1862
|
-
if (
|
|
1863
|
-
body += '<
|
|
2095
|
+
if ( entitledMASN ) {
|
|
2096
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('MASN', 'masnInfo')
|
|
1864
2097
|
let querystring = '?event=MASN'
|
|
1865
2098
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1866
2099
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1875,9 +2108,10 @@ app.get('/', async function(req, res) {
|
|
|
1875
2108
|
}
|
|
1876
2109
|
querystring += content_protect_b
|
|
1877
2110
|
multiviewquerystring += content_protect_b
|
|
1878
|
-
body += '<a href="' + thislink + querystring + '">MASN</a>'
|
|
1879
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1880
|
-
body += '</
|
|
2111
|
+
body += '<span><a href="' + thislink + querystring + '">MASN</a>'
|
|
2112
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)"></span>'
|
|
2113
|
+
body += '</div>' + "\n"
|
|
2114
|
+
body += subscriptionInfo('masnInfo', 'masn')
|
|
1881
2115
|
} // end entitlements check
|
|
1882
2116
|
} catch (e) {
|
|
1883
2117
|
session.debuglog('MASN detect error : ' + e.message)
|
|
@@ -1885,8 +2119,8 @@ app.get('/', async function(req, res) {
|
|
|
1885
2119
|
|
|
1886
2120
|
// MLB Network live stream for eligible USA subscribers
|
|
1887
2121
|
try {
|
|
1888
|
-
if (
|
|
1889
|
-
body += '<
|
|
2122
|
+
if ( entitledMLBN || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
2123
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('MLB Network', 'mlbnInfo')
|
|
1890
2124
|
let querystring = '?event=MLBN'
|
|
1891
2125
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1892
2126
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1901,9 +2135,10 @@ app.get('/', async function(req, res) {
|
|
|
1901
2135
|
}
|
|
1902
2136
|
querystring += content_protect_b
|
|
1903
2137
|
multiviewquerystring += content_protect_b
|
|
1904
|
-
body += '<a href="' + thislink + querystring + '">MLB Network</a>'
|
|
1905
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1906
|
-
body += '</
|
|
2138
|
+
body += '<span><a href="' + thislink + querystring + '">MLB Network</a>'
|
|
2139
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)"></span>'
|
|
2140
|
+
body += '</div>' + "\n"
|
|
2141
|
+
body += subscriptionInfo('mlbnInfo', 'mlbn')
|
|
1907
2142
|
} // end entitlements check
|
|
1908
2143
|
} catch (e) {
|
|
1909
2144
|
session.debuglog('MLB Network detect error : ' + e.message)
|
|
@@ -1911,8 +2146,8 @@ app.get('/', async function(req, res) {
|
|
|
1911
2146
|
|
|
1912
2147
|
// SNLA live stream for entitled subscribers
|
|
1913
2148
|
try {
|
|
1914
|
-
if (
|
|
1915
|
-
body += '<
|
|
2149
|
+
if ( entitledSNLA ) {
|
|
2150
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('SportsNet LA', 'snlaInfo')
|
|
1916
2151
|
let querystring = '?event=SNLA'
|
|
1917
2152
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1918
2153
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1927,9 +2162,10 @@ app.get('/', async function(req, res) {
|
|
|
1927
2162
|
}
|
|
1928
2163
|
querystring += content_protect_b
|
|
1929
2164
|
multiviewquerystring += content_protect_b
|
|
1930
|
-
body += '<a href="' + thislink + querystring + '">SNLA</a>'
|
|
1931
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1932
|
-
body += '</
|
|
2165
|
+
body += '<span><a href="' + thislink + querystring + '">SNLA</a>'
|
|
2166
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)"></span>'
|
|
2167
|
+
body += '</div>' + "\n"
|
|
2168
|
+
body += subscriptionInfo('snlaInfo', 'snla')
|
|
1933
2169
|
} // end entitlements check
|
|
1934
2170
|
} catch (e) {
|
|
1935
2171
|
session.debuglog('SNLA detect error : ' + e.message)
|
|
@@ -1937,8 +2173,8 @@ app.get('/', async function(req, res) {
|
|
|
1937
2173
|
|
|
1938
2174
|
// SNY live stream for entitled subscribers
|
|
1939
2175
|
try {
|
|
1940
|
-
if (
|
|
1941
|
-
body += '<
|
|
2176
|
+
if ( entitledSNY ) {
|
|
2177
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('SNY', 'snyInfo')
|
|
1942
2178
|
let querystring = '?event=SNY'
|
|
1943
2179
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1944
2180
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1953,9 +2189,10 @@ app.get('/', async function(req, res) {
|
|
|
1953
2189
|
}
|
|
1954
2190
|
querystring += content_protect_b
|
|
1955
2191
|
multiviewquerystring += content_protect_b
|
|
1956
|
-
body += '<a href="' + thislink + querystring + '">SNY</a>'
|
|
1957
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1958
|
-
body += '</
|
|
2192
|
+
body += '<span><a href="' + thislink + querystring + '">SNY</a>'
|
|
2193
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)"></span>'
|
|
2194
|
+
body += '</div>' + "\n"
|
|
2195
|
+
body += subscriptionInfo('snyInfo', 'sny')
|
|
1959
2196
|
} // end entitlements check
|
|
1960
2197
|
} catch (e) {
|
|
1961
2198
|
session.debuglog('SNY detect error : ' + e.message)
|
|
@@ -1968,7 +2205,7 @@ app.get('/', async function(req, res) {
|
|
|
1968
2205
|
}
|
|
1969
2206
|
}
|
|
1970
2207
|
|
|
1971
|
-
if ( (
|
|
2208
|
+
if ( (mediaTypeTV == 'MLBTV') && ((level_ids == levels['MLB']) || level_ids.startsWith(levels['MLB'] + ',')) ) {
|
|
1972
2209
|
// Recap Rundown beginning in 2023, disabled because it stopped working
|
|
1973
2210
|
/*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) ) {
|
|
1974
2211
|
body += '<tr><td><span class="tooltip">VOD<span class="tooltiptext">Recap Rundown plays all of a day\'s recaps in order.</span></span></td><td>'
|
|
@@ -1985,14 +2222,16 @@ app.get('/', async function(req, res) {
|
|
|
1985
2222
|
|
|
1986
2223
|
// Big Inning
|
|
1987
2224
|
var big_inning
|
|
1988
|
-
if (
|
|
2225
|
+
if ( entitledBigInning ) {
|
|
1989
2226
|
// Scraped Big Inning schedule
|
|
1990
2227
|
big_inning = await session.getBigInningSchedule(gameDate)
|
|
1991
2228
|
}
|
|
1992
2229
|
if ( big_inning ) {
|
|
2230
|
+
let bigInningRendered = false
|
|
1993
2231
|
for (var i = 0; i < big_inning.length; i++) {
|
|
1994
|
-
if ( big_inning[i].start ) {
|
|
1995
|
-
body += '<
|
|
2232
|
+
if ( big_inning[i].start && !bigInningRendered ) {
|
|
2233
|
+
body += '<div class="flex-between subscriptionStream">' + "\n"
|
|
2234
|
+
body += '<span class="tinytext">' + new Date(big_inning[i].start).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + new Date(big_inning[i].end).toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '</span>'
|
|
1996
2235
|
let compareStart = new Date(big_inning[i].start)
|
|
1997
2236
|
compareStart.setMinutes(compareStart.getMinutes()-10)
|
|
1998
2237
|
let compareEnd = new Date(big_inning[i].end)
|
|
@@ -2012,18 +2251,20 @@ app.get('/', async function(req, res) {
|
|
|
2012
2251
|
}
|
|
2013
2252
|
querystring += content_protect_b
|
|
2014
2253
|
multiviewquerystring += content_protect_b
|
|
2015
|
-
body += '<a href="' + thislink + querystring + '">Big Inning</a>'
|
|
2016
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
2254
|
+
body += '<span>' + subscriptionInfoLabel('<a href="' + thislink + querystring + '">Big Inning</a>', 'bigInningInfo')
|
|
2255
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)"></span>'
|
|
2017
2256
|
} else {
|
|
2018
|
-
body += 'Big Inning'
|
|
2257
|
+
body += subscriptionInfoLabel('Big Inning', 'bigInningInfo')
|
|
2019
2258
|
}
|
|
2020
|
-
body += '</
|
|
2259
|
+
body += '</div>' + "\n"
|
|
2260
|
+
body += subscriptionInfo('bigInningInfo', 'bigInning')
|
|
2261
|
+
bigInningRendered = true
|
|
2021
2262
|
}
|
|
2022
2263
|
}
|
|
2023
2264
|
}
|
|
2024
|
-
|
|
2265
|
+
|
|
2025
2266
|
// Game Changer and Stream Finder
|
|
2026
|
-
if (
|
|
2267
|
+
if ( entitledChangerFinder ) {
|
|
2027
2268
|
let gameIndexes = await session.get_first_and_last_games(cache_data.dates[0].games, blackouts)
|
|
2028
2269
|
if ( (typeof gameIndexes.firstGameIndex !== 'undefined') && (typeof gameIndexes.lastGameIndex !== 'undefined') && (gameIndexes.firstGameIndex !== gameIndexes.lastGameIndex) ) {
|
|
2029
2270
|
let compareStart = new Date(cache_data.dates[0].games[gameIndexes.firstGameIndex].gameDate)
|
|
@@ -2033,7 +2274,8 @@ app.get('/', async function(req, res) {
|
|
|
2033
2274
|
compareEnd.setHours(compareEnd.getHours()+4)
|
|
2034
2275
|
}
|
|
2035
2276
|
compareEnd.setHours(compareEnd.getHours()+4)
|
|
2036
|
-
body += '<
|
|
2277
|
+
body += '<div class="flex-between subscriptionStream">' + "\n"
|
|
2278
|
+
body += '<span class="tinytext">' + compareStart.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + compareEnd.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '</span>'
|
|
2037
2279
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
2038
2280
|
let streamURL = server + '/gamechanger.m3u8'
|
|
2039
2281
|
let multiviewquerystring = '/gamechanger.m3u8?resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
|
|
@@ -2049,14 +2291,15 @@ app.get('/', async function(req, res) {
|
|
|
2049
2291
|
if ( linkType == VALID_LINK_TYPES[4] ) {
|
|
2050
2292
|
streamURL += '&filename=' + gameDate + ' Game Changer'
|
|
2051
2293
|
}
|
|
2052
|
-
body += '<a href="' + streamURL + '">Game Changer</a>'
|
|
2053
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + multiviewquerystring + '" onclick="addmultiview(this, [], excludeTeams)">'
|
|
2294
|
+
body += '<span>' + subscriptionInfoLabel('<a href="' + streamURL + '">Game Changer</a>', 'gameChangerInfo')
|
|
2295
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + multiviewquerystring + '" onclick="addmultiview(this, [], excludeTeams)"></span>'
|
|
2054
2296
|
} else {
|
|
2055
|
-
body += 'Game Changer'
|
|
2297
|
+
body += subscriptionInfoLabel('Game Changer', 'gameChangerInfo')
|
|
2056
2298
|
}
|
|
2057
|
-
body += '</
|
|
2058
|
-
|
|
2059
|
-
body += '<
|
|
2299
|
+
body += '</div>' + "\n"
|
|
2300
|
+
body += subscriptionInfo('gameChangerInfo', 'gameChanger')
|
|
2301
|
+
body += '<div class="flex-between subscriptionStream">' + "\n"
|
|
2302
|
+
body += '<span class="tinytext">' + compareStart.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + compareEnd.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '</span>'
|
|
2060
2303
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
2061
2304
|
let streamURL = server + '/gamechanger.m3u8?streamFinder=on'
|
|
2062
2305
|
let multiviewquerystring = '/gamechanger.m3u8?streamFinder=on&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
|
|
@@ -2072,15 +2315,17 @@ app.get('/', async function(req, res) {
|
|
|
2072
2315
|
if ( linkType == VALID_LINK_TYPES[4] ) {
|
|
2073
2316
|
streamURL += '&filename=' + gameDate + ' Stream Finder'
|
|
2074
2317
|
}
|
|
2075
|
-
body += '<a href="' + streamURL + '">Stream Finder</a>'
|
|
2076
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + multiviewquerystring + '" onclick="addmultiview(this, [], excludeTeams)">'
|
|
2318
|
+
body += '<span>' + subscriptionInfoLabel('<a href="' + streamURL + '">Stream Finder</a>', 'streamFinderInfo')
|
|
2319
|
+
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + multiviewquerystring + '" onclick="addmultiview(this, [], excludeTeams)"></span>'
|
|
2077
2320
|
} else {
|
|
2078
|
-
body += 'Stream Finder'
|
|
2321
|
+
body += subscriptionInfoLabel('Stream Finder', 'streamFinderInfo')
|
|
2079
2322
|
}
|
|
2080
|
-
body += '</
|
|
2323
|
+
body += '</div>'
|
|
2324
|
+
body += subscriptionInfo('streamFinderInfo', 'streamFinder')
|
|
2081
2325
|
}
|
|
2082
2326
|
}
|
|
2083
2327
|
}
|
|
2328
|
+
body += `</div></div>`
|
|
2084
2329
|
|
|
2085
2330
|
if ( cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games ) {
|
|
2086
2331
|
for (var j = 0; j < cache_data.dates[0].games.length; j++) {
|
|
@@ -2088,43 +2333,70 @@ app.get('/', async function(req, res) {
|
|
|
2088
2333
|
|
|
2089
2334
|
let game_started = false
|
|
2090
2335
|
|
|
2091
|
-
let
|
|
2092
|
-
let
|
|
2336
|
+
let parentAwayVis = 'is-invisible'
|
|
2337
|
+
let awayteam = cache_data.dates[0].games[j].teams['away'].team.clubName
|
|
2338
|
+
let awayteam_abbr = cache_data.dates[0].games[j].teams['away'].team.abbreviation
|
|
2093
2339
|
if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
|
|
2094
2340
|
awayteam = cache_data.dates[0].games[j].teams['away'].team.shortName
|
|
2095
2341
|
let parentOrgName = cache_data.dates[0].games[j].teams['away'].team.parentOrgName
|
|
2096
|
-
if (parentOrgName != 'Office of the Commissioner')
|
|
2342
|
+
if (parentOrgName != 'Office of the Commissioner') parentAwayVis = '';
|
|
2097
2343
|
awayteam_abbr = cache_data.dates[0].games[j].teams['away'].team.abbreviation
|
|
2098
2344
|
awayteam_level = session.getLevelNameFromSportId(cache_data.dates[0].games[j].teams['away'].team.sport.id)
|
|
2099
2345
|
}
|
|
2100
|
-
let
|
|
2101
|
-
let
|
|
2346
|
+
let parentHomeVis = 'is-invisible'
|
|
2347
|
+
let hometeam = cache_data.dates[0].games[j].teams['home'].team.clubName
|
|
2348
|
+
let hometeam_abbr = cache_data.dates[0].games[j].teams['home'].team.abbreviation
|
|
2102
2349
|
if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
|
|
2103
2350
|
hometeam = cache_data.dates[0].games[j].teams['home'].team.shortName
|
|
2104
2351
|
let parentOrgName = cache_data.dates[0].games[j].teams['home'].team.parentOrgName
|
|
2105
|
-
if (parentOrgName != 'Office of the Commissioner')
|
|
2352
|
+
if (parentOrgName != 'Office of the Commissioner') parentHomeVis = '';
|
|
2106
2353
|
hometeam_abbr = cache_data.dates[0].games[j].teams['home'].team.abbreviation
|
|
2107
2354
|
hometeam_level = session.getLevelNameFromSportId(cache_data.dates[0].games[j].teams['home'].team.sport.id)
|
|
2108
2355
|
}
|
|
2109
2356
|
|
|
2110
|
-
let
|
|
2357
|
+
let awayDisplay = awayteam
|
|
2358
|
+
let homeDisplay = hometeam
|
|
2359
|
+
/*
|
|
2111
2360
|
if ( awayteam_abbr ) {
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2361
|
+
awayDisplay =
|
|
2362
|
+
'<span class="tooltip">' +
|
|
2363
|
+
awayteam +
|
|
2364
|
+
'<span class="tooltiptext left">' +
|
|
2365
|
+
awayteam_abbr + ' (' + awayteam_level + ')' +
|
|
2366
|
+
'</span></span>'
|
|
2115
2367
|
}
|
|
2116
|
-
|
|
2368
|
+
|
|
2117
2369
|
if ( hometeam_abbr ) {
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2370
|
+
homeDisplay =
|
|
2371
|
+
'<span class="tooltip">' +
|
|
2372
|
+
hometeam +
|
|
2373
|
+
'<span class="tooltiptext right">' +
|
|
2374
|
+
hometeam_abbr + ' (' + hometeam_level + ')' +
|
|
2375
|
+
'</span></span>'
|
|
2121
2376
|
}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
let
|
|
2377
|
+
*/
|
|
2378
|
+
|
|
2379
|
+
let filename_teams = awayteam + ' @ ' + hometeam
|
|
2380
|
+
let awayPitcher = ''
|
|
2381
|
+
let homePitcher = ''
|
|
2382
|
+
let state = ''
|
|
2383
|
+
|
|
2384
|
+
let awayscore = ''
|
|
2385
|
+
let homescore = ''
|
|
2386
|
+
|
|
2387
|
+
let noteworthyTag = ''
|
|
2388
|
+
let noteworthyTooltip = ''
|
|
2389
|
+
let noteworthyCSS = ''
|
|
2390
|
+
let tooltipCSS = ''
|
|
2391
|
+
let tooltipTextCSS = ''
|
|
2392
|
+
|
|
2393
|
+
let streamSource = {
|
|
2394
|
+
homeTV: '',
|
|
2395
|
+
awayTV: '',
|
|
2396
|
+
}
|
|
2125
2397
|
|
|
2126
2398
|
if ( cache_data.dates[0].games[j].status.startTimeTBD == true ) {
|
|
2127
|
-
state
|
|
2399
|
+
state = 'Time TBD'
|
|
2128
2400
|
} else {
|
|
2129
2401
|
let startTime = new Date(cache_data.dates[0].games[j].gameDate)
|
|
2130
2402
|
state += startTime.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true })
|
|
@@ -2138,8 +2410,8 @@ app.get('/', async function(req, res) {
|
|
|
2138
2410
|
relative_inning = relative_inning < 0 ? 0 : relative_inning
|
|
2139
2411
|
//if ( (scores == VALID_SCORES[1]) && (cache_data.dates[0].games[j].gameUtils.isLive || cache_data.dates[0].games[j].gameUtils.isFinal) && !cache_data.dates[0].games[j].gameUtils.isCancelled && !cache_data.dates[0].games[j].gameUtils.isPostponed ) {
|
|
2140
2412
|
if ( (scores == VALID_SCORES[1]) && (abstractGameState != 'Preview') && (detailedState != 'Postponed') ) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2413
|
+
awayscore = ''
|
|
2414
|
+
homescore = ''
|
|
2143
2415
|
if ( (inning_number != VALID_INNING_NUMBER[0]) && cache_data.dates[0].games[j].linescore && cache_data.dates[0].games[j].linescore.innings ) {
|
|
2144
2416
|
awayscore = 0
|
|
2145
2417
|
homescore = 0
|
|
@@ -2169,59 +2441,86 @@ app.get('/', async function(req, res) {
|
|
|
2169
2441
|
}
|
|
2170
2442
|
}
|
|
2171
2443
|
if ( display_inning != '' ) {
|
|
2172
|
-
state =
|
|
2444
|
+
state = display_inning
|
|
2173
2445
|
}
|
|
2174
2446
|
} else {
|
|
2175
2447
|
awayscore = cache_data.dates[0].games[j].teams['away'].score
|
|
2176
2448
|
homescore = cache_data.dates[0].games[j].teams['home'].score
|
|
2177
|
-
|
|
2449
|
+
//if ( cache_data.dates[0].games[j].gameUtils.isLive && !cache_data.dates[0].games[j].gameUtils.isFinal ) {
|
|
2178
2450
|
if ( abstractGameState == 'Live' ) {
|
|
2179
|
-
state =
|
|
2451
|
+
state = cache_data.dates[0].games[j].linescore.inningHalf.substr(0,1) + cache_data.dates[0].games[j].linescore.currentInning
|
|
2180
2452
|
//} else if ( cache_data.dates[0].games[j].gameUtils.isFinal ) {
|
|
2181
2453
|
} else if ( abstractGameState == 'Final' ) {
|
|
2182
|
-
state =
|
|
2454
|
+
state = detailedState
|
|
2183
2455
|
}
|
|
2184
2456
|
if ( cache_data.dates[0].games[j].flags ) {
|
|
2185
2457
|
if ( cache_data.dates[0].games[j].flags.perfectGame == true ) {
|
|
2186
|
-
|
|
2458
|
+
noteworthyTag = 'PG'
|
|
2459
|
+
noteworthyTooltip = 'Perfect Game'
|
|
2460
|
+
noteworthyCSS = 'tag noteworthy'
|
|
2461
|
+
tooltipCSS = 'tooltip'
|
|
2462
|
+
tooltipTextCSS = 'tooltiptext'
|
|
2187
2463
|
} else if ( cache_data.dates[0].games[j].flags.noHitter == true ) {
|
|
2188
|
-
|
|
2464
|
+
noteworthyTag = 'NH'
|
|
2465
|
+
noteworthyTooltip = 'No-Hitter'
|
|
2466
|
+
noteworthyCSS = 'tag noteworthy'
|
|
2467
|
+
tooltipCSS = 'tooltip'
|
|
2468
|
+
tooltipTextCSS = 'tooltiptext'
|
|
2469
|
+
} else {
|
|
2470
|
+
noteworthyTag = ''
|
|
2189
2471
|
}
|
|
2190
2472
|
}
|
|
2191
2473
|
}
|
|
2192
|
-
|
|
2193
|
-
//} else if ( cache_data.dates[0].games[j].gameUtils.isCancelled || cache_data.dates[0].games[j].gameUtils.isPostponed || cache_data.dates[0].games[j].gameUtils.isSuspended ) {
|
|
2474
|
+
//} else if ( cache_data.dates[0].games[j].gameUtils.isCancelled || cache_data.dates[0].games[j].gameUtils.isPostponed || cache_data.dates[0].games[j].gameUtils.isSuspended ) {
|
|
2194
2475
|
} else if ( detailedState == 'Postponed' ) {
|
|
2195
|
-
state =
|
|
2196
|
-
|
|
2476
|
+
state = detailedState
|
|
2477
|
+
//} else if ( cache_data.dates[0].games[j].gameUtils.isDelayed ) {
|
|
2197
2478
|
} else if ( detailedState.startsWith('Delayed') ) {
|
|
2198
|
-
|
|
2479
|
+
state = detailedState
|
|
2199
2480
|
}
|
|
2200
2481
|
|
|
2201
|
-
|
|
2482
|
+
var filename = gameDate + ' ' + filename_teams + ' '
|
|
2483
|
+
|
|
2484
|
+
let scheduleVis = 'is-invisible'
|
|
2485
|
+
let doubleHeaderVis = 'is-invisible'
|
|
2486
|
+
let inningsVis = 'is-invisible'
|
|
2487
|
+
let blackoutVis = 'is-invisible'
|
|
2488
|
+
let blackoutToolTipVis = 'is-invisible'
|
|
2489
|
+
let blackoutType = ''
|
|
2490
|
+
let blackoutExpiry = ''
|
|
2491
|
+
|
|
2492
|
+
let scheduleDesc = ''
|
|
2493
|
+
let gameNo = ''
|
|
2494
|
+
let resumeText = ''
|
|
2495
|
+
|
|
2496
|
+
let highlightLink = ''
|
|
2497
|
+
let condensedLink = ''
|
|
2498
|
+
let recapLink = ''
|
|
2202
2499
|
|
|
2203
2500
|
if ( cache_data.dates[0].games[j].doubleHeader != 'N' ) {
|
|
2204
|
-
|
|
2501
|
+
gameNo = cache_data.dates[0].games[j].gameNumber
|
|
2502
|
+
doubleHeaderVis = ''
|
|
2205
2503
|
filename += 'Game ' + cache_data.dates[0].games[j].gameNumber + ' '
|
|
2206
2504
|
}
|
|
2207
2505
|
if ( cache_data.dates[0].games[j].description ) {
|
|
2208
|
-
|
|
2506
|
+
scheduleDesc = cache_data.dates[0].games[j].description
|
|
2507
|
+
scheduleVis = ''
|
|
2209
2508
|
}
|
|
2210
2509
|
if ( scheduledInnings != '9' ) {
|
|
2211
|
-
|
|
2510
|
+
inningsVis = ''
|
|
2212
2511
|
}
|
|
2213
2512
|
var resumeStatus = false
|
|
2214
2513
|
if ( cache_data.dates[0].games[j].resumeGameDate || cache_data.dates[0].games[j].resumedFromDate ) {
|
|
2215
|
-
|
|
2514
|
+
let resumeText = ''
|
|
2216
2515
|
let resumeDate
|
|
2217
2516
|
if ( cache_data.dates[0].games[j].resumeGameDate ) {
|
|
2218
2517
|
resumeDate = new Date(cache_data.dates[0].games[j].resumeDate)
|
|
2219
|
-
|
|
2518
|
+
resumeText += 'Resuming on'
|
|
2220
2519
|
} else {
|
|
2221
2520
|
resumeDate = new Date(cache_data.dates[0].games[j].resumedFrom)
|
|
2222
|
-
|
|
2521
|
+
resumeText += 'Resumed from'
|
|
2223
2522
|
}
|
|
2224
|
-
|
|
2523
|
+
resumeText += '' + resumeDate.toLocaleString('default', { month: 'long', day: 'numeric' })
|
|
2225
2524
|
// Also show the status by the media links, if one of them is live
|
|
2226
2525
|
resumeStatus = 'archived'
|
|
2227
2526
|
if ( (typeof cache_data.dates[0].games[j].broadcasts) != 'undefined' ) {
|
|
@@ -2235,61 +2534,82 @@ app.get('/', async function(req, res) {
|
|
|
2235
2534
|
}
|
|
2236
2535
|
|
|
2237
2536
|
if ( (cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName) || (cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName) ) {
|
|
2238
|
-
pitchers = "<br/>"
|
|
2239
2537
|
if ( cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName ) {
|
|
2240
2538
|
if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
|
|
2241
|
-
|
|
2539
|
+
awayPitcher = getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName)
|
|
2242
2540
|
} else {
|
|
2243
|
-
|
|
2541
|
+
awayPitcher = getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName)
|
|
2244
2542
|
}
|
|
2245
|
-
pitchers += '</a>'
|
|
2246
2543
|
} else {
|
|
2247
|
-
|
|
2544
|
+
awayPitcher = 'TBD'
|
|
2248
2545
|
}
|
|
2249
|
-
pitchers += ' vs '
|
|
2250
2546
|
if ( cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName ) {
|
|
2251
2547
|
if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
|
|
2252
|
-
|
|
2548
|
+
homePitcher = getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName)
|
|
2253
2549
|
} else {
|
|
2254
|
-
|
|
2550
|
+
homePitcher = getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName)
|
|
2255
2551
|
}
|
|
2256
|
-
pitchers += '</a>'
|
|
2257
2552
|
} else {
|
|
2258
|
-
|
|
2553
|
+
homePitcher = 'TBD'
|
|
2259
2554
|
}
|
|
2260
2555
|
}
|
|
2261
2556
|
|
|
2262
|
-
|
|
2557
|
+
function hexToRgba(hex, alpha) {
|
|
2558
|
+
const r = parseInt(hex.substring(0, 2), 16)
|
|
2559
|
+
const g = parseInt(hex.substring(2, 4), 16)
|
|
2560
|
+
const b = parseInt(hex.substring(4, 6), 16)
|
|
2561
|
+
return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2263
2564
|
let fav_style = ''
|
|
2264
|
-
|
|
2265
|
-
|
|
2565
|
+
let freeGameVis = 'is-invisible'
|
|
2566
|
+
let favoritVis = 'is-invisible'
|
|
2567
|
+
let condensedCSS = 'videos shortButton'
|
|
2568
|
+
let recapCSS = 'videos shortButton'
|
|
2569
|
+
let highlightCSS = 'videos regularButton'
|
|
2570
|
+
let backgroundCSS = 'backgroundRegular'
|
|
2571
|
+
|
|
2572
|
+
if ( argv.free && cache_data.dates[0].games[j].broadcasts && cache_data.dates[0].games[j].broadcasts[0] && cache_data.dates[0].games[j].broadcasts[0].freeGame ) {
|
|
2573
|
+
freeGameVis = ''
|
|
2574
|
+
|
|
2266
2575
|
} else if ( session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['away'].team.abbreviation) || session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['home'].team.abbreviation) ) {
|
|
2267
2576
|
let fav_team = cache_data.dates[0].games[j].teams['away'].team.abbreviation
|
|
2268
2577
|
if ( session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['home'].team.abbreviation) ) {
|
|
2269
2578
|
fav_team = cache_data.dates[0].games[j].teams['home'].team.abbreviation
|
|
2270
2579
|
}
|
|
2271
|
-
|
|
2272
|
-
|
|
2580
|
+
|
|
2581
|
+
favoritVis = ''
|
|
2582
|
+
let color1 = hexToRgba(TEAM_COLORS[fav_team][1], 0.2)
|
|
2583
|
+
let color2 = hexToRgba(TEAM_COLORS[fav_team][0], 0)
|
|
2584
|
+
|
|
2585
|
+
fav_style = ' style="background-image: linear-gradient(' + '0deg, ' + color1 + ', ' + color2 + '); border: 2px solid #' + TEAM_COLORS[fav_team][1] + '!important;; border-style: outset; border-radius: 3px;"'
|
|
2273
2586
|
}
|
|
2274
|
-
|
|
2587
|
+
|
|
2588
|
+
let gameLevel = session.getLevelNameFromSportId(cache_data.dates[0].games[j].teams['home'].team.sport.id);
|
|
2589
|
+
let gameLevelClass = 'level-' + gameLevel.toLowerCase().replace('+', '-plus')
|
|
2590
|
+
|
|
2275
2591
|
if ( cache_data.dates[0].games[j].seriesDescription != 'Regular Season' ) {
|
|
2276
|
-
|
|
2592
|
+
gameLevel = cache_data.dates[0].games[j].seriesDescription
|
|
2277
2593
|
}
|
|
2278
|
-
body += '><td>' + description + teams + pitchers + state + '</td>'
|
|
2279
2594
|
|
|
2280
2595
|
// Check if Winter League / MiLB game first
|
|
2281
|
-
if ( (cache_data.dates[0].games[j].teams['away'].team.sport.id != levels['MLB']) && (cache_data.dates[0].games[j].teams['home'].team.sport.id != levels['MLB']) && (
|
|
2282
|
-
|
|
2596
|
+
if ( (cache_data.dates[0].games[j].teams['away'].team.sport.id != levels['MLB']) && (cache_data.dates[0].games[j].teams['home'].team.sport.id != levels['MLB']) && (mediaTypeTV == 'MLBTV') ) {
|
|
2597
|
+
recapCSS = 'is-invisible'
|
|
2598
|
+
condensedCSS = 'is-invisible'
|
|
2599
|
+
highlightCSS = 'is-invisible'
|
|
2283
2600
|
if ( cache_data.dates[0].games[j].broadcasts ) {
|
|
2284
2601
|
let broadcastName = 'N/A'
|
|
2285
2602
|
for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
|
|
2286
2603
|
if ( cache_data.dates[0].games[j].broadcasts[k].name != 'Audio' ) {
|
|
2287
|
-
broadcastName =
|
|
2604
|
+
broadcastName = mediaTypeTV
|
|
2288
2605
|
break
|
|
2289
2606
|
}
|
|
2290
2607
|
}
|
|
2291
2608
|
if ( broadcastName == 'N/A' ) {
|
|
2292
|
-
|
|
2609
|
+
streamSource.homeTV +=
|
|
2610
|
+
'<span>' +
|
|
2611
|
+
'<span class="streamAction streamActionInactive">' + broadcastName + '</span>' +
|
|
2612
|
+
'</span>'
|
|
2293
2613
|
} else {
|
|
2294
2614
|
// Check if game should be live
|
|
2295
2615
|
if ( (cache_data.dates[0].games[j].status.detailedState != 'Postponed') && (cache_data.dates[0].games[j].status.detailedState != 'Cancelled') ) {
|
|
@@ -2335,21 +2655,30 @@ app.get('/', async function(req, res) {
|
|
|
2335
2655
|
}
|
|
2336
2656
|
querystring += content_protect_b
|
|
2337
2657
|
multiviewquerystring += content_protect_b
|
|
2338
|
-
|
|
2339
|
-
|
|
2658
|
+
|
|
2659
|
+
streamSource.homeTV +=
|
|
2660
|
+
'<span>' +
|
|
2661
|
+
'<input type="checkbox" value="http://127.0.0.1:' +
|
|
2662
|
+
session.data.port +
|
|
2663
|
+
'/stream.m3u8' +
|
|
2664
|
+
multiviewquerystring +
|
|
2665
|
+
'" onclick="addmultiview(this)">' +
|
|
2666
|
+
'<span>' +
|
|
2667
|
+
'<a class="streamAction" href="' + thislink + querystring + '">' + broadcastName + '</a>' +
|
|
2668
|
+
'</span>' +
|
|
2669
|
+
'</span>'
|
|
2340
2670
|
} else {
|
|
2341
|
-
|
|
2671
|
+
streamSource.homeTV +=
|
|
2672
|
+
'<span>' +
|
|
2673
|
+
'<span class="streamAction streamActionInactive">' + broadcastName + '</span>' +
|
|
2674
|
+
'</span>'
|
|
2342
2675
|
}
|
|
2343
2676
|
}
|
|
2344
2677
|
}
|
|
2345
2678
|
}
|
|
2346
|
-
body += "</td>"
|
|
2347
2679
|
} else {
|
|
2348
2680
|
// Begin MLB games
|
|
2349
|
-
if ( (typeof cache_data.dates[0].games[j].broadcasts)
|
|
2350
|
-
body += "<td></td>"
|
|
2351
|
-
} else {
|
|
2352
|
-
body += "<td>"
|
|
2681
|
+
if ( (typeof cache_data.dates[0].games[j].broadcasts) != 'undefined' ) {
|
|
2353
2682
|
for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
|
|
2354
2683
|
let broadcast = cache_data.dates[0].games[j].broadcasts[k]
|
|
2355
2684
|
if ( broadcast.availableForStreaming ) {
|
|
@@ -2359,47 +2688,43 @@ app.get('/', async function(req, res) {
|
|
|
2359
2688
|
} else if ( broadcast.language == 'es' ) {
|
|
2360
2689
|
mediaTitle = 'Spanish'
|
|
2361
2690
|
}
|
|
2362
|
-
if ( mediaTitle ==
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2691
|
+
if ( mediaTitle == mediaTypeTV ) {
|
|
2692
|
+
// for video, check that it's not in-market
|
|
2693
|
+
/*if ( (mediaTypeTV == 'MLBTV') && await session.check_in_market(cache_data.dates[0].games[j].content.media.epg[k].items[x]) ) {
|
|
2694
|
+
continue
|
|
2695
|
+
}*/
|
|
2367
2696
|
|
|
2368
2697
|
// check if language is not set (video) or it matches requested language
|
|
2369
2698
|
if ( broadcast.language == language ) {
|
|
2370
|
-
let teamabbr
|
|
2371
|
-
|
|
2372
|
-
if ( broadcast.isNational ) {
|
|
2373
|
-
teamabbr = 'NATIONAL'
|
|
2374
|
-
} else {
|
|
2375
|
-
teamabbr = hometeam
|
|
2376
|
-
if ( broadcast.homeAway == 'away' ) {
|
|
2377
|
-
teamabbr = awayteam
|
|
2378
|
-
}
|
|
2379
|
-
}
|
|
2380
2699
|
let station = broadcast.callSign
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
if (
|
|
2386
|
-
|
|
2700
|
+
let hasPregameShow = pre_post_shows.pregame_shows && pre_post_shows.pregame_shows[broadcast.mediaId]
|
|
2701
|
+
let hasPostgameShow = pre_post_shows.postgame_shows && pre_post_shows.postgame_shows[broadcast.mediaId]
|
|
2702
|
+
let prePostIndicator = ''
|
|
2703
|
+
|
|
2704
|
+
if ( hasPregameShow || hasPostgameShow ) {
|
|
2705
|
+
let prePostTitle = 'Pregame show'
|
|
2706
|
+
if ( hasPregameShow && hasPostgameShow ) {
|
|
2707
|
+
prePostTitle = 'Pre- & postgame shows'
|
|
2708
|
+
} else if ( hasPostgameShow ) {
|
|
2709
|
+
prePostTitle = 'Postgame show'
|
|
2710
|
+
}
|
|
2711
|
+
prePostIndicator =
|
|
2712
|
+
'<span class="prePostIndicator" title="' + prePostTitle + '">' +
|
|
2713
|
+
'<span class="prePostCircle ' + (hasPregameShow ? 'prePostCircleActive' : 'prePostCircleInactive') + '"></span>' +
|
|
2714
|
+
'<span class="prePostCircle ' + (hasPostgameShow ? 'prePostCircleActive' : 'prePostCircleInactive') + '"></span>' +
|
|
2715
|
+
'</span>'
|
|
2387
2716
|
}
|
|
2388
2717
|
|
|
2389
|
-
// display blackout tooltip, if necessary
|
|
2390
2718
|
if ( blackouts[gamePk] && blackouts[gamePk].blackout_feeds && blackouts[gamePk].blackout_feeds.includes(broadcast.mediaId) ) {
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2719
|
+
blackoutVis = ''
|
|
2720
|
+
blackoutType = blackouts[gamePk].blackout_type
|
|
2721
|
+
|
|
2722
|
+
if ( blackoutType != 'Not entitled' ) {
|
|
2723
|
+
blackoutToolTipVis = ''
|
|
2724
|
+
blackoutExpiry = blackouts[gamePk].blackoutExpiry.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true })
|
|
2725
|
+
// officially ~90 minutes, but more likely ~150 minutes or ~2.5 hours after the game ends
|
|
2397
2726
|
}
|
|
2398
|
-
body += '</span></span>'
|
|
2399
|
-
} else {
|
|
2400
|
-
body += teamabbr
|
|
2401
2727
|
}
|
|
2402
|
-
body += ': '
|
|
2403
2728
|
|
|
2404
2729
|
if ( broadcast.mediaState && broadcast.mediaState.mediaStateCode && ((broadcast.mediaState.mediaStateCode == 'MEDIA_ON') || (broadcast.mediaState.mediaStateCode == 'MEDIA_ARCHIVE') || (abstractGameState == 'Final')) ) {
|
|
2405
2730
|
let gameTime = new Date(cache_data.dates[0].games[j].gameDate)
|
|
@@ -2408,10 +2733,12 @@ app.get('/', async function(req, res) {
|
|
|
2408
2733
|
game_started = true
|
|
2409
2734
|
}
|
|
2410
2735
|
let mediaId = broadcast.mediaId
|
|
2411
|
-
|
|
2412
|
-
|
|
2736
|
+
if ( (mediaTypeTV == 'MLBTV') && (gameDate == today) && 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()) ) {
|
|
2737
|
+
streamSource.awayTV +=
|
|
2738
|
+
'<span class="streamAction streamActionInactive blackoutstation">' + station + '</span>' +
|
|
2739
|
+
prePostIndicator
|
|
2413
2740
|
} else {
|
|
2414
|
-
let querystring
|
|
2741
|
+
let querystring
|
|
2415
2742
|
querystring = '?mediaId=' + mediaId
|
|
2416
2743
|
|
|
2417
2744
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
@@ -2427,7 +2754,7 @@ app.get('/', async function(req, res) {
|
|
|
2427
2754
|
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
2428
2755
|
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
2429
2756
|
}
|
|
2430
|
-
if (
|
|
2757
|
+
if ( mediaTypeTV == 'MLBTV' ) {
|
|
2431
2758
|
if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
|
|
2432
2759
|
if ( inning_number != VALID_INNING_NUMBER[0] ) querystring += '&inning_number=' + relative_inning
|
|
2433
2760
|
if ( skip != VALID_SKIP[0] ) querystring += '&skip=' + skip
|
|
@@ -2448,263 +2775,481 @@ app.get('/', async function(req, res) {
|
|
|
2448
2775
|
}
|
|
2449
2776
|
querystring += content_protect_b
|
|
2450
2777
|
multiviewquerystring += content_protect_b
|
|
2451
|
-
stationlink = '<a
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this, [\'' + awayteam + '\', \'' + hometeam + '\'])">'
|
|
2460
|
-
}
|
|
2461
|
-
if ( resumeStatus ) {
|
|
2462
|
-
body += '('
|
|
2463
|
-
// for suspended games that haven't finished yet, we can simply use the mediaState to determine the status
|
|
2464
|
-
if ( resumeStatus == 'live' ) {
|
|
2465
|
-
if ( broadcast.mediaState.mediaStateCode == 'MEDIA_ARCHIVE' ) {
|
|
2466
|
-
body += '1'
|
|
2467
|
-
} else {
|
|
2468
|
-
body += '2'
|
|
2469
|
-
}
|
|
2470
|
-
// otherwise, for completed games, we need to check the airings data
|
|
2778
|
+
stationlink = '<a class="streamAction" href="' + thislink + querystring + '">' + station + '</a>'
|
|
2779
|
+
|
|
2780
|
+
let streamStationHtml = stationlink + prePostIndicator
|
|
2781
|
+
let resumeStreamSuffix = ''
|
|
2782
|
+
// For suspended games that are currently live, mark whether this feed is part 1 or part 2.
|
|
2783
|
+
if ( resumeStatus == 'live' ) {
|
|
2784
|
+
if ( broadcast.mediaState.mediaStateCode == 'MEDIA_ARCHIVE' ) {
|
|
2785
|
+
resumeStreamSuffix = '(1)'
|
|
2471
2786
|
} else {
|
|
2472
|
-
|
|
2473
|
-
if ( airings_data.data && airings_data.data.Airings && (airings_data.data.Airings.length > 0) ) {
|
|
2474
|
-
for (var y = 0; y < airings_data.data.Airings.length; y++) {
|
|
2475
|
-
if ( airings_data.data.Airings[y].contentId == cache_data.dates[0].games[j].content.media.epg[k].items[x].contentId ) {
|
|
2476
|
-
if ( (cache_data.dates[0].games[j].resumeDate && (cache_data.dates[0].games[j].resumeDate == airings_data.data.Airings[y].startDate)) || (cache_data.dates[0].games[j].resumedFrom && (cache_data.dates[0].games[j].gameDate == airings_data.data.Airings[y].startDate)) ) {
|
|
2477
|
-
body += '2'
|
|
2478
|
-
} else {
|
|
2479
|
-
body += '1'
|
|
2480
|
-
}
|
|
2481
|
-
break
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
}*/
|
|
2787
|
+
resumeStreamSuffix = '(2)'
|
|
2485
2788
|
}
|
|
2486
|
-
body += ')'
|
|
2487
2789
|
}
|
|
2790
|
+
|
|
2791
|
+
if (broadcast.homeAway == 'home') {
|
|
2792
|
+
streamSource.homeTV += '<span>'
|
|
2793
|
+
|
|
2794
|
+
if ( mediaTypeTV == 'MLBTV' ) {
|
|
2795
|
+
streamSource.homeTV +=
|
|
2796
|
+
'<input type="checkbox" value="http://127.0.0.1:' +
|
|
2797
|
+
session.data.port +
|
|
2798
|
+
'/stream.m3u8' +
|
|
2799
|
+
multiviewquerystring +
|
|
2800
|
+
'" onclick="addmultiview(this, [\'' +
|
|
2801
|
+
awayteam +
|
|
2802
|
+
'\', \'' +
|
|
2803
|
+
hometeam +
|
|
2804
|
+
'\'])">'
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
streamSource.homeTV +=
|
|
2808
|
+
streamStationHtml +
|
|
2809
|
+
resumeStreamSuffix
|
|
2810
|
+
|
|
2811
|
+
streamSource.homeTV +=
|
|
2812
|
+
'</span>'
|
|
2813
|
+
} else if (broadcast.homeAway == 'away') {
|
|
2814
|
+
streamSource.awayTV += '<span>'
|
|
2815
|
+
|
|
2816
|
+
if ( mediaTypeTV == 'MLBTV' ) {
|
|
2817
|
+
streamSource.awayTV +=
|
|
2818
|
+
'<input type="checkbox" value="http://127.0.0.1:' +
|
|
2819
|
+
session.data.port +
|
|
2820
|
+
'/stream.m3u8' +
|
|
2821
|
+
multiviewquerystring +
|
|
2822
|
+
'" onclick="addmultiview(this, [\'' +
|
|
2823
|
+
awayteam +
|
|
2824
|
+
'\', \'' +
|
|
2825
|
+
hometeam +
|
|
2826
|
+
'\'])">'
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
streamSource.awayTV +=
|
|
2830
|
+
streamStationHtml +
|
|
2831
|
+
resumeStreamSuffix
|
|
2832
|
+
|
|
2833
|
+
streamSource.awayTV +=
|
|
2834
|
+
|
|
2835
|
+
'</span>'
|
|
2836
|
+
}
|
|
2488
2837
|
}
|
|
2489
2838
|
// add YouTube link where available
|
|
2490
|
-
/*if ( (
|
|
2839
|
+
/*if ( (mediaTypeTV == 'MLBTV') && cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube && cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube.videoId ) {
|
|
2491
2840
|
body += '<a' + fav_style + ' href="https://www.youtube.com/watch?v=' + cache_data.dates[0].games[j].content.media.epg[k].items[x].youtube.videoId + '" target="_blank">' + station + '↗</a>'
|
|
2492
2841
|
}*/
|
|
2493
2842
|
} else {
|
|
2843
|
+
let inactiveStation = station
|
|
2844
|
+
|
|
2494
2845
|
if ( blackouts[gamePk] && blackouts[gamePk].blackout_feeds && blackouts[gamePk].blackout_feeds.includes(broadcast.mediaId) ) {
|
|
2495
|
-
|
|
2846
|
+
inactiveStation = '<span class="streamAction streamActionInactive blackoutstation">' + station + '</span>' + prePostIndicator
|
|
2847
|
+
backgroundCSS = 'backgroundBlackout'
|
|
2496
2848
|
} else {
|
|
2497
|
-
|
|
2849
|
+
inactiveStation = '<span class="streamAction streamActionInactive">' + station + '</span>' + prePostIndicator
|
|
2498
2850
|
}
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2851
|
+
|
|
2852
|
+
if (broadcast.homeAway == 'home') {streamSource.homeTV +=
|
|
2853
|
+
'<div>' +
|
|
2854
|
+
'<div>' +
|
|
2855
|
+
inactiveStation +
|
|
2856
|
+
'</div>' +
|
|
2857
|
+
'</div>' } else if (broadcast.homeAway == 'away') {streamSource.awayTV +=
|
|
2858
|
+
'<div>' +
|
|
2859
|
+
'<div>' +
|
|
2860
|
+
inactiveStation +
|
|
2861
|
+
'</div>' +
|
|
2862
|
+
'</div>' }
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2502
2865
|
}
|
|
2503
2866
|
}
|
|
2504
2867
|
}
|
|
2505
|
-
if ( body.substr(-2) == ', ' ) {
|
|
2506
|
-
body = body.slice(0, -2)
|
|
2507
|
-
}
|
|
2508
|
-
//if ( (mediaType == 'MLBTV') && (game_started) && cache_data.dates[0].games[j].content && cache_data.dates[0].games[j].content.summary && cache_data.dates[0].games[j].content.summary.hasHighlightsVideo ) {
|
|
2509
|
-
if ( (mediaType == 'MLBTV') && (game_started) ) {
|
|
2510
|
-
body += '<br/><a' + fav_style + ' href="#" onclick="showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\'); return false;">Highlights</a>'
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
body += "</td>"
|
|
2514
|
-
body += "</tr>" + "\n"
|
|
2515
|
-
}
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
body += "</table>" + "\n"
|
|
2519
|
-
|
|
2520
|
-
if ( (Object.keys(blackouts).length > 0) ) {
|
|
2521
|
-
body += '<span class="tooltip tinytext"><span class="blackout">strikethrough</span> indicates a live blackout or non-entitled content<span class="tooltiptext">Tap or hover over the team abbreviation to see an estimate of when the blackout will be lifted (officially ~90 minutes, but more likely ~150 minutes or ~2.5 hours after the game ends).</span></span>' + "\n"
|
|
2522
|
-
if ( (Object.keys(pre_post_shows).length > 0) ) {
|
|
2523
|
-
body += '<br/>'
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
if ( (pre_post_shows.pregame_shows && (Object.keys(pre_post_shows.pregame_shows).length > 0)) || (pre_post_shows.postgame_shows && (Object.keys(pre_post_shows.postgame_shows).length > 0)) ) {
|
|
2528
|
-
body += '<span class="tooltip tinytext">/slashes/ indicates a live pre- and/or post-game show<span class="tooltiptext">A /slash before the station indicates a pre-game show; a slash/ after the station indicates a post-game show. Pre- and post-game shows are only available live.</span></span>' + "\n"
|
|
2529
|
-
if ( argv.free ) {
|
|
2530
|
-
body += '<br/>'
|
|
2531
|
-
}
|
|
2532
|
-
}
|
|
2533
2868
|
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2869
|
+
if (mediaTypeTV == 'MLBTV' && game_started) {
|
|
2870
|
+
highlightLink +=
|
|
2871
|
+
'<a' +
|
|
2872
|
+
' href="#" onclick="showhighlights(\'' +
|
|
2873
|
+
cache_data.dates[0].games[j].gamePk +
|
|
2874
|
+
'\',\'' +
|
|
2875
|
+
gameDate +
|
|
2876
|
+
'\'); return false;">Highlights</a>'
|
|
2877
|
+
} else { highlightLink += `Highlights`
|
|
2878
|
+
highlightCSS = 'videosInactive regularButton'
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
if (mediaTypeTV == 'MLBTV' && abstractGameState == 'Final' && (detailedState != 'Postponed') ) {
|
|
2882
|
+
condensedLink +=
|
|
2883
|
+
'<a' +
|
|
2884
|
+
' href="#" onclick="showcondensed(\'' +
|
|
2885
|
+
cache_data.dates[0].games[j].gamePk +
|
|
2886
|
+
'\',\'' +
|
|
2887
|
+
gameDate +
|
|
2888
|
+
'\'); return false;">Cond.</a>'
|
|
2889
|
+
} else { condensedLink += `Cond.`
|
|
2890
|
+
condensedCSS = 'videosInactive shortButton'}
|
|
2891
|
+
|
|
2892
|
+
if (mediaTypeTV == 'MLBTV' && abstractGameState == 'Final' && (detailedState != 'Postponed')) {
|
|
2893
|
+
recapLink +=
|
|
2894
|
+
'<a' +
|
|
2895
|
+
' href="#" onclick="showrecap(\'' +
|
|
2896
|
+
cache_data.dates[0].games[j].gamePk +
|
|
2897
|
+
'\',\'' +
|
|
2898
|
+
gameDate +
|
|
2899
|
+
'\'); return false;">Recap</a>'
|
|
2900
|
+
} else {
|
|
2901
|
+
recapLink += `Recap`
|
|
2902
|
+
recapCSS = 'videosInactive shortButton'
|
|
2903
|
+
}
|
|
2542
2904
|
|
|
2543
|
-
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
2544
|
-
body += '<p><span class="tooltip">Video<span class="tooltiptext">For video streams only: you can manually specifiy a video track (resolution) to use. Adaptive will let your client choose. Best will select either 1080p60 (MLB) or 720p60 (MiLB). 504p is default for multiview (see below).<br/><br/>None will allow to remove the video tracks, if you just want to listen to the audio while using the "start at inning" or "skip breaks" options enabled.</span></span>: '
|
|
2545
|
-
body += '<button '
|
|
2546
|
-
if ( resolution == 'best' ) body += 'class="default" '
|
|
2547
|
-
body += 'onclick="resolution=\'best\';reload()">best</button> '
|
|
2548
|
-
for (var i = 0; i < VALID_RESOLUTIONS.length; i++) {
|
|
2549
|
-
body += '<button '
|
|
2550
|
-
if ( resolution == VALID_RESOLUTIONS[i] ) body += 'class="default" '
|
|
2551
|
-
body += 'onclick="resolution=\'' + VALID_RESOLUTIONS[i] + '\';reload()">' + VALID_RESOLUTIONS[i]
|
|
2552
|
-
if ( DISPLAY_BANDWIDTHS[i] != '' ) {
|
|
2553
|
-
body += '<br/><span class="tinytext">' + DISPLAY_BANDWIDTHS[i] + '</span>'
|
|
2554
2905
|
}
|
|
2555
|
-
body += '</button> '
|
|
2556
|
-
}
|
|
2557
|
-
body += '</p>' + "\n"
|
|
2558
|
-
|
|
2559
|
-
body += '<p><span class="tooltip">Audio<span class="tooltiptext">For video streams only: you can manually specifiy which audio track to include. Some media players can accept them all and let you choose. Not all tracks are available for all games, and injected tracks may not work with skip options below.<br/><br/>If you select "none" for video above, picking an audio track here will make it an audio-only feed that supports the inning start and skip breaks options.</span></span>: '
|
|
2560
|
-
for (var i = 0; i < VALID_AUDIO_TRACKS.length; i++) {
|
|
2561
|
-
body += '<button '
|
|
2562
|
-
if ( audio_track == VALID_AUDIO_TRACKS[i] ) body += 'class="default" '
|
|
2563
|
-
body += 'onclick="audio_track=\'' + VALID_AUDIO_TRACKS[i] + '\';reload()">' + DISPLAY_AUDIO_TRACKS[i] + '</button> '
|
|
2564
|
-
}
|
|
2565
|
-
body += '</p>' + "\n"
|
|
2566
|
-
|
|
2567
|
-
body += '<p><span class="tooltip">Captions<span class="tooltiptext">For video streams only: you can disable the caption track, if one is present. This is handy if you do not want to disable it in your player each time.</span></span>: '
|
|
2568
|
-
for (var i = 0; i < VALID_CAPTIONS.length; i++) {
|
|
2569
|
-
body += '<button '
|
|
2570
|
-
if ( captions == VALID_CAPTIONS[i] ) body += 'class="default" '
|
|
2571
|
-
body += 'onclick="captions=\'' + VALID_CAPTIONS[i] + '\';reload()">' + VALID_CAPTIONS[i] + '</button> '
|
|
2572
2906
|
}
|
|
2573
|
-
|
|
2907
|
+
let pitcherVis = 'is-invisible'
|
|
2908
|
+
if (awayPitcher || homePitcher) { pitcherVis ='' }
|
|
2909
|
+
body +=`
|
|
2910
|
+
<div class="cardContainer ${backgroundCSS} ${gameLevelClass}" ${fav_style}>
|
|
2911
|
+
|
|
2912
|
+
<div class="cardHeader flex-between">
|
|
2913
|
+
<div class="flex-between">
|
|
2914
|
+
<div class="tag level ${gameLevelClass}">${gameLevel}</div>
|
|
2915
|
+
|
|
2916
|
+
</div>
|
|
2917
|
+
<div class="cardTags">
|
|
2918
|
+
<span class="tooltip ${scheduleVis}">
|
|
2919
|
+
<span class="tag schedule">SCD
|
|
2920
|
+
<span class="tooltiptext"> ${scheduleDesc}<br>${resumeText}</span>
|
|
2921
|
+
</span>
|
|
2922
|
+
</span>
|
|
2923
|
+
<span class="tooltip ${doubleHeaderVis}">
|
|
2924
|
+
<span class="tag gameno">G${gameNo}
|
|
2925
|
+
<span class="tooltiptext"> Game ${gameNo}</span>
|
|
2926
|
+
</span>
|
|
2927
|
+
</span>
|
|
2928
|
+
<span class="tooltip ${inningsVis}">
|
|
2929
|
+
<span class="tag innings">${scheduledInnings}IN
|
|
2930
|
+
<span class="tooltiptext">${scheduledInnings}-inning game </span>
|
|
2931
|
+
</span>
|
|
2932
|
+
</span>
|
|
2933
|
+
<span class="tooltip ${blackoutVis}">
|
|
2934
|
+
<span class="tag blackout">BLACKOUT
|
|
2935
|
+
<span class="tooltiptext">${blackoutType}
|
|
2936
|
+
<span class="${blackoutToolTipVis}"> video blackout until approx. ${blackoutExpiry}</span>
|
|
2937
|
+
</span>
|
|
2938
|
+
</span>
|
|
2939
|
+
</span>
|
|
2940
|
+
<span class="tooltip ${freeGameVis}">
|
|
2941
|
+
<span class="tag freegame">Free
|
|
2942
|
+
<span class="tooltiptext">Free Game</span>
|
|
2943
|
+
</span>
|
|
2944
|
+
</span>
|
|
2945
|
+
<span class="tooltip ${favoritVis}">
|
|
2946
|
+
<span class="tag favorite">FAV
|
|
2947
|
+
<span class="tooltiptext">Favorite team</span>
|
|
2948
|
+
</span>
|
|
2949
|
+
</span>
|
|
2950
|
+
<span class="${tooltipCSS}">
|
|
2951
|
+
<span class="${noteworthyCSS}">${noteworthyTag}
|
|
2952
|
+
<span class="${tooltipTextCSS}">${noteworthyTooltip}</span>
|
|
2953
|
+
</span>
|
|
2954
|
+
</span>
|
|
2955
|
+
<span class="time">${state}</span>
|
|
2956
|
+
</div>
|
|
2957
|
+
</div>
|
|
2958
|
+
|
|
2959
|
+
<div class="gameContent">
|
|
2960
|
+
|
|
2961
|
+
<div class="row ${gameLevel}">
|
|
2962
|
+
<div class="team">
|
|
2963
|
+
<span class="pitcher">${awayPitcher}</span>
|
|
2964
|
+
<span class="teamName">${awayteam}</span>
|
|
2965
|
+
|
|
2966
|
+
|
|
2967
|
+
</div>
|
|
2968
|
+
<div class="score">
|
|
2969
|
+
<span class="">${awayscore}</span>
|
|
2970
|
+
</div>
|
|
2971
|
+
|
|
2972
|
+
</div>
|
|
2973
|
+
<div class="parentOrg ${parentAwayVis}">` + cache_data.dates[0].games[j].teams['away'].team.parentOrgName + `</div>
|
|
2974
|
+
|
|
2975
|
+
<div class="row ${gameLevel}">
|
|
2976
|
+
<div class="team">
|
|
2977
|
+
<span class="pitcher">${homePitcher}</span>
|
|
2978
|
+
<span class="teamName">${hometeam}</span>
|
|
2979
|
+
|
|
2980
|
+
|
|
2981
|
+
</div>
|
|
2982
|
+
<div class="score">
|
|
2983
|
+
<span class="">${homescore}</span>
|
|
2984
|
+
</div>
|
|
2985
|
+
</div>
|
|
2986
|
+
<div class="parentOrg ${parentHomeVis}">` + cache_data.dates[0].games[j].teams['home'].team.parentOrgName + `</div>
|
|
2987
|
+
|
|
2988
|
+
|
|
2989
|
+
|
|
2990
|
+
|
|
2991
|
+
</div>
|
|
2992
|
+
<div class="streamContent">
|
|
2993
|
+
<span class="">${streamSource.awayTV}</span>
|
|
2994
|
+
<span class="flex-center ${highlightCSS}">★ ${highlightLink}</span>
|
|
2995
|
+
</div>
|
|
2996
|
+
|
|
2997
|
+
<div class="streamContent">
|
|
2998
|
+
<span class="">${streamSource.homeTV}</span>
|
|
2999
|
+
<div class="flex-center">
|
|
3000
|
+
<span class="flex-center ${recapCSS}">${recapLink}</span>
|
|
3001
|
+
<span class="flex-center ${condensedCSS}">${condensedLink}</span>
|
|
3002
|
+
</div>
|
|
3003
|
+
</div>
|
|
3004
|
+
|
|
2574
3005
|
|
|
2575
|
-
|
|
2576
|
-
for (var i = 0; i < VALID_SKIP.length; i++) {
|
|
2577
|
-
body += '<button '
|
|
2578
|
-
if ( skip == VALID_SKIP[i] ) body += 'class="default" '
|
|
2579
|
-
body += 'onclick="skip=\'' + VALID_SKIP[i] + '\';reload()">' + VALID_SKIP[i] + '</button> '
|
|
2580
|
-
}
|
|
2581
|
-
if ( skip != VALID_SKIP[0] ) {
|
|
2582
|
-
body += '<br><span class="tooltip">Skip Adjust<span class="tooltiptext">Seconds to adjust the skip time video segments, if necessary. Try a negative number if the plays are ending before the video segments begin; use a positive number if the video segments are ending before the play happens.</span></span>: <input type="number" id="skip_adjust" value="' + skip_adjust + '" step="1" onchange="setTimeout(function(){skip_adjust=document.getElementById(\'skip_adjust\').value;reload()},750)" onblur="skip_adjust=this.value;reload()" style="vertical-align:top;font-size:.8em;width:3em"/>'
|
|
2583
|
-
}
|
|
2584
|
-
body += '</p>' + "\n"
|
|
3006
|
+
</div>`
|
|
2585
3007
|
}
|
|
3008
|
+
}
|
|
2586
3009
|
|
|
2587
|
-
|
|
2588
|
-
for (var i = 0; i < VALID_PAD.length; i++) {
|
|
2589
|
-
body += '<button '
|
|
2590
|
-
if ( pad == VALID_PAD[i] ) body += 'class="default" '
|
|
2591
|
-
body += 'onclick="pad=\'' + VALID_PAD[i] + '\';reload()">' + VALID_PAD[i] + '</button> '
|
|
2592
|
-
}
|
|
2593
|
-
body += '</p>' + "\n"
|
|
3010
|
+
body +=`</div>`
|
|
2594
3011
|
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
for (var i = 0; i < VALID_FORCE_VOD.length; i++) {
|
|
2598
|
-
body += '<button '
|
|
2599
|
-
if ( force_vod == VALID_FORCE_VOD[i] ) body += 'class="default" '
|
|
2600
|
-
body += 'onclick="force_vod=\'' + VALID_FORCE_VOD[i] + '\';reload()">' + VALID_FORCE_VOD[i] + '</button> '
|
|
2601
|
-
}
|
|
2602
|
-
body += '<span class="tinytext">(if client does not support seeking in live streams)</span></p>' + "\n"
|
|
2603
|
-
}
|
|
3012
|
+
body +=`<div class="section">`
|
|
3013
|
+
|
|
2604
3014
|
|
|
2605
3015
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
2606
|
-
body += '<
|
|
3016
|
+
body += '<div class="menuContainer"><div class="cardMenuHeader">Multiview / Alternate Audio / Sync</div><div class="menuContent multiviewContent">' +
|
|
3017
|
+
'<div class="multiviewHeader"><div class="is-flex"><span>For video streams only: create a new live stream combining 1-4 separate video streams, using the layout shown at left (if more than 1 video stream is selected). Check the boxes next to feeds above to add/remove them, then click "Start" when ready, "Stop" when done watching, or "Restart" to stop and start with the currently selected streams. <span class="info infoPadding multiviewInfoButton" data-target="#multiviewInfo">?</span></span></div><div class="multiviewActions"><a id="startmultiview" class="multiviewAction multiviewActionPrimary" href="" onclick="startmultiview(this);return false">Start'
|
|
2607
3018
|
if ( ffmpeg_status ) body += 'ed'
|
|
2608
|
-
body += '</a
|
|
3019
|
+
body += '</a><a id="stopmultiview" class="multiviewAction" href="" onclick="stopmultiview(this);return false">Stop'
|
|
2609
3020
|
if ( !ffmpeg_status ) body += 'ped'
|
|
2610
|
-
body += '</a
|
|
2611
|
-
|
|
3021
|
+
body += '</a></div></div>' + "\n" +
|
|
3022
|
+
'<div class="multiviewIntro"><div class="square-grid"><div>1</div><div>2</div><div>3</div><div>4</div></div>'
|
|
3023
|
+
|
|
3024
|
+
body += '<span>Check boxes next to games to add, then click Start. Click Stop when done, or manually kill ffmpeg.</span></div>' + ''
|
|
3025
|
+
body += `
|
|
3026
|
+
<div id="multiviewInfo" class="infoContainer">
|
|
3027
|
+
<div class="">
|
|
3028
|
+
<div class="infoContent">
|
|
3029
|
+
May take up to 15 seconds after starting before it is ready to play.<br/><br/>No video scaling is performed: defaults to 540p video for each stream, which can combine to make one 1080p stream. Audio defaults to English (TV) audio. If you specify a different audio track instead, you can use the box after each URL below to adjust the sync in seconds (use positive values if audio is early and the audio stream needs to be padded with silence at the beginning to line up with the video; negative values if audio is late, and audio needs to be trimmed from the beginning.)<br/><br/>TIP #1: You can enter just 1 video stream here, at any resolution, to take advantage of the audio sync or alternate audio features without using multiview -- a single video stream will not be re-encoded and will be presented at its full resolution.<br/><br/>TIP #2: You can also manually enter streams from other sources like <a href="https://www.npmjs.com/package/milbserver" target="_blank">milbserver</a> in the boxes below. Make sure any manually entered streams have the desired resolution.<br/><br/>WARNING #1: if the mlbserver process dies or restarts while multiview is active, the ffmpeg encoding process will be orphaned and must be killed manually.<br/><br/>WARNING #2: If you did not specify a hardware encoder for ffmpeg on the command line, this will use your server CPU for encoding. Either way, your system may not be able to keep up with processing 4 video streams at once. Try fewer streams if you have perisistent trouble.
|
|
3030
|
+
</div>
|
|
3031
|
+
</div>
|
|
3032
|
+
|
|
3033
|
+
</div>
|
|
3034
|
+
`
|
|
3035
|
+
|
|
2612
3036
|
for (var i=1; i<=4; i++) {
|
|
2613
|
-
body += i + '
|
|
2614
|
-
body += '<input type="number" id="sync' + i + '" value="0.0" step=".1"
|
|
2615
|
-
body += '<br/>' + "\n"
|
|
3037
|
+
body += '<div class="multiviewStreamRow"><label for="multiview' + i + '">' + i + ':</label><textarea id="multiview' + i + '" oninput="this.value=stream_substitution(this.value)"></textarea>'
|
|
3038
|
+
body += '<input type="number" id="sync' + i + '" value="0.0" step=".1"/></div>' + "\n"
|
|
2616
3039
|
}
|
|
2617
|
-
body +=
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
3040
|
+
body += `
|
|
3041
|
+
<div class="is-flex">
|
|
3042
|
+
<input type="checkbox" id="dvr"/>
|
|
3043
|
+
<span>DVR: allow pausing/seeking multiview</span>
|
|
3044
|
+
<div class="info infoPadding" data-target="#dvrInfo">?</div>
|
|
3045
|
+
</div>
|
|
3046
|
+
<div id="dvrInfo" class="infoContainer">
|
|
3047
|
+
<div class="infoContent">
|
|
3048
|
+
If this is enabled, it will use more disk space but you will be able to pause and seek in the multiview stream. Not necessary if you are strictly watching live.
|
|
3049
|
+
</div>
|
|
3050
|
+
</div>
|
|
3051
|
+
|
|
3052
|
+
<div class="is-flex">
|
|
3053
|
+
<input type="checkbox" id="faster" onchange="if (this.checked){document.getElementById('dvr').checked=true}"/>
|
|
3054
|
+
<span>Encode faster than real-time</span>
|
|
3055
|
+
<div class="info infoPadding" data-target="#fasterInfo">?</div>
|
|
3056
|
+
</div>
|
|
3057
|
+
<div id="fasterInfo" class="infoContainer">
|
|
3058
|
+
<div class="infoContent">
|
|
3059
|
+
Implies DVR. Not necessary for live streams (which are only delivered in real-time), but if you want to seek ahead in archive streams using multiview, you may want to enable this. WARNING: ffmpeg may approach 100% CPU usage if you use this while combining multiple archive video streams in multiview.
|
|
3060
|
+
</div>
|
|
3061
|
+
</div>
|
|
3062
|
+
|
|
3063
|
+
<div class="is-flex">
|
|
3064
|
+
<input type="checkbox" id="reencode"/>
|
|
3065
|
+
<span>Re-encode all audio</span>
|
|
3066
|
+
<div class="info infoPadding" data-target="#reencodeInfo">?</div>
|
|
3067
|
+
</div>
|
|
3068
|
+
<div id="reencodeInfo" class="infoContainer">
|
|
3069
|
+
<div class="infoContent">
|
|
3070
|
+
Uses more CPU. Generally only necessary if you need the multiview stream to continue after one of the individual streams has ended. Any streams with sync adjustments above will automatically be re-encoded, regardless of this setting.
|
|
3071
|
+
</div>
|
|
3072
|
+
</div>
|
|
3073
|
+
|
|
3074
|
+
<div class="is-flex">
|
|
3075
|
+
<input type="checkbox" id="park_audio"/>
|
|
3076
|
+
<span>Park audio: filter out announcers</span>
|
|
3077
|
+
<div class="info infoPadding" data-target="#parkAudioInfo">?</div>
|
|
3078
|
+
</div>
|
|
3079
|
+
<div id="parkAudioInfo" class="infoContainer">
|
|
3080
|
+
<div class="infoContent">
|
|
3081
|
+
Implies re-encoding all audio. If this is enabled, an extra audio filter is applied to remove the announcer voices.
|
|
3082
|
+
</div>
|
|
3083
|
+
</div>`
|
|
3084
|
+
|
|
3085
|
+
body += `
|
|
3086
|
+
<div class="is-flex">
|
|
3087
|
+
<span>Alternate audio URL and sync</span>
|
|
3088
|
+
<div class="info infoPadding" data-target="#altAudio">?</div>
|
|
3089
|
+
</div>
|
|
3090
|
+
<div id="altAudio" class="infoContainer">
|
|
3091
|
+
<div class="infoContent">
|
|
3092
|
+
Optional: you can also include a separate audio-only URL as an additional alternate audio track. Archive games will likely require a very large negative sync value, as the radio broadcasts may not be trimmed like the video archives.
|
|
3093
|
+
</div>
|
|
3094
|
+
</div>
|
|
3095
|
+
<div class="multiviewAudioRow">
|
|
3096
|
+
<textarea id="audio_url" oninput="this.value=stream_substitution(this.value)"></textarea>
|
|
3097
|
+
<input id="audio_url_seek" type="number" value="0"/>
|
|
3098
|
+
</div>
|
|
3099
|
+
<div class="multiviewWatchLinks">
|
|
3100
|
+
<a class="channelLink" href="${http_root}/embed.html?msrc=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Embed</a>
|
|
3101
|
+
<a class="channelLink" href="${http_root}/stream.m3u8?src=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Stream</a>
|
|
3102
|
+
<a class="channelLink" href="${http_root}/chromecast.html?msrc=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Chromecast</a>
|
|
3103
|
+
<a class="channelLink" href="${http_root}/advanced.html?msrc=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Advanced</a>
|
|
3104
|
+
<a class="channelLink" href="${http_root}/download.ts?src=${encodeURIComponent(multiview_stream_url)}${content_protect_b}&filename=${gameDate} Multiview">Download</a>
|
|
3105
|
+
</div>
|
|
3106
|
+
<div class="multiviewWatchLinks">
|
|
3107
|
+
<span class="channelNote">Kodi STRM files:</span>
|
|
3108
|
+
<a class="channelLink" href="${http_root}/kodi.strm?src=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Matrix/19+</a>
|
|
3109
|
+
<a class="channelLink" href="${http_root}/kodi.strm?version=18&src=${encodeURIComponent(multiview_stream_url)}${content_protect_b}">Leia/18</a>
|
|
3110
|
+
</div>
|
|
3111
|
+
</div></div>
|
|
3112
|
+
`
|
|
2624
3113
|
|
|
2625
|
-
body +=
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
3114
|
+
body += `
|
|
3115
|
+
<div class="menuContainer">
|
|
3116
|
+
<div class="cardMenuHeader">Stream Finder Settings</div>
|
|
3117
|
+
<div class="menuContent streamFinderContent">
|
|
3118
|
+
<div>Automatically switches between games according to your preferences. This program is not affiliated with Baseball Reference, do not contact them for support.</div>
|
|
3119
|
+
<div>
|
|
3120
|
+
<a class="channelLink" download="mlbserverStreamFinder.txt" href="${http_root}/downloadsettings${content_protect_a}">Download current settings</a>
|
|
3121
|
+
</div>
|
|
3122
|
+
<div class="streamFinderSteps">
|
|
3123
|
+
<div class="streamFinderStep">
|
|
3124
|
+
<div class="streamFinderStepNumber">1</div>
|
|
3125
|
+
<div class="streamFinderStepContent">
|
|
3126
|
+
<div class="streamFinderStepTitle">Export settings</div>
|
|
3127
|
+
<div class="streamFinderStepText">Create and export your Stream Finder settings at Baseball Reference.</div>
|
|
3128
|
+
<a class="channelLink" href="https://www.baseball-reference.com/stream-finder.shtml" target="_blank">Open Stream Finder</a>
|
|
3129
|
+
</div>
|
|
3130
|
+
</div>
|
|
3131
|
+
<div class="streamFinderStep">
|
|
3132
|
+
<div class="streamFinderStepNumber">2</div>
|
|
3133
|
+
<div class="streamFinderStepContent">
|
|
3134
|
+
<div class="streamFinderStepTitle">Upload settings</div>
|
|
3135
|
+
<div class="streamFinderStepText">Select the settings file you just downloaded.</div>
|
|
3136
|
+
<form method="POST" enctype="multipart/form-data" action="${http_root}/upload${content_protect_a}">
|
|
3137
|
+
<input name="file" type="file" onchange="form.submit()"/>
|
|
3138
|
+
</form>
|
|
3139
|
+
</div>
|
|
3140
|
+
</div>
|
|
3141
|
+
</div>
|
|
3142
|
+
</div>
|
|
3143
|
+
</div>` + "\n"
|
|
3144
|
+
}
|
|
3145
|
+
body +=`</div>`
|
|
3146
|
+
const channelInfoText = {
|
|
3147
|
+
scanMode: 'During setup, some TV/DVR/PVR software will attempt to load all stream URLs. Turning Scan Mode ON will return a sample stream for all stream requests, thus satisfying that software without overloading mlbserver or excluding streams which are not currently live. Once the channels are set up, turning Scan Mode OFF will restore normal stream behavior. WARNING: Be sure your TV/DVR/PVR software does not periodically scan all channels automatically or you might overload mlbserver.',
|
|
3148
|
+
allChannels: 'Will include all entitled live MLB broadcasts (games plus Big Inning, Game Changer, and Multiview, as well as MASN, 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.',
|
|
3149
|
+
byTeam: '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 broadcast if available. It will also include affiliate games for those organizations. Channels/games subject to blackout will be omitted by default.',
|
|
3150
|
+
byTeamRadio: 'Same as By team, but defaults to that team radio track, if available.',
|
|
3151
|
+
byTeamSpanish: 'Same as By team, but defaults to that team Spanish radio track, if available.',
|
|
3152
|
+
includeBlackouts: 'Adds an optional parameter to include channels/games subject to blackout, although you may not be able to play those games.',
|
|
3153
|
+
excludeTeam: 'Excluding a team (MLB only, by abbreviation, in a comma-separated list if more than 1) will exclude every game involving that team. Blackouts are already excluded without this parameter.',
|
|
3154
|
+
winterLeagues: 'Winter leagues include the Arizona Fall League, Dominican Winter League aka Liga de Beisbol Dominicano, and Mexican Winter League aka Liga Mexicana del Pacifico. Live stream only, does not support starting from the beginning or certain innings, skip options, etc.',
|
|
3155
|
+
masn: 'MASN live stream for entitled subscribers. See https://support.mlb.com/s/article/MASN-In-Market-Offering for more information.',
|
|
3156
|
+
mlbn: '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. See https://support.mlb.com/s/article/MLB-Network-Streaming-FAQ for more information.',
|
|
3157
|
+
snla: 'SNLA live stream for entitled subscribers. See https://support.mlb.com/s/article/SNLA-Plus-Subscription-Packages for more information.',
|
|
3158
|
+
sny: 'SNY live stream for entitled subscribers. See https://support.mlb.com/s/article/SNY-In-Market-Offering for more information.',
|
|
3159
|
+
bigInning: 'Big Inning is the live look-in and highlights show. See https://www.mlb.com/live-stream-games/big-inning for more information.',
|
|
3160
|
+
gameChanger: '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 best resolution if not specified.',
|
|
3161
|
+
streamFinder: 'The stream finder stream will automatically switch between games according to your uploaded preferences. This stream is not affiliated with Baseball Reference, do not contact them for support. Visit http://bit.ly/bbrefsf to create and export your preferences, then upload and save them to mlbserver above. Does not support adaptive bitrate switching, will default to best resolution if not specified.',
|
|
3162
|
+
multiviewChannel: 'Requires starting and stopping the multiview stream from the web interface.',
|
|
3163
|
+
freeGames: 'Only includes games marked as free. Blackouts still apply. Channels/games subject to blackout will be omitted by default.',
|
|
3164
|
+
includeOrgs: 'Including an organization (by MLB team abbreviation, in a comma-separated list if more than 1) will include all of its affiliate broadcasts, or if that affiliate is not broadcasting the game, it will include the opponent broadcast if available. If this option is not specified, but favorite team(s) have been provided, affiliate games for those organizations will be included anyway.',
|
|
3165
|
+
includeLevels: 'Including a level (AAA, AA, A+ encoded as A%2B, or A, in a comma-separated list if more than 1) will include all of its broadcasts, and exclude all other levels.',
|
|
3166
|
+
includeTeamsInTitles: 'Adds an optional parameter to include team names in the ICS/XML titles. A value of "channels" will format the titles in the style of the legacy Channels container.',
|
|
3167
|
+
offAir: 'Adds an optional parameter to create "Off Air" events in the XML guide, listing the time of the next game on that channel. A value of "channels" will format the events in the style of the legacy Channels container.'
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
function channelInfo(label, id) {
|
|
3171
|
+
return `<div class="channelLabel"><span>${label}</span><div class="info infoPadding" data-target="#${id}">?</div></div><div id="${id}" class="infoContainer"><div class="infoContent">${channelInfoText[id]}</div></div>`
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
body += `
|
|
3175
|
+
<div class="section">`
|
|
3176
|
+
|
|
3177
|
+
body += '<div class="menuContainer"><div class="cardMenuHeader">Live Channel Playlist, XMLTV Guide, ICS Calendar</div><div class="menuContent channelContent">' + "\n"
|
|
3178
|
+
body += '<span> Allows you to generate a M3U playlist of channels, and an XML file of guide listings for those channels, to import into TV/DVR/PVR software like Tvheadend or Jellyfin. You can also subscribe to the calendar links in your preferred calendar program/service to set up event notifications. NOTE: May be helpful to specify a resolution above.</span>'
|
|
3179
|
+
|
|
3180
|
+
body += '<div class="channelScanMode"><div>' + channelInfo('Scan Mode', 'scanMode') + '</div><div class="channelLinks">'
|
|
2633
3181
|
for (var i = 0; i < VALID_SCAN_MODES.length; i++) {
|
|
2634
3182
|
body += '<button '
|
|
2635
3183
|
if ( scan_mode == VALID_SCAN_MODES[i] ) body += 'class="default" '
|
|
2636
3184
|
body += 'onclick="scan_mode=\'' + VALID_SCAN_MODES[i] + '\';reload()">' + VALID_SCAN_MODES[i] + '</button> '
|
|
2637
3185
|
}
|
|
2638
|
-
body += ' <span class="
|
|
3186
|
+
body += ' <span class="channelNote">(ON plays sample for all stream requests)</span></div></div>' + "\n"
|
|
2639
3187
|
|
|
2640
3188
|
if ( !req.query.resolution ) {
|
|
2641
3189
|
resolution = 'best'
|
|
2642
3190
|
}
|
|
2643
3191
|
|
|
2644
|
-
body += '<
|
|
3192
|
+
body += '<div class="channelRow"><div>' + channelInfo('All', 'allChannels') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2645
3193
|
|
|
2646
3194
|
let include_teams = 'ath,atl'
|
|
2647
3195
|
if ( (session.credentials.fav_teams.length > 0) && (session.credentials.fav_teams[0].length > 0) ) {
|
|
2648
3196
|
include_teams = session.credentials.fav_teams.toString()
|
|
2649
3197
|
}
|
|
2650
|
-
body += '<
|
|
3198
|
+
body += '<div class="channelRow"><div>' + channelInfo('By team', 'byTeam') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2651
3199
|
|
|
2652
|
-
body += '<
|
|
3200
|
+
body += '<div class="channelRow"><div>' + channelInfo('By team w/ radio', 'byTeamRadio') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=radio' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2653
3201
|
|
|
2654
|
-
body += '<
|
|
3202
|
+
body += '<div class="channelRow"><div>' + channelInfo('By team w/ Spanish', 'byTeamSpanish') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&audio_track=spanish' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2655
3203
|
|
|
2656
|
-
body += '<
|
|
3204
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include blackouts', 'includeBlackouts') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeBlackouts=true' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2657
3205
|
|
|
2658
3206
|
let exclude_teams = 'ath,atl'
|
|
2659
|
-
body += '<
|
|
3207
|
+
body += '<div class="channelRow"><div>' + channelInfo('Exclude a team', 'excludeTeam') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&excludeTeams=' + exclude_teams + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&excludeTeams=' + exclude_teams + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&excludeTeams=' + exclude_teams + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2660
3208
|
|
|
2661
|
-
body += '<
|
|
3209
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) Winter Leagues', 'winterLeagues') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=winter' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=winter' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=winter' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2662
3210
|
|
|
2663
3211
|
if ( entitlements.includes('MASN_110') ) {
|
|
2664
|
-
body += '<
|
|
3212
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) MASN', 'masn') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=masn' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=masn' + content_protect_b + '">xml</a></div></div>' + "\n"
|
|
2665
3213
|
}
|
|
2666
3214
|
|
|
2667
3215
|
if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
2668
|
-
body += '<
|
|
3216
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) MLB Network', 'mlbn') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=mlbn' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=mlbn' + content_protect_b + '">xml</a></div></div>' + "\n"
|
|
2669
3217
|
}
|
|
2670
3218
|
|
|
2671
3219
|
if ( entitlements.includes('SNLA_119') ) {
|
|
2672
|
-
body += '<
|
|
3220
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) SportsNet LA', 'snla') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=snla' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=snla' + content_protect_b + '">xml</a></div></div>' + "\n"
|
|
2673
3221
|
}
|
|
2674
3222
|
|
|
2675
3223
|
if ( entitlements.includes('SNY_121') ) {
|
|
2676
|
-
body += '<
|
|
3224
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) SNY', 'sny') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=sny' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=sny' + content_protect_b + '">xml</a></div></div>' + "\n"
|
|
2677
3225
|
}
|
|
2678
3226
|
|
|
2679
|
-
body += '<
|
|
3227
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) Big Inning', 'bigInning') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2680
3228
|
|
|
2681
3229
|
let gamechanger_resolution = resolution
|
|
2682
3230
|
if ( gamechanger_resolution == VALID_RESOLUTIONS[0] ) {
|
|
2683
3231
|
gamechanger_resolution = 'best'
|
|
2684
3232
|
}
|
|
2685
|
-
body += '<
|
|
3233
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) Game Changer', 'gameChanger') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + gamechanger_resolution + '&includeTeams=gamechanger' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=gamechanger' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=gamechanger' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2686
3234
|
|
|
2687
|
-
body += '<
|
|
3235
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) Stream Finder', 'streamFinder') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + gamechanger_resolution + '&includeTeams=streamfinder' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=streamfinder' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=streamfinder' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2688
3236
|
|
|
2689
|
-
body += '<
|
|
3237
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include (or exclude) Multiview', 'multiviewChannel') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2690
3238
|
|
|
2691
3239
|
if ( argv.free ) {
|
|
2692
|
-
body += '<
|
|
3240
|
+
body += '<div class="channelRow"><div>' + channelInfo('Free games only', 'freeGames') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=free' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=free' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=free' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2693
3241
|
}
|
|
2694
3242
|
|
|
2695
|
-
body += '<
|
|
2696
|
-
|
|
2697
|
-
body += '<p><span class="tooltip">Include by level<span class="tooltiptext">Including a level (AAA, AA, A+ encoded as A%2B, or A, in a comma-separated list if more than 1) will include all of its broadcasts, and exclude all other levels.</span></span>: <a href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeLevels=a%2B,aaa' + content_protect_b + '">m3u</a> and <a href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeLevels=a%2B,aaa' + content_protect_b + '">xml</a> and <a href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeLevels=a%2B,aaa' + content_protect_b + '">ics</a></p>' + "\n"
|
|
3243
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include affiliates by org', 'includeOrgs') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeOrgs=ath,atl' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeOrgs=ath' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeOrgs=ath' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2698
3244
|
|
|
2699
|
-
body += '<
|
|
3245
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include by level', 'includeLevels') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeLevels=a%2B,aaa' + content_protect_b + '">m3u</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeLevels=a%2B,aaa' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeLevels=a%2B,aaa' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2700
3246
|
|
|
2701
|
-
body += '<
|
|
3247
|
+
body += '<div class="channelRow"><div>' + channelInfo('Include teams in titles', 'includeTeamsInTitles') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeTeamsInTitles=true' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeTeamsInTitles=channels' + content_protect_b + '">legacy</a><a class="channelLink" href="' + http_root + '/calendar.ics?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&includeTeamsInTitles=true' + content_protect_b + '">ics</a></div></div>' + "\n"
|
|
2702
3248
|
|
|
2703
|
-
body += '</
|
|
3249
|
+
body += '<div class="channelRow"><div>' + channelInfo('Create Off Air events between games', 'offAir') + '</div><div class="channelLinks"><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&offAir=true' + content_protect_b + '">xml</a><a class="channelLink" href="' + http_root + '/guide.xml?mediaType=' + mediaType + '&includeTeams=' + include_teams + '&offAir=channels' + content_protect_b + '">legacy</a></div></div>' + "\n"
|
|
3250
|
+
body += '</div></div>' + "\n"
|
|
2704
3251
|
|
|
2705
|
-
body += '<
|
|
2706
|
-
body += '<p><span class="tooltip">Example links<span class="tooltiptext">Some examples how to generate predictable links.</span></span>:</p>' + "\n"
|
|
2707
|
-
body += '<p>' + "\n"
|
|
3252
|
+
body += '<div class="menuContainer"><div class="cardMenuHeader">Example links</div><div class="menuContent">'
|
|
2708
3253
|
let example_types = [ ['embed.html', 'Embed'], ['stream.m3u8', 'Stream'], ['chromecast.html', 'Chromecast'], ['kodi.strm', 'Kodi'] ]
|
|
2709
3254
|
|
|
2710
3255
|
let example_team = 'ath'
|
|
@@ -2742,10 +3287,19 @@ app.get('/', async function(req, res) {
|
|
|
2742
3287
|
}
|
|
2743
3288
|
}
|
|
2744
3289
|
}
|
|
2745
|
-
body += '</p>' + "\n"
|
|
2746
3290
|
|
|
2747
3291
|
include_teams = 'ath,atl'
|
|
2748
|
-
body +=
|
|
3292
|
+
body += `<br>
|
|
3293
|
+
<div class="is-flex">
|
|
3294
|
+
<span>Game Changer by team examples:</span>
|
|
3295
|
+
<div class="info infoPadding" data-target="#gameChangerExamplesInfo">?</div>
|
|
3296
|
+
</div>
|
|
3297
|
+
<div id="gameChangerExamplesInfo" class="infoContainer">
|
|
3298
|
+
<div class="infoContent">
|
|
3299
|
+
Game Changer supports specifying certain teams to include or exclude. Useful for following a group of teams.
|
|
3300
|
+
</div>
|
|
3301
|
+
</div>
|
|
3302
|
+
`
|
|
2749
3303
|
body += '<p>' + "\n"
|
|
2750
3304
|
let gamechanger_streamURL = server + '/gamechanger.m3u8?resolution=best' + content_protect_b
|
|
2751
3305
|
let gamechanger_types = ['in', 'ex']
|
|
@@ -2753,19 +3307,65 @@ app.get('/', async function(req, res) {
|
|
|
2753
3307
|
let example_streamURL = gamechanger_streamURL + '&' + gamechanger_types[i] + 'cludeTeams=' + include_teams
|
|
2754
3308
|
body += '• ' + gamechanger_types[i] + 'clude: <a href="' + http_root + '/embed.html?src=' + encodeURIComponent(example_streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b + '">Embed</a> | <a href="' + example_streamURL + '">Stream</a> | <a href="' + http_root + '/chromecast.html?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Chromecast</a> | <a href="' + http_root + '/advanced.html?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Advanced</a> | <a href="' + http_root + '/kodi.strm?src=' + encodeURIComponent(example_streamURL) + content_protect_b + '">Kodi</a><br/>' + "\n"
|
|
2755
3309
|
}
|
|
3310
|
+
body += '</p>' + "\n"
|
|
2756
3311
|
|
|
2757
|
-
body +=
|
|
3312
|
+
body += `
|
|
3313
|
+
<div class="is-flex">
|
|
3314
|
+
<span>Comskip link examples:</span>
|
|
3315
|
+
<div class="info infoPadding" data-target="#comskipExamplesInfo">?</div>
|
|
3316
|
+
</div>
|
|
3317
|
+
<div id="comskipExamplesInfo" class="infoContainer">
|
|
3318
|
+
<div class="infoContent">
|
|
3319
|
+
You can generate a <a href="https://github.com/erikkaashoek/Comskip">Comskip</a>-style file to automatically skip sections (breaks, idle time, or non-action pitches) of games you record using DVR software when watched in compatible players. For example, if you record a game from your local OTA channel using Tvheadend, you can then fetch one of these Comskip files, put it in the same directory with the same name as your recorded video file, and Kodi will automatically skip those sections while you watch the video.<br><br>Specifying the team and broadcast_start_timestamp in the URL is required! For the timestamp, use the time your DVR software began the recording. This should be your local time in YYYY-MM-DDTHH:MM:SS format.<br><br>Specifying a skip_adjust value in the URL is recommended, to adjust for broadcast delays. This will vary across different channels and different video sources.<br><br>For the txt file format, specifying the video frame rate (fps) in the URL is also required. This will commonly be either 30, 59.94, or 60, depending on your video source.<br><br>Optionally, setting pad to "on" will generate random extra skips at the end, to help avoid timeline spoilers.
|
|
3320
|
+
</div>
|
|
3321
|
+
</div>
|
|
3322
|
+
<p><a href="${http_root}/comskip.edl?team=CHC&date=2025-10-01&pad=on&skip=pitches&skip_adjust=11&broadcast_start_timestamp=2025-10-01T14:00:00${content_protect_a}">comskip.edl</a> or <a href="${http_root}/comskip.txt?team=CHC&date=2025-10-01&pad=on&skip=pitches&skip_adjust=11&broadcast_start_timestamp=2025-10-01T14:00:00&fps=59.94${content_protect_a}">comskip.txt</a></p>
|
|
3323
|
+
`
|
|
2758
3324
|
|
|
2759
|
-
body +=
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
3325
|
+
body += `
|
|
3326
|
+
<div class="is-flex">
|
|
3327
|
+
<span>MPEG-TS examples:</span>
|
|
3328
|
+
<div class="info infoPadding" data-target="#mpegTsExamplesInfo">?</div>
|
|
3329
|
+
</div>
|
|
3330
|
+
<div id="mpegTsExamplesInfo" class="infoContainer">
|
|
3331
|
+
<div class="infoContent">
|
|
3332
|
+
Experimental feature: a MPEGTS output format where you can adjust the audio sync with a URL parameter. Useful if the radio track is a consistent number of seconds ahead or behind the video track. Use positive sync values if radio is early, or negative values if radio is late.
|
|
3333
|
+
</div>
|
|
3334
|
+
</div>
|
|
3335
|
+
<p><a href="${http_root}/stream.ts?team=${example_team}${content_protect_a}">Stream</a> or <a href="${http_root}/stream.ts?team=${example_team}&audio_track=radio&sync=2.3${content_protect_a}">Stream w/ radio sync</a></p>
|
|
3336
|
+
`
|
|
3337
|
+
|
|
3338
|
+
body += '<br/>' + "\n"
|
|
3339
|
+
|
|
3340
|
+
body += `
|
|
3341
|
+
<div class="is-flex">
|
|
3342
|
+
<span>Sample video:</span>
|
|
3343
|
+
<div class="info infoPadding" data-target="#sampleVideoInfo">?</div>
|
|
3344
|
+
</div>
|
|
3345
|
+
<div id="sampleVideoInfo" class="infoContainer">
|
|
3346
|
+
<div class="infoContent">
|
|
3347
|
+
A sample stream. Useful for testing and troubleshooting.
|
|
3348
|
+
</div>
|
|
3349
|
+
</div>
|
|
3350
|
+
<p><a href="${http_root}/embed.html${content_protect_a}">Embed</a> | <a href="${http_root}/stream.m3u8${content_protect_a}">Stream</a> | <a href="${http_root}/chromecast.html${content_protect_a}">Chromecast</a> | <a href="${http_root}/advanced.html${content_protect_a}">Advanced</a></p>
|
|
3351
|
+
`
|
|
3352
|
+
|
|
3353
|
+
body += `
|
|
3354
|
+
<div class="is-flex">
|
|
3355
|
+
<span>Bookmarklets for MLB.com:</span>
|
|
3356
|
+
<div class="info infoPadding" data-target="#bookmarkletsInfo">?</div>
|
|
3357
|
+
</div>
|
|
3358
|
+
<div id="bookmarkletsInfo" class="infoContainer">
|
|
3359
|
+
<div class="infoContent">
|
|
3360
|
+
If you watch at MLB.com, drag these bookmarklets to your bookmarks toolbar and use them to hide parts of the interface.
|
|
3361
|
+
</div>
|
|
3362
|
+
</div>
|
|
3363
|
+
`
|
|
3364
|
+
body += '<p><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"
|
|
2766
3365
|
|
|
2767
3366
|
// Print version
|
|
2768
3367
|
body += '<p class="tinytext">Version ' + version + ' (<a href="' + http_root + '/clearcache">clear session and cache</a>)</p>' + "\n"
|
|
3368
|
+
body += '</div></div></div>'
|
|
2769
3369
|
|
|
2770
3370
|
// Datepicker functions
|
|
2771
3371
|
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"
|
|
@@ -2775,6 +3375,33 @@ app.get('/', async function(req, res) {
|
|
|
2775
3375
|
|
|
2776
3376
|
// Highlights modal functions
|
|
2777
3377
|
body += `<script type="text/javascript">
|
|
3378
|
+
`
|
|
3379
|
+
// Highlights modal positioning
|
|
3380
|
+
body += `var pageScrollY = 0;
|
|
3381
|
+
|
|
3382
|
+
function openModal() {
|
|
3383
|
+
pageScrollY = window.scrollY || document.documentElement.scrollTop;
|
|
3384
|
+
document.body.style.position = "fixed";
|
|
3385
|
+
document.body.style.top = "-" + pageScrollY + "px";
|
|
3386
|
+
document.body.style.left = "0";
|
|
3387
|
+
document.body.style.right = "0";
|
|
3388
|
+
document.body.style.width = "100%";
|
|
3389
|
+
document.body.style.overflow = "hidden";
|
|
3390
|
+
modal.style.display = "flex";
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
function closeModal() {
|
|
3394
|
+
document.body.style.position = "";
|
|
3395
|
+
document.body.style.top = "";
|
|
3396
|
+
document.body.style.left = "";
|
|
3397
|
+
document.body.style.right = "";
|
|
3398
|
+
document.body.style.width = "";
|
|
3399
|
+
document.body.style.overflow = "";
|
|
3400
|
+
window.scrollTo(0, pageScrollY);
|
|
3401
|
+
modal.style.display = "none";
|
|
3402
|
+
}
|
|
3403
|
+
`
|
|
3404
|
+
body += `
|
|
2778
3405
|
var modal = document.getElementById("myModal");
|
|
2779
3406
|
var highlightsModal = document.getElementById("highlights");
|
|
2780
3407
|
var span = document.getElementsByClassName("close")[0];
|
|
@@ -2806,7 +3433,7 @@ function parsehighlightsresponse(responsetext) {
|
|
|
2806
3433
|
}
|
|
2807
3434
|
modaltext += "</ul>";
|
|
2808
3435
|
highlightsModal.innerHTML = modaltext;
|
|
2809
|
-
|
|
3436
|
+
openModal();
|
|
2810
3437
|
} catch (e) {
|
|
2811
3438
|
alert("Error processing highlights: " + e.message)
|
|
2812
3439
|
}
|
|
@@ -2815,12 +3442,77 @@ function showhighlights(gamePk, gameDate) {
|
|
|
2815
3442
|
makeGETRequest("` + http_root + `/highlights?gamePk=" + gamePk + "&gameDate=" + gameDate + "` + content_protect_b + `", parsehighlightsresponse);
|
|
2816
3443
|
return false
|
|
2817
3444
|
}
|
|
2818
|
-
span.onclick =
|
|
2819
|
-
|
|
2820
|
-
}
|
|
2821
|
-
|
|
3445
|
+
span.onclick = closeModal;`
|
|
3446
|
+
|
|
3447
|
+
body += 'window.onclick = function(event) { if (event.target == modal) {closeModal()} }</script>' + "\n"
|
|
3448
|
+
|
|
3449
|
+
// Find recaps and condensed games from highlights
|
|
3450
|
+
body += `<script type="text/javascript">
|
|
3451
|
+
|
|
3452
|
+
function parseHighlight(responsetext, keyword) {
|
|
3453
|
+
try {
|
|
3454
|
+
var highlights = JSON.parse(responsetext);
|
|
3455
|
+
var captions_parameter = '';
|
|
3456
|
+
if (captions == 'disabled') {
|
|
3457
|
+
captions_parameter = '&captions=disabled';
|
|
3458
|
+
}
|
|
3459
|
+
var highlightExists = null;
|
|
3460
|
+
var hls_url = '';
|
|
3461
|
+
var mp4_url = '';
|
|
3462
|
+
if (highlights && (highlights.length > 0)) {
|
|
3463
|
+
|
|
3464
|
+
for (var i = 0; i < highlights.length; i++) {
|
|
3465
|
+
var keywordsAll = highlights[i].keywordsAll || [];
|
|
3466
|
+
|
|
3467
|
+
var hasKeyword = keywordsAll.some(function(item) {
|
|
3468
|
+
return item && (item.value && item.value === keyword)
|
|
3469
|
+
});
|
|
3470
|
+
if (highlights[i].headline && hasKeyword) {
|
|
3471
|
+
highlightExists = highlights[i];
|
|
3472
|
+
for (var j = 0; j < highlights[i].playbacks.length; j++) {
|
|
3473
|
+
if (highlights[i].playbacks[j].name === 'HTTP_CLOUD_WIRED_60') {
|
|
3474
|
+
hls_url = highlights[i].playbacks[j].url;
|
|
3475
|
+
} else if (highlights[i].playbacks[j].name === 'mp4Avc') {
|
|
3476
|
+
mp4_url = highlights[i].playbacks[j].url;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
break;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (!highlightExists || !hls_url) {
|
|
3483
|
+
toast();
|
|
3484
|
+
return;
|
|
3485
|
+
} { window.location.href = "` + link + `?highlight_src=" + encodeURIComponent(hls_url) + "&resolution=" + resolution + captions_parameter + "` + content_protect_b + `"; }
|
|
3486
|
+
|
|
3487
|
+
|
|
3488
|
+
}
|
|
3489
|
+
} catch (e) {
|
|
3490
|
+
|
|
3491
|
+
{toast()}
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
function showHighlightByKeyword(gamePk, gameDate, keyword) {
|
|
3496
|
+
makeGETRequest(
|
|
3497
|
+
"` + http_root + `/highlights?gamePk=" + gamePk + "&gameDate=" + gameDate + "` + content_protect_b + `",
|
|
3498
|
+
function(response) {
|
|
3499
|
+
parseHighlight(response, keyword);
|
|
3500
|
+
}
|
|
3501
|
+
);
|
|
3502
|
+
return false;
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
function showcondensed(gamePk, gameDate) {
|
|
3506
|
+
return showHighlightByKeyword(gamePk, gameDate, 'MLBCOM_CONDENSED_GAME');
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
function showrecap(gamePk, gameDate) {
|
|
3510
|
+
return showHighlightByKeyword(gamePk, gameDate, 'MLBCOM_GAME_RECAP');
|
|
3511
|
+
}
|
|
2822
3512
|
|
|
2823
|
-
|
|
3513
|
+
</script>`
|
|
3514
|
+
body +=`</div>`
|
|
3515
|
+
body += '<div id="snackbar">Not yet available. Come back later.</div>'
|
|
2824
3516
|
|
|
2825
3517
|
body += "</body></html>"
|
|
2826
3518
|
|
|
@@ -3330,6 +4022,7 @@ app.get('/highlights', async function(req, res) {
|
|
|
3330
4022
|
if ( req.query.gamePk && req.query.gameDate ) {
|
|
3331
4023
|
highlightsData = await session.getHighlightsData(req.query.gamePk, req.query.gameDate)
|
|
3332
4024
|
}
|
|
4025
|
+
res.writeHead(200, {'Content-Type': 'application/json; charset=utf-8'});
|
|
3333
4026
|
res.end(JSON.stringify(highlightsData))
|
|
3334
4027
|
} catch (e) {
|
|
3335
4028
|
session.log('highlights request error : ' + e.message)
|