mlbserver 2022.5.13 → 2022.5.26
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/README.md +1 -1
- package/index.js +77 -59
- package/package.json +1 -1
package/README.md
CHANGED
package/index.js
CHANGED
|
@@ -38,6 +38,7 @@ const DEFAULT_MULTIVIEW_AUDIO_TRACK = 'English'
|
|
|
38
38
|
const VALID_SKIP = [ 'off', 'breaks', 'idle time', 'pitches' ]
|
|
39
39
|
const VALID_PAD = [ 'off', 'on' ]
|
|
40
40
|
const VALID_FORCE_VOD = [ 'off', 'on' ]
|
|
41
|
+
const VALID_SCAN_MODES = [ 'off', 'on' ]
|
|
41
42
|
|
|
42
43
|
const SAMPLE_STREAM_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
|
|
43
44
|
|
|
@@ -166,10 +167,10 @@ app.get('/stream.m3u8', async function(req, res) {
|
|
|
166
167
|
let streamURL
|
|
167
168
|
let options = {}
|
|
168
169
|
let urlArray = req.url.split('?')
|
|
169
|
-
if ( (urlArray.length == 1) || ((session.data.scan_mode ==
|
|
170
|
+
if ( (urlArray.length == 1) || ((session.data.scan_mode == VALID_SCAN_MODES[1]) && req.query.team) || (!req.query.team && !req.query.src && !req.query.highlight_src && !req.query.event && !req.query.id && !req.query.mediaId && !req.query.contentId) ) {
|
|
170
171
|
// load a sample encrypted HLS stream
|
|
171
172
|
session.log('loading sample stream')
|
|
172
|
-
options.resolution =
|
|
173
|
+
options.resolution = VALID_RESOLUTIONS[0]
|
|
173
174
|
streamURL = SAMPLE_STREAM_URL
|
|
174
175
|
options.referer = 'https://hls-js.netlify.app/'
|
|
175
176
|
} else {
|
|
@@ -231,7 +232,7 @@ app.get('/stream.m3u8', async function(req, res) {
|
|
|
231
232
|
session.debuglog('using streamURL : ' + streamURL)
|
|
232
233
|
|
|
233
234
|
if ( streamURL.indexOf('master_radio_') > 0 ) {
|
|
234
|
-
options.resolution =
|
|
235
|
+
options.resolution = VALID_RESOLUTIONS[0]
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
if ( req.query.audio_url && (req.query.audio_url != '') ) {
|
|
@@ -384,7 +385,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
384
385
|
var video_track_matched = false
|
|
385
386
|
var audio_track_matched = false
|
|
386
387
|
var frame_rate = '29.97'
|
|
387
|
-
if ( (resolution !=
|
|
388
|
+
if ( (resolution != VALID_RESOLUTIONS[0]) && (resolution != VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1]) ) {
|
|
388
389
|
if ( resolution.endsWith('p60') ) {
|
|
389
390
|
frame_rate = '59.94'
|
|
390
391
|
resolution = resolution.slice(0, -3)
|
|
@@ -419,7 +420,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
// Omit captions track when no video is specified
|
|
422
|
-
if ( (resolution ==
|
|
423
|
+
if ( (resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1]) && (line.indexOf('#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,') === 0) ) {
|
|
423
424
|
return
|
|
424
425
|
}
|
|
425
426
|
|
|
@@ -430,13 +431,13 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
430
431
|
audio_track_matched = true
|
|
431
432
|
return '#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="Alternate Audio",AUTOSELECT=YES,DEFAULT=YES,URI="' + audio_url + content_protect + referer_parameter + '"'
|
|
432
433
|
}
|
|
433
|
-
if ( audio_track ==
|
|
434
|
-
if ( (resolution ==
|
|
435
|
-
if ( (audio_track !=
|
|
434
|
+
if ( audio_track == VALID_AUDIO_TRACKS[VALID_AUDIO_TRACKS.length-1]) return
|
|
435
|
+
if ( (resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1]) && (line.indexOf(',URI=') < 0) ) return
|
|
436
|
+
if ( (audio_track != VALID_AUDIO_TRACKS[0]) && ((line.indexOf('NAME="'+audio_track+'"') > 0) || (line.indexOf('NAME="'+audio_track.substring(0,audio_track.length-1)+'"') > 0)) ) {
|
|
436
437
|
audio_track_matched = true
|
|
437
438
|
line = line.replace('AUTOSELECT=NO','AUTOSELECT=YES')
|
|
438
439
|
if ( line.indexOf(',DEFAULT=YES') < 0 ) line = line.replace('AUTOSELECT=YES','AUTOSELECT=YES,DEFAULT=YES')
|
|
439
|
-
} else if ( (audio_track !=
|
|
440
|
+
} else if ( (audio_track != VALID_AUDIO_TRACKS[0]) && ((line.indexOf('NAME="'+audio_track+'"') === -1) || (line.indexOf('NAME="'+audio_track.substring(0,audio_track.length-1)+'"') === -1)) ) {
|
|
440
441
|
return
|
|
441
442
|
}
|
|
442
443
|
if (line.indexOf(',URI=') > 0) {
|
|
@@ -452,7 +453,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
452
453
|
if ( pad != VALID_PAD[0] ) newurl += '&pad=' + pad
|
|
453
454
|
if ( contentId ) newurl += '&contentId=' + contentId
|
|
454
455
|
newurl += content_protect + referer_parameter
|
|
455
|
-
if ( resolution ==
|
|
456
|
+
if ( resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1] ) {
|
|
456
457
|
audio_track_matched = true
|
|
457
458
|
return line.replace(parsed[0],'') + "\n" + '#EXT-X-STREAM-INF:BANDWIDTH=50000,CODECS="mp4a.40.2",AUDIO="aac"' + "\n" + newurl
|
|
458
459
|
}
|
|
@@ -464,10 +465,10 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
464
465
|
|
|
465
466
|
// Parse video tracks to only include matching one, if specified
|
|
466
467
|
if (line.indexOf('#EXT-X-STREAM-INF:BANDWIDTH=') === 0) {
|
|
467
|
-
if ( resolution ==
|
|
468
|
+
if ( resolution == VALID_RESOLUTIONS[VALID_RESOLUTIONS.length-1] ) {
|
|
468
469
|
return
|
|
469
470
|
} else {
|
|
470
|
-
if ( resolution ===
|
|
471
|
+
if ( resolution === VALID_RESOLUTIONS[0] ) {
|
|
471
472
|
return line
|
|
472
473
|
} else {
|
|
473
474
|
if (line.indexOf(resolution+',FRAME-RATE='+frame_rate) > 0) {
|
|
@@ -489,7 +490,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
489
490
|
return line
|
|
490
491
|
}
|
|
491
492
|
|
|
492
|
-
if ( (resolution ===
|
|
493
|
+
if ( (resolution === VALID_RESOLUTIONS[0]) || (video_track_matched) ) {
|
|
493
494
|
video_track_matched = false
|
|
494
495
|
newurl = encodeURIComponent(url.resolve(streamURL, line.trim()))
|
|
495
496
|
if ( force_vod != VALID_FORCE_VOD[0] ) newurl += '&force_vod=on'
|
|
@@ -667,11 +668,17 @@ app.get('/playlist', async function(req, res) {
|
|
|
667
668
|
if ( body_array[last_segment_index] == '#EXT-X-ENDLIST' ) {
|
|
668
669
|
session.debuglog('padding archive stream with extra segments')
|
|
669
670
|
last_segment_index--
|
|
670
|
-
|
|
671
|
+
while ( !body_array[last_segment_index].startsWith('#EXTINF:5') ) {
|
|
672
|
+
last_segment_index--
|
|
673
|
+
}
|
|
674
|
+
last_segment_inf = body_array[last_segment_index]
|
|
675
|
+
last_segment_ts = body_array[last_segment_index+1]
|
|
676
|
+
let pad_lines = '#EXT-X-DISCONTINUITY' + '\n' + last_segment_inf + '\n' + last_segment_ts + '\n'
|
|
671
677
|
session.debuglog(pad_lines)
|
|
672
678
|
for (i=0; i<pad; i++) {
|
|
673
679
|
body += pad_lines
|
|
674
680
|
}
|
|
681
|
+
body += '#EXT-X-ENDLIST' + '\n'
|
|
675
682
|
}
|
|
676
683
|
}
|
|
677
684
|
if ( force_vod != VALID_FORCE_VOD[0] ) body += '#EXT-X-ENDLIST' + '\n'
|
|
@@ -860,11 +867,13 @@ app.get('/', async function(req, res) {
|
|
|
860
867
|
session.setScanMode(req.query.scan_mode)
|
|
861
868
|
}
|
|
862
869
|
|
|
870
|
+
var content_protect = ''
|
|
863
871
|
var content_protect_a = ''
|
|
864
872
|
var content_protect_b = ''
|
|
865
873
|
if ( session.protection.content_protect ) {
|
|
866
|
-
|
|
867
|
-
|
|
874
|
+
content_protect = session.protection.content_protect
|
|
875
|
+
content_protect_a = '?content_protect=' + content_protect
|
|
876
|
+
content_protect_b = '&content_protect=' + content_protect
|
|
868
877
|
}
|
|
869
878
|
|
|
870
879
|
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 + '"><style type="text/css">input[type=text],input[type=button]{-webkit-appearance:none;-webkit-border-radius:0}body{width:480px;color:lightgray;background-color:black;font-family:Arial,Helvetica,sans-serif;-webkit-text-size-adjust:none}a{color:darkgray}button{color:lightgray;background-color:black}button.default{color:black;background-color:lightgray}table{width:100%;pad}table,th,td{border:1px solid darkgray;border-collapse:collapse}th,td{padding:5px}.tinytext,textarea,input[type="number"]{font-size:.8em}textarea{width:380px}'
|
|
@@ -879,13 +888,13 @@ app.get('/', async function(req, res) {
|
|
|
879
888
|
body += '</style><script type="text/javascript">' + "\n";
|
|
880
889
|
|
|
881
890
|
// Define option variables in page
|
|
882
|
-
body += 'var date="' + gameDate + '";var mediaType="' + mediaType + '";var resolution="' + resolution + '";var audio_track="' + audio_track + '";var force_vod="' + force_vod + '";var inning_half="' + inning_half + '";var inning_number="' + inning_number + '";var skip="' + skip + '";var pad="' + pad + '";var linkType="' + linkType + '";var startFrom="' + startFrom + '";var scores="' + scores + '";var controls="' + controls + '";var scan_mode="' + scan_mode + '";' + "\n"
|
|
891
|
+
body += 'var date="' + gameDate + '";var mediaType="' + mediaType + '";var resolution="' + resolution + '";var audio_track="' + audio_track + '";var force_vod="' + force_vod + '";var inning_half="' + inning_half + '";var inning_number="' + inning_number + '";var skip="' + skip + '";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"
|
|
883
892
|
// audio_url is disabled here, now used in multiview instead
|
|
884
893
|
//body += 'var audio_url="' + audio_url + '";' + "\n"
|
|
885
894
|
|
|
886
895
|
// Reload function, called after options change
|
|
887
896
|
// audio_url is disabled here, now used in multiview instead
|
|
888
|
-
body += 'var defaultDate="' + session.liveDate() + '";var curDate=new Date();var utcHours=curDate.getUTCHours();if ((utcHours >= ' + todayUTCHours + ') && (utcHours < ' + YESTERDAY_UTC_HOURS + ')){defaultDate="' + session.yesterdayDate() + '"}function reload(){var newurl="/?";if (date != defaultDate){var urldate=date;if (date == "' + session.liveDate() + '"){urldate="today"}else if (date == "' + session.yesterdayDate() + '"){urldate="yesterday"}newurl+="date="+urldate+"&"}if (mediaType != "' + VALID_MEDIA_TYPES[0] + '"){newurl+="mediaType="+mediaType+"&"}if (mediaType=="Video"){if (resolution != "' + VALID_RESOLUTIONS[0] + '"){newurl+="resolution="+resolution+"&"}if (audio_track != "' + VALID_AUDIO_TRACKS[0] + '"){newurl+="audio_track="+encodeURIComponent(audio_track)+"&"}else if (resolution == "none"){newurl+="audio_track="+encodeURIComponent("' + VALID_AUDIO_TRACKS[2] + '")+"&"}/*if (audio_url != ""){newurl+="audio_url="+encodeURIComponent(audio_url)+"&"}*/if (inning_half != "' + VALID_INNING_HALF[0] + '"){newurl+="inning_half="+inning_half+"&"}if (inning_number != "' + VALID_INNING_NUMBER[0] + '"){newurl+="inning_number="+inning_number+"&"}if (skip != "' + VALID_SKIP[0] + '"){newurl+="skip="+skip+"&";}}if (pad != "' + VALID_PAD[0] + '"){newurl+="pad="+pad+"&";}if (linkType != "' + VALID_LINK_TYPES[0] + '"){newurl+="linkType="+linkType+"&"}if (linkType=="
|
|
897
|
+
body += 'var defaultDate="' + session.liveDate() + '";var curDate=new Date();var utcHours=curDate.getUTCHours();if ((utcHours >= ' + todayUTCHours + ') && (utcHours < ' + YESTERDAY_UTC_HOURS + ')){defaultDate="' + session.yesterdayDate() + '"}function reload(){var newurl="/?";if (date != defaultDate){var urldate=date;if (date == "' + session.liveDate() + '"){urldate="today"}else if (date == "' + session.yesterdayDate() + '"){urldate="yesterday"}newurl+="date="+urldate+"&"}if (mediaType != "' + VALID_MEDIA_TYPES[0] + '"){newurl+="mediaType="+mediaType+"&"}if (mediaType=="Video"){if (resolution != "' + VALID_RESOLUTIONS[0] + '"){newurl+="resolution="+resolution+"&"}if (audio_track != "' + VALID_AUDIO_TRACKS[0] + '"){newurl+="audio_track="+encodeURIComponent(audio_track)+"&"}else if (resolution == "none"){newurl+="audio_track="+encodeURIComponent("' + VALID_AUDIO_TRACKS[2] + '")+"&"}/*if (audio_url != ""){newurl+="audio_url="+encodeURIComponent(audio_url)+"&"}*/if (inning_half != "' + VALID_INNING_HALF[0] + '"){newurl+="inning_half="+inning_half+"&"}if (inning_number != "' + VALID_INNING_NUMBER[0] + '"){newurl+="inning_number="+inning_number+"&"}if (skip != "' + VALID_SKIP[0] + '"){newurl+="skip="+skip+"&";}}if (pad != "' + VALID_PAD[0] + '"){newurl+="pad="+pad+"&";}if (linkType != "' + VALID_LINK_TYPES[0] + '"){newurl+="linkType="+linkType+"&"}if (linkType=="' + VALID_LINK_TYPES[0] + '"){if (startFrom != "' + VALID_START_FROM[0] + '"){newurl+="startFrom="+startFrom+"&"}if (controls != "' + VALID_CONTROLS[0] + '"){newurl+="controls="+controls+"&"}}if (linkType=="Stream"){if (force_vod != "' + VALID_FORCE_VOD[0] + '"){newurl+="force_vod="+force_vod+"&"}}if (scores != "' + VALID_SCORES[0] + '"){newurl+="scores="+scores+"&"}if (scan_mode != "' + session.data.scan_mode + '"){newurl+="scan_mode="+scan_mode+"&"}if (content_protect != ""){newurl+="content_protect="+content_protect+"&"}window.location=newurl.substring(0,newurl.length-1)}' + "\n"
|
|
889
898
|
|
|
890
899
|
// Ajax function for multiview and highlights
|
|
891
900
|
body += 'function makeGETRequest(url, callback){var request=new XMLHttpRequest();request.onreadystatechange=function(){if (request.readyState==4 && request.status==200){callback(request.responseText)}};request.open("GET", url);request.send();}' + "\n"
|
|
@@ -907,7 +916,7 @@ app.get('/', async function(req, res) {
|
|
|
907
916
|
body += '<p><span class="tooltip">Date<span class="tooltiptext">"today" lasts until ' + todayUTCHours + ' AM EST. Home page will default to yesterday between ' + todayUTCHours + ' AM - ' + (YESTERDAY_UTC_HOURS - 4) + ' AM EST.</span></span>: <input type="date" id="gameDate" value="' + gameDate + '"/> '
|
|
908
917
|
for (var i = 0; i < VALID_DATES.length; i++) {
|
|
909
918
|
body += '<button '
|
|
910
|
-
if ( ((VALID_DATES[i] ==
|
|
919
|
+
if ( ((VALID_DATES[i] == VALID_DATES[0]) && (gameDate == session.liveDate())) || ((VALID_DATES[i] == VALID_DATES[1]) && (gameDate == session.yesterdayDate())) ) body += 'class="default" '
|
|
911
920
|
body += 'onclick="date=\'' + VALID_DATES[i] + '\';reload()">' + VALID_DATES[i] + '</button> '
|
|
912
921
|
}
|
|
913
922
|
body += '</p>' + "\n" + '<p><span class="tinytext">Updated ' + session.getCacheUpdatedDate(gameDate) + '</span></p>' + "\n"
|
|
@@ -929,8 +938,8 @@ app.get('/', async function(req, res) {
|
|
|
929
938
|
body += '</p>' + "\n"
|
|
930
939
|
|
|
931
940
|
body += '<p>'
|
|
932
|
-
if ( linkType ==
|
|
933
|
-
body += '<
|
|
941
|
+
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
942
|
+
body += '<span class="tooltip">Video Controls<span class="tooltiptext">Choose whether to show or hide controls on the embedded video page. Helpful to avoid timeline spoilers.</span></span>: '
|
|
934
943
|
for (var i = 0; i < VALID_CONTROLS.length; i++) {
|
|
935
944
|
body += '<button '
|
|
936
945
|
if ( controls == VALID_CONTROLS[i] ) body += 'class="default" '
|
|
@@ -945,12 +954,12 @@ app.get('/', async function(req, res) {
|
|
|
945
954
|
body += 'onclick="startFrom=\'' + VALID_START_FROM[i] + '\';reload()">' + VALID_START_FROM[i] + '</button> '
|
|
946
955
|
}
|
|
947
956
|
body += "\n"
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if ( mediaType == 'Video' ) {
|
|
951
|
-
if ( linkType == 'Embed' ) {
|
|
957
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
952
958
|
body += 'or '
|
|
953
959
|
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
954
963
|
body += '<span class="tooltip">Inning<span class="tooltiptext">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. Inning 0 (zero) should be the broadcast start time, if available. Default is the beginning of the stream. To use with radio, set the video track to "None".</span></span>: '
|
|
955
964
|
body += '<select id="inning_half" onchange="inning_half=this.value;reload()">'
|
|
956
965
|
for (var i = 0; i < VALID_INNING_HALF.length; i++) {
|
|
@@ -986,21 +995,21 @@ app.get('/', async function(req, res) {
|
|
|
986
995
|
// Rename some parameters before display links
|
|
987
996
|
var mediaFeedType = 'mediaFeedType'
|
|
988
997
|
var language = 'en'
|
|
989
|
-
if ( mediaType ==
|
|
998
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
990
999
|
mediaType = 'MLBTV'
|
|
991
|
-
} else if ( mediaType ==
|
|
992
|
-
mediaType =
|
|
1000
|
+
} else if ( mediaType == VALID_MEDIA_TYPES[2] ) {
|
|
1001
|
+
mediaType = VALID_MEDIA_TYPES[1]
|
|
993
1002
|
language = 'es'
|
|
994
1003
|
}
|
|
995
|
-
if ( mediaType ==
|
|
1004
|
+
if ( mediaType == VALID_MEDIA_TYPES[1] ) {
|
|
996
1005
|
mediaFeedType = 'type'
|
|
997
1006
|
}
|
|
998
1007
|
linkType = linkType.toLowerCase()
|
|
999
1008
|
let link = linkType + '.html'
|
|
1000
|
-
if ( linkType ==
|
|
1009
|
+
if ( linkType == VALID_LINK_TYPES[1].toLowerCase() ) {
|
|
1001
1010
|
link = linkType + '.m3u8'
|
|
1002
1011
|
} else {
|
|
1003
|
-
force_vod =
|
|
1012
|
+
force_vod = VALID_FORCE_VOD[0]
|
|
1004
1013
|
}
|
|
1005
1014
|
var thislink = '/' + link
|
|
1006
1015
|
|
|
@@ -1014,12 +1023,12 @@ app.get('/', async function(req, res) {
|
|
|
1014
1023
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
1015
1024
|
let querystring = '?event=biginning'
|
|
1016
1025
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1017
|
-
if ( linkType ==
|
|
1026
|
+
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1018
1027
|
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
1019
1028
|
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
1020
1029
|
}
|
|
1021
1030
|
if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
|
|
1022
|
-
if ( linkType ==
|
|
1031
|
+
if ( linkType == VALID_LINK_TYPES[1] ) {
|
|
1023
1032
|
if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
|
|
1024
1033
|
}
|
|
1025
1034
|
querystring += content_protect_b
|
|
@@ -1062,7 +1071,7 @@ app.get('/', async function(req, res) {
|
|
|
1062
1071
|
}
|
|
1063
1072
|
var relative_inning = (inning_number - (9 - scheduledInnings))
|
|
1064
1073
|
relative_inning = relative_inning < 0 ? 0 : relative_inning
|
|
1065
|
-
if ( (scores ==
|
|
1074
|
+
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 ) {
|
|
1066
1075
|
let awayscore = ''
|
|
1067
1076
|
let homescore = ''
|
|
1068
1077
|
if ( (inning_number != VALID_INNING_NUMBER[0]) && cache_data.dates[0].games[j].linescore && cache_data.dates[0].games[j].linescore.innings ) {
|
|
@@ -1244,7 +1253,7 @@ app.get('/', async function(req, res) {
|
|
|
1244
1253
|
let querystring
|
|
1245
1254
|
querystring = '?mediaId=' + mediaId
|
|
1246
1255
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
|
|
1247
|
-
if ( linkType ==
|
|
1256
|
+
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
1248
1257
|
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
1249
1258
|
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
1250
1259
|
}
|
|
@@ -1262,7 +1271,7 @@ app.get('/', async function(req, res) {
|
|
|
1262
1271
|
//if ( audio_url != '' ) querystring += '&audio_url=' + encodeURIComponent(audio_url)
|
|
1263
1272
|
}
|
|
1264
1273
|
if ( pad != VALID_PAD[0] ) querystring += '&pad=' + pad
|
|
1265
|
-
if ( linkType ==
|
|
1274
|
+
if ( linkType == VALID_LINK_TYPES[1] ) {
|
|
1266
1275
|
if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
|
|
1267
1276
|
if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
|
|
1268
1277
|
}
|
|
@@ -1333,10 +1342,10 @@ app.get('/', async function(req, res) {
|
|
|
1333
1342
|
|
|
1334
1343
|
// Rename parameter back before displaying further links
|
|
1335
1344
|
if ( mediaType == 'MLBTV' ) {
|
|
1336
|
-
mediaType =
|
|
1345
|
+
mediaType = VALID_MEDIA_TYPES[0]
|
|
1337
1346
|
}
|
|
1338
1347
|
|
|
1339
|
-
if ( mediaType ==
|
|
1348
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1340
1349
|
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. 720p60 is the best quality. 540p 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>: '
|
|
1341
1350
|
for (var i = 0; i < VALID_RESOLUTIONS.length; i++) {
|
|
1342
1351
|
body += '<button '
|
|
@@ -1376,8 +1385,8 @@ app.get('/', async function(req, res) {
|
|
|
1376
1385
|
}
|
|
1377
1386
|
body += '</p>' + "\n"
|
|
1378
1387
|
|
|
1379
|
-
if ( mediaType ==
|
|
1380
|
-
body += '<table><tr><td><table><tr><td>1</td><td>2</tr><tr><td>3</td><td>4</td></tr></table><td><span class="tooltip">Multiview / Alternate Audio / Sync<span class="tooltiptext">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. 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.<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.</span></span>: <a id="startmultiview" href="" onclick="startmultiview(this);return false">Start'
|
|
1388
|
+
if ( mediaType == VALID_MEDIA_TYPES[0] ) {
|
|
1389
|
+
body += '<table><tr><td><table><tr><td>1</td><td>2</tr><tr><td>3</td><td>4</td></tr></table><td><span class="tooltip">Multiview / Alternate Audio / Sync<span class="tooltiptext">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. 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.</span></span>: <a id="startmultiview" href="" onclick="startmultiview(this);return false">Start'
|
|
1381
1390
|
if ( ffmpeg_status ) body += 'ed'
|
|
1382
1391
|
body += '</a> | <a id="stopmultiview" href="" onclick="stopmultiview(this);return false">Stop'
|
|
1383
1392
|
if ( !ffmpeg_status ) body += 'ped'
|
|
@@ -1396,7 +1405,7 @@ app.get('/', async function(req, res) {
|
|
|
1396
1405
|
body += '</td></tr></table><br/>' + "\n"
|
|
1397
1406
|
}
|
|
1398
1407
|
|
|
1399
|
-
if ( (linkType ==
|
|
1408
|
+
if ( (linkType == VALID_LINK_TYPES[1]) && (gameDate == session.liveDate()) ) {
|
|
1400
1409
|
body += '<p><span class="tooltip">Force VOD<span class="tooltiptext">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.</span></span>: '
|
|
1401
1410
|
for (var i = 0; i < VALID_FORCE_VOD.length; i++) {
|
|
1402
1411
|
body += '<button '
|
|
@@ -1411,11 +1420,10 @@ app.get('/', async function(req, res) {
|
|
|
1411
1420
|
body += '<p><span class="tooltip">Live Channel Playlist and XMLTV Guide<span class="tooltiptext">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.<br/><br/>NOTE: May be helpful to specify a resolution above.</span></span>:</p>' + "\n"
|
|
1412
1421
|
|
|
1413
1422
|
body += '<p><span class="tooltip">Scan Mode<span class="tooltiptext">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 aren\'t currently live. Once the channels are set up, turning Scan Mode OFF will restore normal stream behavior.<br/><br/>WARNING: Be sure your TV/DVR/PVR software doesn\'t periodically scan all channels automatically or you might overload mlbserver.</span></span>: '
|
|
1414
|
-
|
|
1415
|
-
for (var i = 0; i < options.length; i++) {
|
|
1423
|
+
for (var i = 0; i < VALID_SCAN_MODES.length; i++) {
|
|
1416
1424
|
body += '<button '
|
|
1417
|
-
if ( scan_mode ==
|
|
1418
|
-
body += 'onclick="scan_mode=\'' +
|
|
1425
|
+
if ( scan_mode == VALID_SCAN_MODES[i] ) body += 'class="default" '
|
|
1426
|
+
body += 'onclick="scan_mode=\'' + VALID_SCAN_MODES[i] + '\';reload()">' + VALID_SCAN_MODES[i] + '</button> '
|
|
1419
1427
|
}
|
|
1420
1428
|
body += ' <span class="tinytext">(ON plays sample for all stream requests)</span></p>' + "\n"
|
|
1421
1429
|
|
|
@@ -1463,8 +1471,13 @@ app.get('/', async function(req, res) {
|
|
|
1463
1471
|
}
|
|
1464
1472
|
body += '</p></td></tr></table><br/>' + "\n"
|
|
1465
1473
|
|
|
1466
|
-
let
|
|
1467
|
-
|
|
1474
|
+
let local_url = '' // default to embedded player
|
|
1475
|
+
let urlArray = req.url.split('?')
|
|
1476
|
+
if ( (urlArray.length == 2) ) {
|
|
1477
|
+
local_url += '?' + urlArray[1]
|
|
1478
|
+
}
|
|
1479
|
+
let media_center_link = '/live-stream-games/' + gameDate.replace(/-/g,'/') + local_url
|
|
1480
|
+
body += '<p><span class="tooltip">Media Center View<span class="tooltiptext">Allows you to use the MLB Media Center page format for nagivation.</span></span>: <a href="' + media_center_link + '" target="_blank">Link</a></p>' + "\n"
|
|
1468
1481
|
|
|
1469
1482
|
body += '<p><span class="tooltip">Sample video<span class="tooltiptext">A sample stream. Useful for testing and troubleshooting.</span></span>: <a href="/embed.html' + content_protect_a + '">Embed</a> | <a href="/stream.m3u8' + content_protect_a + '">Stream</a> | <a href="/chromecast.html' + content_protect_a + '">Chromecast</a> | <a href="/advanced.html' + content_protect_a + '">Advanced</a></p>' + "\n"
|
|
1470
1483
|
|
|
@@ -1514,7 +1527,7 @@ app.get('/live-stream-games*', async function(req, res) {
|
|
|
1514
1527
|
session.debuglog('schedule request : ' + req.url)
|
|
1515
1528
|
|
|
1516
1529
|
// check for a linkType parameter in the url
|
|
1517
|
-
let linkType =
|
|
1530
|
+
let linkType = VALID_LINK_TYPES[0]
|
|
1518
1531
|
if ( req.query.linkType ) {
|
|
1519
1532
|
linkType = req.query.linkType
|
|
1520
1533
|
session.setLinkType(linkType)
|
|
@@ -1522,15 +1535,17 @@ app.get('/live-stream-games*', async function(req, res) {
|
|
|
1522
1535
|
|
|
1523
1536
|
// use the link type to determine the local url to use
|
|
1524
1537
|
var local_url = '/embed.html' // default to embedded player
|
|
1525
|
-
|
|
1526
|
-
if ( linkType == 'stream' ) { // direct stream
|
|
1538
|
+
if ( linkType == VALID_LINK_TYPES[1] ) { // direct stream
|
|
1527
1539
|
local_url = '/stream.m3u8'
|
|
1528
|
-
if ( session.protection.content_protect ) content_protect = '&content_protect=' + session.protection.content_protect
|
|
1529
1540
|
} else { // other
|
|
1530
1541
|
local_url = '/' + linkType + '.html'
|
|
1531
1542
|
}
|
|
1543
|
+
let urlArray = req.url.split('?')
|
|
1544
|
+
if ( (urlArray.length == 2) ) {
|
|
1545
|
+
local_url += '?' + urlArray[1]
|
|
1546
|
+
}
|
|
1532
1547
|
|
|
1533
|
-
// remove our
|
|
1548
|
+
// remove our local parameters, if specified, from the url we will fetch remotely
|
|
1534
1549
|
var remote_url = url.parse(req.url).pathname
|
|
1535
1550
|
|
|
1536
1551
|
let reqObj = {
|
|
@@ -1546,7 +1561,12 @@ app.get('/live-stream-games*', async function(req, res) {
|
|
|
1546
1561
|
var body = await session.httpGet(reqObj)
|
|
1547
1562
|
|
|
1548
1563
|
// a regex substitution to change existing links to local urls
|
|
1549
|
-
body = body.replace(/https:\/\/www.mlb.com\/tv\/g\d+\/[v]([a-zA-Z0-9-]+)/g,local_url+"
|
|
1564
|
+
body = body.replace(/https:\/\/www.mlb.com\/tv\/g\d+\/[v]([a-zA-Z0-9-]+)/g,local_url+"&contentId=$1")
|
|
1565
|
+
|
|
1566
|
+
// a regex substitution to remove unsupported filter menus
|
|
1567
|
+
if ( session.protection.content_protect ) {
|
|
1568
|
+
body = body.replace(/<div\n id="date-container"[\S\s]+><\/span>\n <\/div>/g,'')
|
|
1569
|
+
}
|
|
1550
1570
|
|
|
1551
1571
|
// hide popup to accept cookies
|
|
1552
1572
|
body = body.replace(/www.googletagmanager.com/g,'0.0.0.0')
|
|
@@ -1560,8 +1580,6 @@ app.get('/embed.html', async function(req, res) {
|
|
|
1560
1580
|
|
|
1561
1581
|
session.log('embed.html request : ' + req.url)
|
|
1562
1582
|
|
|
1563
|
-
delete req.headers.host
|
|
1564
|
-
|
|
1565
1583
|
let startFrom = VALID_START_FROM[0]
|
|
1566
1584
|
if ( req.query.startFrom ) {
|
|
1567
1585
|
startFrom = req.query.startFrom
|
|
@@ -1653,7 +1671,7 @@ app.get('/channels.m3u', async function(req, res) {
|
|
|
1653
1671
|
|
|
1654
1672
|
session.log('channels.m3u request : ' + req.url)
|
|
1655
1673
|
|
|
1656
|
-
let mediaType =
|
|
1674
|
+
let mediaType = VALID_MEDIA_TYPES[0]
|
|
1657
1675
|
if ( req.query.mediaType ) {
|
|
1658
1676
|
mediaType = req.query.mediaType
|
|
1659
1677
|
}
|
|
@@ -1696,7 +1714,7 @@ app.get('/guide.xml', async function(req, res) {
|
|
|
1696
1714
|
|
|
1697
1715
|
session.log('guide.xml request : ' + req.url)
|
|
1698
1716
|
|
|
1699
|
-
let mediaType =
|
|
1717
|
+
let mediaType = VALID_MEDIA_TYPES[0]
|
|
1700
1718
|
if ( req.query.mediaType ) {
|
|
1701
1719
|
mediaType = req.query.mediaType
|
|
1702
1720
|
}
|
|
@@ -1853,7 +1871,7 @@ function start_multiview_stream(streams, sync, dvr, faster, reencode, audio_url,
|
|
|
1853
1871
|
if ( stream_count > 1 ) {
|
|
1854
1872
|
complexFilter.push({
|
|
1855
1873
|
filter: 'setpts=PTS-STARTPTS',
|
|
1856
|
-
inputs: i+':v',
|
|
1874
|
+
inputs: i+':v:0',
|
|
1857
1875
|
outputs: 'v'+i
|
|
1858
1876
|
})
|
|
1859
1877
|
xstack_inputs.push('v'+i)
|
|
@@ -1904,7 +1922,7 @@ function start_multiview_stream(streams, sync, dvr, faster, reencode, audio_url,
|
|
|
1904
1922
|
// Filters: resampling preserve timestamps and padding allows the multiview stream to continue if one stream ends
|
|
1905
1923
|
audio_reencoded = []
|
|
1906
1924
|
for (var i=0; i<audio_present.length; i++) {
|
|
1907
|
-
let audio_input = audio_present[i] + ':
|
|
1925
|
+
let audio_input = audio_present[i] + ':m:language:en?'
|
|
1908
1926
|
let filter = ''
|
|
1909
1927
|
// Optionally apply sync adjustments
|
|
1910
1928
|
if ( sync[audio_present[i]] ) {
|