mlbserver 2026.7.20 → 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 +1088 -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
|
|
@@ -1473,7 +1491,7 @@ app.get('/login', async function(req, res) {
|
|
|
1473
1491
|
const redirect = req.query.redirect || '/';
|
|
1474
1492
|
const error = req.query.error ? '<p style="color:red;">Invalid username or password</p>' : '';
|
|
1475
1493
|
res.writeHead(200, {'Content-Type': 'text/html'});
|
|
1476
|
-
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>`);
|
|
1477
1495
|
} catch (e) {
|
|
1478
1496
|
session.log('login get request error : ' + e.message);
|
|
1479
1497
|
res.end('login get request error, check log');
|
|
@@ -1644,6 +1662,7 @@ app.get('/', async function(req, res) {
|
|
|
1644
1662
|
if ( req.query.mediaType ) {
|
|
1645
1663
|
mediaType = req.query.mediaType
|
|
1646
1664
|
}
|
|
1665
|
+
var mediaTypeTV = mediaType
|
|
1647
1666
|
var resolution = VALID_RESOLUTIONS[0]
|
|
1648
1667
|
if ( req.query.resolution ) {
|
|
1649
1668
|
resolution = req.query.resolution
|
|
@@ -1696,15 +1715,9 @@ app.get('/', async function(req, res) {
|
|
|
1696
1715
|
content_protect_b = '&content_protect=' + content_protect
|
|
1697
1716
|
}
|
|
1698
1717
|
|
|
1699
|
-
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 + '"><
|
|
1700
|
-
|
|
1701
|
-
// Highlights CSS
|
|
1702
|
-
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;}'
|
|
1703
|
-
|
|
1704
|
-
// Tooltip CSS
|
|
1705
|
-
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 + '">'
|
|
1706
1719
|
|
|
1707
|
-
body += '
|
|
1720
|
+
body += '<script type="text/javascript">' + "\n";
|
|
1708
1721
|
|
|
1709
1722
|
// Define option variables in page
|
|
1710
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"
|
|
@@ -1727,28 +1740,80 @@ app.get('/', async function(req, res) {
|
|
|
1727
1740
|
body += '</script></head><body><h1>' + appname + '</h1>' + "\n"
|
|
1728
1741
|
|
|
1729
1742
|
if (argv.login_page) {
|
|
1730
|
-
body += '<p><
|
|
1743
|
+
body += '<p><form action="' + http_root + '/logout" method="GET"><button type="submit">Logout</button></form></p>' + "\n"
|
|
1731
1744
|
}
|
|
1732
|
-
|
|
1733
|
-
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">'
|
|
1734
1749
|
|
|
1735
1750
|
todayUTCHours -= 4
|
|
1736
|
-
|
|
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 + '"/> '
|
|
1737
1784
|
for (var i = 0; i < VALID_DATES.length; i++) {
|
|
1738
1785
|
body += '<button '
|
|
1739
1786
|
if ( ((VALID_DATES[i] == VALID_DATES[0]) && (gameDate == today)) || ((VALID_DATES[i] == VALID_DATES[1]) && (gameDate == yesterday)) ) body += 'class="default" '
|
|
1740
1787
|
body += 'onclick="date=\'' + VALID_DATES[i] + '\';reload()">' + VALID_DATES[i] + '</button> '
|
|
1741
1788
|
}
|
|
1742
|
-
body += '</
|
|
1789
|
+
body += '</div></div>' + "\n" + settingInfo('dateInfo') + '<div class="settingsMeta"><span>Updated ' + session.getCacheUpdatedDate(cache_name) + '</span></div>' + "\n"
|
|
1743
1790
|
|
|
1744
|
-
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">'
|
|
1745
1809
|
for (const [key, value] of Object.entries(levels)) {
|
|
1746
1810
|
body += '<button '
|
|
1747
1811
|
if ( level == key ) body += 'class="default" '
|
|
1748
1812
|
body += 'onclick="org=\'' + default_org + '\';level=\'' + key + '\';reload()">' + key + '</button> '
|
|
1749
1813
|
}
|
|
1750
1814
|
|
|
1751
|
-
body += '
|
|
1815
|
+
body += '<span class="settingsDivider">or</span>'
|
|
1816
|
+
body += settingInlineInfoLabel('Org', 'orgInfo') + ' '
|
|
1752
1817
|
body += '<select id="org" onchange="level=\'' + default_org + '\';org=this.value;reload()">'
|
|
1753
1818
|
body += '<option value="' + default_org + '">' + default_org + '</option>'
|
|
1754
1819
|
var orgs = session.getOrgs()
|
|
@@ -1757,48 +1822,22 @@ app.get('/', async function(req, res) {
|
|
|
1757
1822
|
if ( org == orgs[i] ) body += ' selected'
|
|
1758
1823
|
body += '>' + orgs[i] + '</option> '
|
|
1759
1824
|
}
|
|
1760
|
-
body += '</select></
|
|
1825
|
+
body += '</select></div></div>' + settingInfo('levelInfo') + settingInfo('orgInfo') + '</div></details></div>' + "\n"
|
|
1761
1826
|
|
|
1762
|
-
body += '<
|
|
1763
|
-
for (var i = 0; i < VALID_MEDIA_TYPES.length; i++) {
|
|
1764
|
-
body += '<button '
|
|
1765
|
-
if ( mediaType == VALID_MEDIA_TYPES[i] ) body += 'class="default" '
|
|
1766
|
-
body += 'onclick="mediaType=\'' + VALID_MEDIA_TYPES[i] + '\';reload()">' + VALID_MEDIA_TYPES[i] + '</button> '
|
|
1767
|
-
}
|
|
1768
|
-
body += '</p>' + "\n"
|
|
1769
|
-
|
|
1770
|
-
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>: '
|
|
1771
|
-
for (var i = 0; i < VALID_LINK_TYPES.length; i++) {
|
|
1772
|
-
body += '<button '
|
|
1773
|
-
if ( linkType == VALID_LINK_TYPES[i] ) body += 'class="default" '
|
|
1774
|
-
body += 'onclick="linkType=\'' + VALID_LINK_TYPES[i] + '\';reload()">' + VALID_LINK_TYPES[i] + '</button> '
|
|
1775
|
-
}
|
|
1776
|
-
body += '</p>' + "\n"
|
|
1827
|
+
body += '<div class="settingsGroup">'
|
|
1777
1828
|
|
|
1778
|
-
body += '<p>'
|
|
1779
1829
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1780
|
-
body += '<
|
|
1781
|
-
for (var i = 0; i < VALID_CONTROLS.length; i++) {
|
|
1782
|
-
body += '<button '
|
|
1783
|
-
if ( controls == VALID_CONTROLS[i] ) body += 'class="default" '
|
|
1784
|
-
body += 'onclick="controls=\'' + VALID_CONTROLS[i] + '\';reload()">' + VALID_CONTROLS[i] + '</button> '
|
|
1785
|
-
}
|
|
1786
|
-
body += '</p>' + "\n"
|
|
1787
|
-
|
|
1788
|
-
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">'
|
|
1789
1831
|
for (var i = 0; i < VALID_START_FROM.length; i++) {
|
|
1790
1832
|
body += '<button '
|
|
1791
1833
|
if ( startFrom == VALID_START_FROM[i] ) body += 'class="default" '
|
|
1792
1834
|
body += 'onclick="startFrom=\'' + VALID_START_FROM[i] + '\';reload()">' + VALID_START_FROM[i] + '</button> '
|
|
1793
1835
|
}
|
|
1794
|
-
body +=
|
|
1795
|
-
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1796
|
-
body += 'or '
|
|
1797
|
-
}
|
|
1836
|
+
body += '</div></div>' + settingInfo('startFromInfo')
|
|
1798
1837
|
}
|
|
1799
1838
|
|
|
1800
1839
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1801
|
-
body += '<
|
|
1840
|
+
body += '<div class="settingRow">' + settingInfoLabel('Inning', 'inningInfo') + '<div class="settingControl">'
|
|
1802
1841
|
body += '<select id="inning_half" onchange="inning_half=this.value;reload()">'
|
|
1803
1842
|
for (var i = 0; i < VALID_INNING_HALF.length; i++) {
|
|
1804
1843
|
body += '<option value="' + VALID_INNING_HALF[i] + '"'
|
|
@@ -1816,23 +1855,119 @@ app.get('/', async function(req, res) {
|
|
|
1816
1855
|
}
|
|
1817
1856
|
body += '</select>'
|
|
1818
1857
|
}
|
|
1819
|
-
|
|
1858
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1859
|
+
body += '</div></div>' + "\n"
|
|
1860
|
+
body += settingInfo('inningInfo')
|
|
1861
|
+
}
|
|
1820
1862
|
|
|
1821
|
-
body += '<
|
|
1822
|
-
|
|
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++) {
|
|
1823
1867
|
body += '<button '
|
|
1824
|
-
if (
|
|
1825
|
-
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.'
|
|
1826
1956
|
}
|
|
1827
|
-
body += '</p>' + "\n"
|
|
1828
1957
|
|
|
1829
|
-
|
|
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
|
+
}
|
|
1830
1965
|
|
|
1831
1966
|
// Rename some parameters before display links
|
|
1832
1967
|
var mediaFeedType = 'mediaFeedType'
|
|
1833
1968
|
var language = 'en'
|
|
1834
1969
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1835
|
-
|
|
1970
|
+
mediaTypeTV = 'MLBTV'
|
|
1836
1971
|
} else if ( mediaType == VALID_MEDIA_TYPES[2] ) {
|
|
1837
1972
|
//mediaType = VALID_MEDIA_TYPES[1]
|
|
1838
1973
|
language = 'es'
|
|
@@ -1858,10 +1993,107 @@ app.get('/', async function(req, res) {
|
|
|
1858
1993
|
|
|
1859
1994
|
let entitlements = await session.getEntitlements()
|
|
1860
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
|
+
|
|
1861
2093
|
// MASN live stream for entitled subscribers
|
|
1862
2094
|
try {
|
|
1863
|
-
if (
|
|
1864
|
-
body += '<
|
|
2095
|
+
if ( entitledMASN ) {
|
|
2096
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('MASN', 'masnInfo')
|
|
1865
2097
|
let querystring = '?event=MASN'
|
|
1866
2098
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1867
2099
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1876,9 +2108,10 @@ app.get('/', async function(req, res) {
|
|
|
1876
2108
|
}
|
|
1877
2109
|
querystring += content_protect_b
|
|
1878
2110
|
multiviewquerystring += content_protect_b
|
|
1879
|
-
body += '<a href="' + thislink + querystring + '">MASN</a>'
|
|
1880
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1881
|
-
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')
|
|
1882
2115
|
} // end entitlements check
|
|
1883
2116
|
} catch (e) {
|
|
1884
2117
|
session.debuglog('MASN detect error : ' + e.message)
|
|
@@ -1886,8 +2119,8 @@ app.get('/', async function(req, res) {
|
|
|
1886
2119
|
|
|
1887
2120
|
// MLB Network live stream for eligible USA subscribers
|
|
1888
2121
|
try {
|
|
1889
|
-
if (
|
|
1890
|
-
body += '<
|
|
2122
|
+
if ( entitledMLBN || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
2123
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('MLB Network', 'mlbnInfo')
|
|
1891
2124
|
let querystring = '?event=MLBN'
|
|
1892
2125
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1893
2126
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1902,9 +2135,10 @@ app.get('/', async function(req, res) {
|
|
|
1902
2135
|
}
|
|
1903
2136
|
querystring += content_protect_b
|
|
1904
2137
|
multiviewquerystring += content_protect_b
|
|
1905
|
-
body += '<a href="' + thislink + querystring + '">MLB Network</a>'
|
|
1906
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1907
|
-
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')
|
|
1908
2142
|
} // end entitlements check
|
|
1909
2143
|
} catch (e) {
|
|
1910
2144
|
session.debuglog('MLB Network detect error : ' + e.message)
|
|
@@ -1912,8 +2146,8 @@ app.get('/', async function(req, res) {
|
|
|
1912
2146
|
|
|
1913
2147
|
// SNLA live stream for entitled subscribers
|
|
1914
2148
|
try {
|
|
1915
|
-
if (
|
|
1916
|
-
body += '<
|
|
2149
|
+
if ( entitledSNLA ) {
|
|
2150
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('SportsNet LA', 'snlaInfo')
|
|
1917
2151
|
let querystring = '?event=SNLA'
|
|
1918
2152
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1919
2153
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1928,9 +2162,10 @@ app.get('/', async function(req, res) {
|
|
|
1928
2162
|
}
|
|
1929
2163
|
querystring += content_protect_b
|
|
1930
2164
|
multiviewquerystring += content_protect_b
|
|
1931
|
-
body += '<a href="' + thislink + querystring + '">SNLA</a>'
|
|
1932
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1933
|
-
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')
|
|
1934
2169
|
} // end entitlements check
|
|
1935
2170
|
} catch (e) {
|
|
1936
2171
|
session.debuglog('SNLA detect error : ' + e.message)
|
|
@@ -1938,8 +2173,8 @@ app.get('/', async function(req, res) {
|
|
|
1938
2173
|
|
|
1939
2174
|
// SNY live stream for entitled subscribers
|
|
1940
2175
|
try {
|
|
1941
|
-
if (
|
|
1942
|
-
body += '<
|
|
2176
|
+
if ( entitledSNY ) {
|
|
2177
|
+
body += '<div class="flex-between subscriptionStream">' + subscriptionInfoLabel('SNY', 'snyInfo')
|
|
1943
2178
|
let querystring = '?event=SNY'
|
|
1944
2179
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1945
2180
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1954,9 +2189,10 @@ app.get('/', async function(req, res) {
|
|
|
1954
2189
|
}
|
|
1955
2190
|
querystring += content_protect_b
|
|
1956
2191
|
multiviewquerystring += content_protect_b
|
|
1957
|
-
body += '<a href="' + thislink + querystring + '">SNY</a>'
|
|
1958
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this)">'
|
|
1959
|
-
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')
|
|
1960
2196
|
} // end entitlements check
|
|
1961
2197
|
} catch (e) {
|
|
1962
2198
|
session.debuglog('SNY detect error : ' + e.message)
|
|
@@ -1969,7 +2205,7 @@ app.get('/', async function(req, res) {
|
|
|
1969
2205
|
}
|
|
1970
2206
|
}
|
|
1971
2207
|
|
|
1972
|
-
if ( (
|
|
2208
|
+
if ( (mediaTypeTV == 'MLBTV') && ((level_ids == levels['MLB']) || level_ids.startsWith(levels['MLB'] + ',')) ) {
|
|
1973
2209
|
// Recap Rundown beginning in 2023, disabled because it stopped working
|
|
1974
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) ) {
|
|
1975
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>'
|
|
@@ -1986,14 +2222,16 @@ app.get('/', async function(req, res) {
|
|
|
1986
2222
|
|
|
1987
2223
|
// Big Inning
|
|
1988
2224
|
var big_inning
|
|
1989
|
-
if (
|
|
2225
|
+
if ( entitledBigInning ) {
|
|
1990
2226
|
// Scraped Big Inning schedule
|
|
1991
2227
|
big_inning = await session.getBigInningSchedule(gameDate)
|
|
1992
2228
|
}
|
|
1993
2229
|
if ( big_inning ) {
|
|
2230
|
+
let bigInningRendered = false
|
|
1994
2231
|
for (var i = 0; i < big_inning.length; i++) {
|
|
1995
|
-
if ( big_inning[i].start ) {
|
|
1996
|
-
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>'
|
|
1997
2235
|
let compareStart = new Date(big_inning[i].start)
|
|
1998
2236
|
compareStart.setMinutes(compareStart.getMinutes()-10)
|
|
1999
2237
|
let compareEnd = new Date(big_inning[i].end)
|
|
@@ -2013,18 +2251,20 @@ app.get('/', async function(req, res) {
|
|
|
2013
2251
|
}
|
|
2014
2252
|
querystring += content_protect_b
|
|
2015
2253
|
multiviewquerystring += content_protect_b
|
|
2016
|
-
body += '<a href="' + thislink + querystring + '">Big Inning</a>'
|
|
2017
|
-
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>'
|
|
2018
2256
|
} else {
|
|
2019
|
-
body += 'Big Inning'
|
|
2257
|
+
body += subscriptionInfoLabel('Big Inning', 'bigInningInfo')
|
|
2020
2258
|
}
|
|
2021
|
-
body += '</
|
|
2259
|
+
body += '</div>' + "\n"
|
|
2260
|
+
body += subscriptionInfo('bigInningInfo', 'bigInning')
|
|
2261
|
+
bigInningRendered = true
|
|
2022
2262
|
}
|
|
2023
2263
|
}
|
|
2024
2264
|
}
|
|
2025
|
-
|
|
2265
|
+
|
|
2026
2266
|
// Game Changer and Stream Finder
|
|
2027
|
-
if (
|
|
2267
|
+
if ( entitledChangerFinder ) {
|
|
2028
2268
|
let gameIndexes = await session.get_first_and_last_games(cache_data.dates[0].games, blackouts)
|
|
2029
2269
|
if ( (typeof gameIndexes.firstGameIndex !== 'undefined') && (typeof gameIndexes.lastGameIndex !== 'undefined') && (gameIndexes.firstGameIndex !== gameIndexes.lastGameIndex) ) {
|
|
2030
2270
|
let compareStart = new Date(cache_data.dates[0].games[gameIndexes.firstGameIndex].gameDate)
|
|
@@ -2034,7 +2274,8 @@ app.get('/', async function(req, res) {
|
|
|
2034
2274
|
compareEnd.setHours(compareEnd.getHours()+4)
|
|
2035
2275
|
}
|
|
2036
2276
|
compareEnd.setHours(compareEnd.getHours()+4)
|
|
2037
|
-
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>'
|
|
2038
2279
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
2039
2280
|
let streamURL = server + '/gamechanger.m3u8'
|
|
2040
2281
|
let multiviewquerystring = '/gamechanger.m3u8?resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
|
|
@@ -2050,14 +2291,15 @@ app.get('/', async function(req, res) {
|
|
|
2050
2291
|
if ( linkType == VALID_LINK_TYPES[4] ) {
|
|
2051
2292
|
streamURL += '&filename=' + gameDate + ' Game Changer'
|
|
2052
2293
|
}
|
|
2053
|
-
body += '<a href="' + streamURL + '">Game Changer</a>'
|
|
2054
|
-
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>'
|
|
2055
2296
|
} else {
|
|
2056
|
-
body += 'Game Changer'
|
|
2297
|
+
body += subscriptionInfoLabel('Game Changer', 'gameChangerInfo')
|
|
2057
2298
|
}
|
|
2058
|
-
body += '</
|
|
2059
|
-
|
|
2060
|
-
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>'
|
|
2061
2303
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
2062
2304
|
let streamURL = server + '/gamechanger.m3u8?streamFinder=on'
|
|
2063
2305
|
let multiviewquerystring = '/gamechanger.m3u8?streamFinder=on&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
|
|
@@ -2073,15 +2315,17 @@ app.get('/', async function(req, res) {
|
|
|
2073
2315
|
if ( linkType == VALID_LINK_TYPES[4] ) {
|
|
2074
2316
|
streamURL += '&filename=' + gameDate + ' Stream Finder'
|
|
2075
2317
|
}
|
|
2076
|
-
body += '<a href="' + streamURL + '">Stream Finder</a>'
|
|
2077
|
-
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>'
|
|
2078
2320
|
} else {
|
|
2079
|
-
body += 'Stream Finder'
|
|
2321
|
+
body += subscriptionInfoLabel('Stream Finder', 'streamFinderInfo')
|
|
2080
2322
|
}
|
|
2081
|
-
body += '</
|
|
2323
|
+
body += '</div>'
|
|
2324
|
+
body += subscriptionInfo('streamFinderInfo', 'streamFinder')
|
|
2082
2325
|
}
|
|
2083
2326
|
}
|
|
2084
2327
|
}
|
|
2328
|
+
body += `</div></div>`
|
|
2085
2329
|
|
|
2086
2330
|
if ( cache_data.dates && cache_data.dates[0] && cache_data.dates[0].games ) {
|
|
2087
2331
|
for (var j = 0; j < cache_data.dates[0].games.length; j++) {
|
|
@@ -2089,43 +2333,70 @@ app.get('/', async function(req, res) {
|
|
|
2089
2333
|
|
|
2090
2334
|
let game_started = false
|
|
2091
2335
|
|
|
2092
|
-
let
|
|
2093
|
-
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
|
|
2094
2339
|
if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
|
|
2095
2340
|
awayteam = cache_data.dates[0].games[j].teams['away'].team.shortName
|
|
2096
2341
|
let parentOrgName = cache_data.dates[0].games[j].teams['away'].team.parentOrgName
|
|
2097
|
-
if (parentOrgName != 'Office of the Commissioner')
|
|
2342
|
+
if (parentOrgName != 'Office of the Commissioner') parentAwayVis = '';
|
|
2098
2343
|
awayteam_abbr = cache_data.dates[0].games[j].teams['away'].team.abbreviation
|
|
2099
2344
|
awayteam_level = session.getLevelNameFromSportId(cache_data.dates[0].games[j].teams['away'].team.sport.id)
|
|
2100
2345
|
}
|
|
2101
|
-
let
|
|
2102
|
-
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
|
|
2103
2349
|
if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
|
|
2104
2350
|
hometeam = cache_data.dates[0].games[j].teams['home'].team.shortName
|
|
2105
2351
|
let parentOrgName = cache_data.dates[0].games[j].teams['home'].team.parentOrgName
|
|
2106
|
-
if (parentOrgName != 'Office of the Commissioner')
|
|
2352
|
+
if (parentOrgName != 'Office of the Commissioner') parentHomeVis = '';
|
|
2107
2353
|
hometeam_abbr = cache_data.dates[0].games[j].teams['home'].team.abbreviation
|
|
2108
2354
|
hometeam_level = session.getLevelNameFromSportId(cache_data.dates[0].games[j].teams['home'].team.sport.id)
|
|
2109
2355
|
}
|
|
2110
2356
|
|
|
2111
|
-
let
|
|
2357
|
+
let awayDisplay = awayteam
|
|
2358
|
+
let homeDisplay = hometeam
|
|
2359
|
+
/*
|
|
2112
2360
|
if ( awayteam_abbr ) {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2361
|
+
awayDisplay =
|
|
2362
|
+
'<span class="tooltip">' +
|
|
2363
|
+
awayteam +
|
|
2364
|
+
'<span class="tooltiptext left">' +
|
|
2365
|
+
awayteam_abbr + ' (' + awayteam_level + ')' +
|
|
2366
|
+
'</span></span>'
|
|
2116
2367
|
}
|
|
2117
|
-
|
|
2368
|
+
|
|
2118
2369
|
if ( hometeam_abbr ) {
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2370
|
+
homeDisplay =
|
|
2371
|
+
'<span class="tooltip">' +
|
|
2372
|
+
hometeam +
|
|
2373
|
+
'<span class="tooltiptext right">' +
|
|
2374
|
+
hometeam_abbr + ' (' + hometeam_level + ')' +
|
|
2375
|
+
'</span></span>'
|
|
2122
2376
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
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
|
+
}
|
|
2126
2397
|
|
|
2127
2398
|
if ( cache_data.dates[0].games[j].status.startTimeTBD == true ) {
|
|
2128
|
-
state
|
|
2399
|
+
state = 'Time TBD'
|
|
2129
2400
|
} else {
|
|
2130
2401
|
let startTime = new Date(cache_data.dates[0].games[j].gameDate)
|
|
2131
2402
|
state += startTime.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true })
|
|
@@ -2139,8 +2410,8 @@ app.get('/', async function(req, res) {
|
|
|
2139
2410
|
relative_inning = relative_inning < 0 ? 0 : relative_inning
|
|
2140
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 ) {
|
|
2141
2412
|
if ( (scores == VALID_SCORES[1]) && (abstractGameState != 'Preview') && (detailedState != 'Postponed') ) {
|
|
2142
|
-
|
|
2143
|
-
|
|
2413
|
+
awayscore = ''
|
|
2414
|
+
homescore = ''
|
|
2144
2415
|
if ( (inning_number != VALID_INNING_NUMBER[0]) && cache_data.dates[0].games[j].linescore && cache_data.dates[0].games[j].linescore.innings ) {
|
|
2145
2416
|
awayscore = 0
|
|
2146
2417
|
homescore = 0
|
|
@@ -2170,59 +2441,86 @@ app.get('/', async function(req, res) {
|
|
|
2170
2441
|
}
|
|
2171
2442
|
}
|
|
2172
2443
|
if ( display_inning != '' ) {
|
|
2173
|
-
state =
|
|
2444
|
+
state = display_inning
|
|
2174
2445
|
}
|
|
2175
2446
|
} else {
|
|
2176
2447
|
awayscore = cache_data.dates[0].games[j].teams['away'].score
|
|
2177
2448
|
homescore = cache_data.dates[0].games[j].teams['home'].score
|
|
2178
|
-
|
|
2449
|
+
//if ( cache_data.dates[0].games[j].gameUtils.isLive && !cache_data.dates[0].games[j].gameUtils.isFinal ) {
|
|
2179
2450
|
if ( abstractGameState == 'Live' ) {
|
|
2180
|
-
state =
|
|
2451
|
+
state = cache_data.dates[0].games[j].linescore.inningHalf.substr(0,1) + cache_data.dates[0].games[j].linescore.currentInning
|
|
2181
2452
|
//} else if ( cache_data.dates[0].games[j].gameUtils.isFinal ) {
|
|
2182
2453
|
} else if ( abstractGameState == 'Final' ) {
|
|
2183
|
-
state =
|
|
2454
|
+
state = detailedState
|
|
2184
2455
|
}
|
|
2185
2456
|
if ( cache_data.dates[0].games[j].flags ) {
|
|
2186
2457
|
if ( cache_data.dates[0].games[j].flags.perfectGame == true ) {
|
|
2187
|
-
|
|
2458
|
+
noteworthyTag = 'PG'
|
|
2459
|
+
noteworthyTooltip = 'Perfect Game'
|
|
2460
|
+
noteworthyCSS = 'tag noteworthy'
|
|
2461
|
+
tooltipCSS = 'tooltip'
|
|
2462
|
+
tooltipTextCSS = 'tooltiptext'
|
|
2188
2463
|
} else if ( cache_data.dates[0].games[j].flags.noHitter == true ) {
|
|
2189
|
-
|
|
2464
|
+
noteworthyTag = 'NH'
|
|
2465
|
+
noteworthyTooltip = 'No-Hitter'
|
|
2466
|
+
noteworthyCSS = 'tag noteworthy'
|
|
2467
|
+
tooltipCSS = 'tooltip'
|
|
2468
|
+
tooltipTextCSS = 'tooltiptext'
|
|
2469
|
+
} else {
|
|
2470
|
+
noteworthyTag = ''
|
|
2190
2471
|
}
|
|
2191
2472
|
}
|
|
2192
2473
|
}
|
|
2193
|
-
|
|
2194
|
-
//} 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 ) {
|
|
2195
2475
|
} else if ( detailedState == 'Postponed' ) {
|
|
2196
|
-
state =
|
|
2197
|
-
|
|
2476
|
+
state = detailedState
|
|
2477
|
+
//} else if ( cache_data.dates[0].games[j].gameUtils.isDelayed ) {
|
|
2198
2478
|
} else if ( detailedState.startsWith('Delayed') ) {
|
|
2199
|
-
|
|
2479
|
+
state = detailedState
|
|
2200
2480
|
}
|
|
2201
2481
|
|
|
2202
|
-
|
|
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 = ''
|
|
2203
2499
|
|
|
2204
2500
|
if ( cache_data.dates[0].games[j].doubleHeader != 'N' ) {
|
|
2205
|
-
|
|
2501
|
+
gameNo = cache_data.dates[0].games[j].gameNumber
|
|
2502
|
+
doubleHeaderVis = ''
|
|
2206
2503
|
filename += 'Game ' + cache_data.dates[0].games[j].gameNumber + ' '
|
|
2207
2504
|
}
|
|
2208
2505
|
if ( cache_data.dates[0].games[j].description ) {
|
|
2209
|
-
|
|
2506
|
+
scheduleDesc = cache_data.dates[0].games[j].description
|
|
2507
|
+
scheduleVis = ''
|
|
2210
2508
|
}
|
|
2211
2509
|
if ( scheduledInnings != '9' ) {
|
|
2212
|
-
|
|
2510
|
+
inningsVis = ''
|
|
2213
2511
|
}
|
|
2214
2512
|
var resumeStatus = false
|
|
2215
2513
|
if ( cache_data.dates[0].games[j].resumeGameDate || cache_data.dates[0].games[j].resumedFromDate ) {
|
|
2216
|
-
|
|
2514
|
+
let resumeText = ''
|
|
2217
2515
|
let resumeDate
|
|
2218
2516
|
if ( cache_data.dates[0].games[j].resumeGameDate ) {
|
|
2219
2517
|
resumeDate = new Date(cache_data.dates[0].games[j].resumeDate)
|
|
2220
|
-
|
|
2518
|
+
resumeText += 'Resuming on'
|
|
2221
2519
|
} else {
|
|
2222
2520
|
resumeDate = new Date(cache_data.dates[0].games[j].resumedFrom)
|
|
2223
|
-
|
|
2521
|
+
resumeText += 'Resumed from'
|
|
2224
2522
|
}
|
|
2225
|
-
|
|
2523
|
+
resumeText += '' + resumeDate.toLocaleString('default', { month: 'long', day: 'numeric' })
|
|
2226
2524
|
// Also show the status by the media links, if one of them is live
|
|
2227
2525
|
resumeStatus = 'archived'
|
|
2228
2526
|
if ( (typeof cache_data.dates[0].games[j].broadcasts) != 'undefined' ) {
|
|
@@ -2236,61 +2534,82 @@ app.get('/', async function(req, res) {
|
|
|
2236
2534
|
}
|
|
2237
2535
|
|
|
2238
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) ) {
|
|
2239
|
-
pitchers = "<br/>"
|
|
2240
2537
|
if ( cache_data.dates[0].games[j].teams['away'].probablePitcher && cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName ) {
|
|
2241
2538
|
if ( cache_data.dates[0].games[j].teams['away'].team.sport.name != 'Major League Baseball' ) {
|
|
2242
|
-
|
|
2539
|
+
awayPitcher = getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName)
|
|
2243
2540
|
} else {
|
|
2244
|
-
|
|
2541
|
+
awayPitcher = getLastName(cache_data.dates[0].games[j].teams['away'].probablePitcher.fullName)
|
|
2245
2542
|
}
|
|
2246
|
-
pitchers += '</a>'
|
|
2247
2543
|
} else {
|
|
2248
|
-
|
|
2544
|
+
awayPitcher = 'TBD'
|
|
2249
2545
|
}
|
|
2250
|
-
pitchers += ' vs '
|
|
2251
2546
|
if ( cache_data.dates[0].games[j].teams['home'].probablePitcher && cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName ) {
|
|
2252
2547
|
if ( cache_data.dates[0].games[j].teams['home'].team.sport.name != 'Major League Baseball' ) {
|
|
2253
|
-
|
|
2548
|
+
homePitcher = getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName)
|
|
2254
2549
|
} else {
|
|
2255
|
-
|
|
2550
|
+
homePitcher = getLastName(cache_data.dates[0].games[j].teams['home'].probablePitcher.fullName)
|
|
2256
2551
|
}
|
|
2257
|
-
pitchers += '</a>'
|
|
2258
2552
|
} else {
|
|
2259
|
-
|
|
2553
|
+
homePitcher = 'TBD'
|
|
2260
2554
|
}
|
|
2261
2555
|
}
|
|
2262
2556
|
|
|
2263
|
-
|
|
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
|
+
|
|
2264
2564
|
let fav_style = ''
|
|
2265
|
-
|
|
2266
|
-
|
|
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
|
+
|
|
2267
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) ) {
|
|
2268
2576
|
let fav_team = cache_data.dates[0].games[j].teams['away'].team.abbreviation
|
|
2269
2577
|
if ( session.credentials.fav_teams.includes(cache_data.dates[0].games[j].teams['home'].team.abbreviation) ) {
|
|
2270
2578
|
fav_team = cache_data.dates[0].games[j].teams['home'].team.abbreviation
|
|
2271
2579
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
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;"'
|
|
2274
2586
|
}
|
|
2275
|
-
|
|
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
|
+
|
|
2276
2591
|
if ( cache_data.dates[0].games[j].seriesDescription != 'Regular Season' ) {
|
|
2277
|
-
|
|
2592
|
+
gameLevel = cache_data.dates[0].games[j].seriesDescription
|
|
2278
2593
|
}
|
|
2279
|
-
body += '><td>' + description + teams + pitchers + state + '</td>'
|
|
2280
2594
|
|
|
2281
2595
|
// Check if Winter League / MiLB game first
|
|
2282
|
-
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']) && (
|
|
2283
|
-
|
|
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'
|
|
2284
2600
|
if ( cache_data.dates[0].games[j].broadcasts ) {
|
|
2285
2601
|
let broadcastName = 'N/A'
|
|
2286
2602
|
for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
|
|
2287
2603
|
if ( cache_data.dates[0].games[j].broadcasts[k].name != 'Audio' ) {
|
|
2288
|
-
broadcastName =
|
|
2604
|
+
broadcastName = mediaTypeTV
|
|
2289
2605
|
break
|
|
2290
2606
|
}
|
|
2291
2607
|
}
|
|
2292
2608
|
if ( broadcastName == 'N/A' ) {
|
|
2293
|
-
|
|
2609
|
+
streamSource.homeTV +=
|
|
2610
|
+
'<span>' +
|
|
2611
|
+
'<span class="streamAction streamActionInactive">' + broadcastName + '</span>' +
|
|
2612
|
+
'</span>'
|
|
2294
2613
|
} else {
|
|
2295
2614
|
// Check if game should be live
|
|
2296
2615
|
if ( (cache_data.dates[0].games[j].status.detailedState != 'Postponed') && (cache_data.dates[0].games[j].status.detailedState != 'Cancelled') ) {
|
|
@@ -2336,21 +2655,30 @@ app.get('/', async function(req, res) {
|
|
|
2336
2655
|
}
|
|
2337
2656
|
querystring += content_protect_b
|
|
2338
2657
|
multiviewquerystring += content_protect_b
|
|
2339
|
-
|
|
2340
|
-
|
|
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>'
|
|
2341
2670
|
} else {
|
|
2342
|
-
|
|
2671
|
+
streamSource.homeTV +=
|
|
2672
|
+
'<span>' +
|
|
2673
|
+
'<span class="streamAction streamActionInactive">' + broadcastName + '</span>' +
|
|
2674
|
+
'</span>'
|
|
2343
2675
|
}
|
|
2344
2676
|
}
|
|
2345
2677
|
}
|
|
2346
2678
|
}
|
|
2347
|
-
body += "</td>"
|
|
2348
2679
|
} else {
|
|
2349
2680
|
// Begin MLB games
|
|
2350
|
-
if ( (typeof cache_data.dates[0].games[j].broadcasts)
|
|
2351
|
-
body += "<td></td>"
|
|
2352
|
-
} else {
|
|
2353
|
-
body += "<td>"
|
|
2681
|
+
if ( (typeof cache_data.dates[0].games[j].broadcasts) != 'undefined' ) {
|
|
2354
2682
|
for (var k = 0; k < cache_data.dates[0].games[j].broadcasts.length; k++) {
|
|
2355
2683
|
let broadcast = cache_data.dates[0].games[j].broadcasts[k]
|
|
2356
2684
|
if ( broadcast.availableForStreaming ) {
|
|
@@ -2360,47 +2688,43 @@ app.get('/', async function(req, res) {
|
|
|
2360
2688
|
} else if ( broadcast.language == 'es' ) {
|
|
2361
2689
|
mediaTitle = 'Spanish'
|
|
2362
2690
|
}
|
|
2363
|
-
if ( mediaTitle ==
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
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
|
+
}*/
|
|
2368
2696
|
|
|
2369
2697
|
// check if language is not set (video) or it matches requested language
|
|
2370
2698
|
if ( broadcast.language == language ) {
|
|
2371
|
-
let teamabbr
|
|
2372
|
-
|
|
2373
|
-
if ( broadcast.isNational ) {
|
|
2374
|
-
teamabbr = 'NATIONAL'
|
|
2375
|
-
} else {
|
|
2376
|
-
teamabbr = hometeam
|
|
2377
|
-
if ( broadcast.homeAway == 'away' ) {
|
|
2378
|
-
teamabbr = awayteam
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
2699
|
let station = broadcast.callSign
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
if (
|
|
2387
|
-
|
|
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>'
|
|
2388
2716
|
}
|
|
2389
2717
|
|
|
2390
|
-
// display blackout tooltip, if necessary
|
|
2391
2718
|
if ( blackouts[gamePk] && blackouts[gamePk].blackout_feeds && blackouts[gamePk].blackout_feeds.includes(broadcast.mediaId) ) {
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
|
2398
2726
|
}
|
|
2399
|
-
body += '</span></span>'
|
|
2400
|
-
} else {
|
|
2401
|
-
body += teamabbr
|
|
2402
2727
|
}
|
|
2403
|
-
body += ': '
|
|
2404
2728
|
|
|
2405
2729
|
if ( broadcast.mediaState && broadcast.mediaState.mediaStateCode && ((broadcast.mediaState.mediaStateCode == 'MEDIA_ON') || (broadcast.mediaState.mediaStateCode == 'MEDIA_ARCHIVE') || (abstractGameState == 'Final')) ) {
|
|
2406
2730
|
let gameTime = new Date(cache_data.dates[0].games[j].gameDate)
|
|
@@ -2409,10 +2733,12 @@ app.get('/', async function(req, res) {
|
|
|
2409
2733
|
game_started = true
|
|
2410
2734
|
}
|
|
2411
2735
|
let mediaId = broadcast.mediaId
|
|
2412
|
-
|
|
2413
|
-
|
|
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
|
|
2414
2740
|
} else {
|
|
2415
|
-
let querystring
|
|
2741
|
+
let querystring
|
|
2416
2742
|
querystring = '?mediaId=' + mediaId
|
|
2417
2743
|
|
|
2418
2744
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
@@ -2428,7 +2754,7 @@ app.get('/', async function(req, res) {
|
|
|
2428
2754
|
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
2429
2755
|
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
2430
2756
|
}
|
|
2431
|
-
if (
|
|
2757
|
+
if ( mediaTypeTV == 'MLBTV' ) {
|
|
2432
2758
|
if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
|
|
2433
2759
|
if ( inning_number != VALID_INNING_NUMBER[0] ) querystring += '&inning_number=' + relative_inning
|
|
2434
2760
|
if ( skip != VALID_SKIP[0] ) querystring += '&skip=' + skip
|
|
@@ -2449,263 +2775,481 @@ app.get('/', async function(req, res) {
|
|
|
2449
2775
|
}
|
|
2450
2776
|
querystring += content_protect_b
|
|
2451
2777
|
multiviewquerystring += content_protect_b
|
|
2452
|
-
stationlink = '<a
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
body += '<input type="checkbox" value="http://127.0.0.1:' + session.data.port + '/stream.m3u8' + multiviewquerystring + '" onclick="addmultiview(this, [\'' + awayteam + '\', \'' + hometeam + '\'])">'
|
|
2461
|
-
}
|
|
2462
|
-
if ( resumeStatus ) {
|
|
2463
|
-
body += '('
|
|
2464
|
-
// for suspended games that haven't finished yet, we can simply use the mediaState to determine the status
|
|
2465
|
-
if ( resumeStatus == 'live' ) {
|
|
2466
|
-
if ( broadcast.mediaState.mediaStateCode == 'MEDIA_ARCHIVE' ) {
|
|
2467
|
-
body += '1'
|
|
2468
|
-
} else {
|
|
2469
|
-
body += '2'
|
|
2470
|
-
}
|
|
2471
|
-
// 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)'
|
|
2472
2786
|
} else {
|
|
2473
|
-
|
|
2474
|
-
if ( airings_data.data && airings_data.data.Airings && (airings_data.data.Airings.length > 0) ) {
|
|
2475
|
-
for (var y = 0; y < airings_data.data.Airings.length; y++) {
|
|
2476
|
-
if ( airings_data.data.Airings[y].contentId == cache_data.dates[0].games[j].content.media.epg[k].items[x].contentId ) {
|
|
2477
|
-
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)) ) {
|
|
2478
|
-
body += '2'
|
|
2479
|
-
} else {
|
|
2480
|
-
body += '1'
|
|
2481
|
-
}
|
|
2482
|
-
break
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
}*/
|
|
2787
|
+
resumeStreamSuffix = '(2)'
|
|
2486
2788
|
}
|
|
2487
|
-
body += ')'
|
|
2488
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
|
+
}
|
|
2489
2837
|
}
|
|
2490
2838
|
// add YouTube link where available
|
|
2491
|
-
/*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 ) {
|
|
2492
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>'
|
|
2493
2841
|
}*/
|
|
2494
2842
|
} else {
|
|
2843
|
+
let inactiveStation = station
|
|
2844
|
+
|
|
2495
2845
|
if ( blackouts[gamePk] && blackouts[gamePk].blackout_feeds && blackouts[gamePk].blackout_feeds.includes(broadcast.mediaId) ) {
|
|
2496
|
-
|
|
2846
|
+
inactiveStation = '<span class="streamAction streamActionInactive blackoutstation">' + station + '</span>' + prePostIndicator
|
|
2847
|
+
backgroundCSS = 'backgroundBlackout'
|
|
2497
2848
|
} else {
|
|
2498
|
-
|
|
2849
|
+
inactiveStation = '<span class="streamAction streamActionInactive">' + station + '</span>' + prePostIndicator
|
|
2499
2850
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
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
|
+
}
|
|
2503
2865
|
}
|
|
2504
2866
|
}
|
|
2505
2867
|
}
|
|
2506
|
-
if ( body.substr(-2) == ', ' ) {
|
|
2507
|
-
body = body.slice(0, -2)
|
|
2508
|
-
}
|
|
2509
|
-
//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 ) {
|
|
2510
|
-
if ( (mediaType == 'MLBTV') && (game_started) ) {
|
|
2511
|
-
body += '<br/><a' + fav_style + ' href="#" onclick="showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\'); return false;">Highlights</a>'
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
body += "</td>"
|
|
2515
|
-
body += "</tr>" + "\n"
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
body += "</table>" + "\n"
|
|
2520
|
-
|
|
2521
|
-
if ( (Object.keys(blackouts).length > 0) ) {
|
|
2522
|
-
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"
|
|
2523
|
-
if ( (Object.keys(pre_post_shows).length > 0) ) {
|
|
2524
|
-
body += '<br/>'
|
|
2525
|
-
}
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
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)) ) {
|
|
2529
|
-
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"
|
|
2530
|
-
if ( argv.free ) {
|
|
2531
|
-
body += '<br/>'
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
2868
|
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
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
|
+
}
|
|
2543
2904
|
|
|
2544
|
-
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
2545
|
-
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>: '
|
|
2546
|
-
body += '<button '
|
|
2547
|
-
if ( resolution == 'best' ) body += 'class="default" '
|
|
2548
|
-
body += 'onclick="resolution=\'best\';reload()">best</button> '
|
|
2549
|
-
for (var i = 0; i < VALID_RESOLUTIONS.length; i++) {
|
|
2550
|
-
body += '<button '
|
|
2551
|
-
if ( resolution == VALID_RESOLUTIONS[i] ) body += 'class="default" '
|
|
2552
|
-
body += 'onclick="resolution=\'' + VALID_RESOLUTIONS[i] + '\';reload()">' + VALID_RESOLUTIONS[i]
|
|
2553
|
-
if ( DISPLAY_BANDWIDTHS[i] != '' ) {
|
|
2554
|
-
body += '<br/><span class="tinytext">' + DISPLAY_BANDWIDTHS[i] + '</span>'
|
|
2555
2905
|
}
|
|
2556
|
-
body += '</button> '
|
|
2557
|
-
}
|
|
2558
|
-
body += '</p>' + "\n"
|
|
2559
|
-
|
|
2560
|
-
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>: '
|
|
2561
|
-
for (var i = 0; i < VALID_AUDIO_TRACKS.length; i++) {
|
|
2562
|
-
body += '<button '
|
|
2563
|
-
if ( audio_track == VALID_AUDIO_TRACKS[i] ) body += 'class="default" '
|
|
2564
|
-
body += 'onclick="audio_track=\'' + VALID_AUDIO_TRACKS[i] + '\';reload()">' + DISPLAY_AUDIO_TRACKS[i] + '</button> '
|
|
2565
|
-
}
|
|
2566
|
-
body += '</p>' + "\n"
|
|
2567
|
-
|
|
2568
|
-
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>: '
|
|
2569
|
-
for (var i = 0; i < VALID_CAPTIONS.length; i++) {
|
|
2570
|
-
body += '<button '
|
|
2571
|
-
if ( captions == VALID_CAPTIONS[i] ) body += 'class="default" '
|
|
2572
|
-
body += 'onclick="captions=\'' + VALID_CAPTIONS[i] + '\';reload()">' + VALID_CAPTIONS[i] + '</button> '
|
|
2573
2906
|
}
|
|
2574
|
-
|
|
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
|
+
|
|
2575
3005
|
|
|
2576
|
-
|
|
2577
|
-
for (var i = 0; i < VALID_SKIP.length; i++) {
|
|
2578
|
-
body += '<button '
|
|
2579
|
-
if ( skip == VALID_SKIP[i] ) body += 'class="default" '
|
|
2580
|
-
body += 'onclick="skip=\'' + VALID_SKIP[i] + '\';reload()">' + VALID_SKIP[i] + '</button> '
|
|
2581
|
-
}
|
|
2582
|
-
if ( skip != VALID_SKIP[0] ) {
|
|
2583
|
-
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"/>'
|
|
2584
|
-
}
|
|
2585
|
-
body += '</p>' + "\n"
|
|
3006
|
+
</div>`
|
|
2586
3007
|
}
|
|
3008
|
+
}
|
|
2587
3009
|
|
|
2588
|
-
|
|
2589
|
-
for (var i = 0; i < VALID_PAD.length; i++) {
|
|
2590
|
-
body += '<button '
|
|
2591
|
-
if ( pad == VALID_PAD[i] ) body += 'class="default" '
|
|
2592
|
-
body += 'onclick="pad=\'' + VALID_PAD[i] + '\';reload()">' + VALID_PAD[i] + '</button> '
|
|
2593
|
-
}
|
|
2594
|
-
body += '</p>' + "\n"
|
|
3010
|
+
body +=`</div>`
|
|
2595
3011
|
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
for (var i = 0; i < VALID_FORCE_VOD.length; i++) {
|
|
2599
|
-
body += '<button '
|
|
2600
|
-
if ( force_vod == VALID_FORCE_VOD[i] ) body += 'class="default" '
|
|
2601
|
-
body += 'onclick="force_vod=\'' + VALID_FORCE_VOD[i] + '\';reload()">' + VALID_FORCE_VOD[i] + '</button> '
|
|
2602
|
-
}
|
|
2603
|
-
body += '<span class="tinytext">(if client does not support seeking in live streams)</span></p>' + "\n"
|
|
2604
|
-
}
|
|
3012
|
+
body +=`<div class="section">`
|
|
3013
|
+
|
|
2605
3014
|
|
|
2606
3015
|
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
2607
|
-
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'
|
|
2608
3018
|
if ( ffmpeg_status ) body += 'ed'
|
|
2609
|
-
body += '</a
|
|
3019
|
+
body += '</a><a id="stopmultiview" class="multiviewAction" href="" onclick="stopmultiview(this);return false">Stop'
|
|
2610
3020
|
if ( !ffmpeg_status ) body += 'ped'
|
|
2611
|
-
body += '</a
|
|
2612
|
-
|
|
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
|
+
|
|
2613
3036
|
for (var i=1; i<=4; i++) {
|
|
2614
|
-
body += i + '
|
|
2615
|
-
body += '<input type="number" id="sync' + i + '" value="0.0" step=".1"
|
|
2616
|
-
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"
|
|
2617
3039
|
}
|
|
2618
|
-
body +=
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
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
|
+
`
|
|
2625
3113
|
|
|
2626
|
-
body +=
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
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">'
|
|
2634
3181
|
for (var i = 0; i < VALID_SCAN_MODES.length; i++) {
|
|
2635
3182
|
body += '<button '
|
|
2636
3183
|
if ( scan_mode == VALID_SCAN_MODES[i] ) body += 'class="default" '
|
|
2637
3184
|
body += 'onclick="scan_mode=\'' + VALID_SCAN_MODES[i] + '\';reload()">' + VALID_SCAN_MODES[i] + '</button> '
|
|
2638
3185
|
}
|
|
2639
|
-
body += ' <span class="
|
|
3186
|
+
body += ' <span class="channelNote">(ON plays sample for all stream requests)</span></div></div>' + "\n"
|
|
2640
3187
|
|
|
2641
3188
|
if ( !req.query.resolution ) {
|
|
2642
3189
|
resolution = 'best'
|
|
2643
3190
|
}
|
|
2644
3191
|
|
|
2645
|
-
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"
|
|
2646
3193
|
|
|
2647
3194
|
let include_teams = 'ath,atl'
|
|
2648
3195
|
if ( (session.credentials.fav_teams.length > 0) && (session.credentials.fav_teams[0].length > 0) ) {
|
|
2649
3196
|
include_teams = session.credentials.fav_teams.toString()
|
|
2650
3197
|
}
|
|
2651
|
-
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"
|
|
2652
3199
|
|
|
2653
|
-
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"
|
|
2654
3201
|
|
|
2655
|
-
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"
|
|
2656
3203
|
|
|
2657
|
-
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"
|
|
2658
3205
|
|
|
2659
3206
|
let exclude_teams = 'ath,atl'
|
|
2660
|
-
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"
|
|
2661
3208
|
|
|
2662
|
-
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"
|
|
2663
3210
|
|
|
2664
3211
|
if ( entitlements.includes('MASN_110') ) {
|
|
2665
|
-
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"
|
|
2666
3213
|
}
|
|
2667
3214
|
|
|
2668
3215
|
if ( entitlements.includes('MLBN') || entitlements.includes('EXECMLB') || entitlements.includes('MLBTVMLBNADOBEPASS') ) {
|
|
2669
|
-
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"
|
|
2670
3217
|
}
|
|
2671
3218
|
|
|
2672
3219
|
if ( entitlements.includes('SNLA_119') ) {
|
|
2673
|
-
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"
|
|
2674
3221
|
}
|
|
2675
3222
|
|
|
2676
3223
|
if ( entitlements.includes('SNY_121') ) {
|
|
2677
|
-
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"
|
|
2678
3225
|
}
|
|
2679
3226
|
|
|
2680
|
-
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"
|
|
2681
3228
|
|
|
2682
3229
|
let gamechanger_resolution = resolution
|
|
2683
3230
|
if ( gamechanger_resolution == VALID_RESOLUTIONS[0] ) {
|
|
2684
3231
|
gamechanger_resolution = 'best'
|
|
2685
3232
|
}
|
|
2686
|
-
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"
|
|
2687
3234
|
|
|
2688
|
-
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"
|
|
2689
3236
|
|
|
2690
|
-
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"
|
|
2691
3238
|
|
|
2692
3239
|
if ( argv.free ) {
|
|
2693
|
-
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"
|
|
2694
3241
|
}
|
|
2695
3242
|
|
|
2696
|
-
body += '<
|
|
2697
|
-
|
|
2698
|
-
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"
|
|
2699
3244
|
|
|
2700
|
-
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"
|
|
2701
3246
|
|
|
2702
|
-
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"
|
|
2703
3248
|
|
|
2704
|
-
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"
|
|
2705
3251
|
|
|
2706
|
-
body += '<
|
|
2707
|
-
body += '<p><span class="tooltip">Example links<span class="tooltiptext">Some examples how to generate predictable links.</span></span>:</p>' + "\n"
|
|
2708
|
-
body += '<p>' + "\n"
|
|
3252
|
+
body += '<div class="menuContainer"><div class="cardMenuHeader">Example links</div><div class="menuContent">'
|
|
2709
3253
|
let example_types = [ ['embed.html', 'Embed'], ['stream.m3u8', 'Stream'], ['chromecast.html', 'Chromecast'], ['kodi.strm', 'Kodi'] ]
|
|
2710
3254
|
|
|
2711
3255
|
let example_team = 'ath'
|
|
@@ -2743,10 +3287,19 @@ app.get('/', async function(req, res) {
|
|
|
2743
3287
|
}
|
|
2744
3288
|
}
|
|
2745
3289
|
}
|
|
2746
|
-
body += '</p>' + "\n"
|
|
2747
3290
|
|
|
2748
3291
|
include_teams = 'ath,atl'
|
|
2749
|
-
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
|
+
`
|
|
2750
3303
|
body += '<p>' + "\n"
|
|
2751
3304
|
let gamechanger_streamURL = server + '/gamechanger.m3u8?resolution=best' + content_protect_b
|
|
2752
3305
|
let gamechanger_types = ['in', 'ex']
|
|
@@ -2754,19 +3307,65 @@ app.get('/', async function(req, res) {
|
|
|
2754
3307
|
let example_streamURL = gamechanger_streamURL + '&' + gamechanger_types[i] + 'cludeTeams=' + include_teams
|
|
2755
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"
|
|
2756
3309
|
}
|
|
3310
|
+
body += '</p>' + "\n"
|
|
2757
3311
|
|
|
2758
|
-
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
|
+
`
|
|
2759
3324
|
|
|
2760
|
-
body +=
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
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"
|
|
2767
3365
|
|
|
2768
3366
|
// Print version
|
|
2769
3367
|
body += '<p class="tinytext">Version ' + version + ' (<a href="' + http_root + '/clearcache">clear session and cache</a>)</p>' + "\n"
|
|
3368
|
+
body += '</div></div></div>'
|
|
2770
3369
|
|
|
2771
3370
|
// Datepicker functions
|
|
2772
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"
|
|
@@ -2776,6 +3375,33 @@ app.get('/', async function(req, res) {
|
|
|
2776
3375
|
|
|
2777
3376
|
// Highlights modal functions
|
|
2778
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 += `
|
|
2779
3405
|
var modal = document.getElementById("myModal");
|
|
2780
3406
|
var highlightsModal = document.getElementById("highlights");
|
|
2781
3407
|
var span = document.getElementsByClassName("close")[0];
|
|
@@ -2807,7 +3433,7 @@ function parsehighlightsresponse(responsetext) {
|
|
|
2807
3433
|
}
|
|
2808
3434
|
modaltext += "</ul>";
|
|
2809
3435
|
highlightsModal.innerHTML = modaltext;
|
|
2810
|
-
|
|
3436
|
+
openModal();
|
|
2811
3437
|
} catch (e) {
|
|
2812
3438
|
alert("Error processing highlights: " + e.message)
|
|
2813
3439
|
}
|
|
@@ -2816,12 +3442,77 @@ function showhighlights(gamePk, gameDate) {
|
|
|
2816
3442
|
makeGETRequest("` + http_root + `/highlights?gamePk=" + gamePk + "&gameDate=" + gameDate + "` + content_protect_b + `", parsehighlightsresponse);
|
|
2817
3443
|
return false
|
|
2818
3444
|
}
|
|
2819
|
-
span.onclick =
|
|
2820
|
-
|
|
2821
|
-
}
|
|
2822
|
-
|
|
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
|
+
}
|
|
2823
3512
|
|
|
2824
|
-
|
|
3513
|
+
</script>`
|
|
3514
|
+
body +=`</div>`
|
|
3515
|
+
body += '<div id="snackbar">Not yet available. Come back later.</div>'
|
|
2825
3516
|
|
|
2826
3517
|
body += "</body></html>"
|
|
2827
3518
|
|
|
@@ -3331,6 +4022,7 @@ app.get('/highlights', async function(req, res) {
|
|
|
3331
4022
|
if ( req.query.gamePk && req.query.gameDate ) {
|
|
3332
4023
|
highlightsData = await session.getHighlightsData(req.query.gamePk, req.query.gameDate)
|
|
3333
4024
|
}
|
|
4025
|
+
res.writeHead(200, {'Content-Type': 'application/json; charset=utf-8'});
|
|
3334
4026
|
res.end(JSON.stringify(highlightsData))
|
|
3335
4027
|
} catch (e) {
|
|
3336
4028
|
session.log('highlights request error : ' + e.message)
|