mlbserver 2022.3.27 → 2022.5.9
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 +113 -91
- package/package.json +1 -1
- package/session.js +120 -134
package/README.md
CHANGED
package/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const YESTERDAY_UTC_HOURS = 14 // UTC hours (EST + 4) to change home page defaul
|
|
|
25
25
|
const VALID_MEDIA_TYPES = [ 'Video', 'Audio', 'Spanish' ]
|
|
26
26
|
const VALID_LINK_TYPES = [ 'Embed', 'Stream', 'Chromecast', 'Advanced' ]
|
|
27
27
|
const VALID_START_FROM = [ 'Beginning', 'Live' ]
|
|
28
|
+
const VALID_CONTROLS = [ 'Show', 'Hide' ]
|
|
28
29
|
const VALID_INNING_HALF = [ '', 'top', 'bottom' ]
|
|
29
30
|
const VALID_INNING_NUMBER = [ '', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12' ]
|
|
30
31
|
const VALID_SCORES = [ 'Hide', 'Show' ]
|
|
@@ -34,7 +35,8 @@ const DEFAULT_MULTIVIEW_RESOLUTION = '540p'
|
|
|
34
35
|
const VALID_BANDWIDTHS = [ '', '6600k', '4160k', '2950k', '2120k', '1400k', '' ]
|
|
35
36
|
const VALID_AUDIO_TRACKS = [ 'all', 'English', 'English Radio', 'Radio Española', 'none' ]
|
|
36
37
|
const DEFAULT_MULTIVIEW_AUDIO_TRACK = 'English'
|
|
37
|
-
const VALID_SKIP = [ 'off', 'breaks', 'pitches' ]
|
|
38
|
+
const VALID_SKIP = [ 'off', 'breaks', 'idle time', 'pitches' ]
|
|
39
|
+
const VALID_PAD = [ 'off', 'on' ]
|
|
38
40
|
const VALID_FORCE_VOD = [ 'off', 'on' ]
|
|
39
41
|
|
|
40
42
|
const SAMPLE_STREAM_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
|
|
@@ -133,7 +135,7 @@ session.setPorts(port, multiview_port)
|
|
|
133
135
|
app.listen(port, function(addr) {
|
|
134
136
|
session.log(appname + ' started at http://' + addr)
|
|
135
137
|
session.debuglog('multiview port ' + multiview_port)
|
|
136
|
-
session.
|
|
138
|
+
session.debuglog('multiview server started at http://' + addr.replace(':' + port, ':' + multiview_port) + multiview_url_path)
|
|
137
139
|
session.clear_multiview_files()
|
|
138
140
|
})
|
|
139
141
|
var multiview_app = http.createServer()
|
|
@@ -179,6 +181,11 @@ app.get('/stream.m3u8', async function(req, res) {
|
|
|
179
181
|
options.inning_half = req.query.inning_half || VALID_INNING_HALF[0]
|
|
180
182
|
options.inning_number = req.query.inning_number || VALID_INNING_NUMBER[0]
|
|
181
183
|
options.skip = req.query.skip || VALID_SKIP[0]
|
|
184
|
+
options.pad = req.query.pad || VALID_PAD[0]
|
|
185
|
+
if ( options.pad != VALID_PAD[0] ) {
|
|
186
|
+
// if pad is selected, pick a random number of times to repeat the last segment
|
|
187
|
+
options.pad = Math.floor(Math.random() * 1440) + 720
|
|
188
|
+
}
|
|
182
189
|
|
|
183
190
|
if ( req.query.src ) {
|
|
184
191
|
streamURL = req.query.src
|
|
@@ -231,15 +238,8 @@ app.get('/stream.m3u8', async function(req, res) {
|
|
|
231
238
|
if ( contentId ) {
|
|
232
239
|
options.contentId = contentId
|
|
233
240
|
|
|
234
|
-
let
|
|
235
|
-
|
|
236
|
-
if ( (options.inning_half != VALID_INNING_HALF[0]) || (options.inning_number != VALID_INNING_NUMBER[0]) ) {
|
|
237
|
-
skip_types.push('innings')
|
|
238
|
-
}
|
|
239
|
-
if ( options.skip != VALID_SKIP[0] ) {
|
|
240
|
-
skip_types.push(options.skip)
|
|
241
|
-
}
|
|
242
|
-
await session.getEventOffsets(contentId, skip_types, skip_adjust)
|
|
241
|
+
let skip_type = VALID_SKIP.indexOf(options.skip)
|
|
242
|
+
await session.getSkipMarkers(contentId, skip_type, options.inning_number, options.inning_half)
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -364,6 +364,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
364
364
|
let inning_half = options.inning_half || VALID_INNING_HALF[0]
|
|
365
365
|
let inning_number = options.inning_number || VALID_INNING_NUMBER[0]
|
|
366
366
|
let skip = options.skip || VALID_SKIP[0]
|
|
367
|
+
let pad = options.pad || VALID_PAD[0]
|
|
367
368
|
let contentId = options.contentId || false
|
|
368
369
|
|
|
369
370
|
if ( (inning_number > 0) && (inning_half == VALID_INNING_HALF[0]) ) {
|
|
@@ -444,6 +445,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
444
445
|
if ( inning_half != VALID_INNING_HALF[0] ) newurl += '&inning_half=' + inning_half
|
|
445
446
|
if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
|
|
446
447
|
if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
|
|
448
|
+
if ( pad != VALID_PAD[0] ) newurl += '&pad=' + pad
|
|
447
449
|
if ( contentId ) newurl += '&contentId=' + contentId
|
|
448
450
|
newurl += content_protect + referer_parameter
|
|
449
451
|
if ( resolution == 'none' ) {
|
|
@@ -490,6 +492,7 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
490
492
|
if ( inning_half != VALID_INNING_HALF[0] ) newurl += '&inning_half=' + inning_half
|
|
491
493
|
if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
|
|
492
494
|
if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
|
|
495
|
+
if ( pad != VALID_PAD[0] ) newurl += '&pad=' + pad
|
|
493
496
|
if ( contentId ) newurl += '&contentId=' + contentId
|
|
494
497
|
newurl += content_protect + referer_parameter
|
|
495
498
|
return 'playlist?url='+newurl
|
|
@@ -537,6 +540,7 @@ app.get('/playlist', async function(req, res) {
|
|
|
537
540
|
var inning_half = req.query.inning_half || VALID_INNING_HALF[0]
|
|
538
541
|
var inning_number = req.query.inning_number || VALID_INNING_NUMBER[0]
|
|
539
542
|
var skip = req.query.skip || VALID_SKIP[0]
|
|
543
|
+
var pad = req.query.pad || VALID_PAD[0]
|
|
540
544
|
var contentId = req.query.contentId || false
|
|
541
545
|
|
|
542
546
|
var req = function () {
|
|
@@ -574,26 +578,22 @@ app.get('/playlist', async function(req, res) {
|
|
|
574
578
|
|
|
575
579
|
var key
|
|
576
580
|
var iv
|
|
581
|
+
var skip_markers
|
|
582
|
+
var skip_marker_index = 0
|
|
583
|
+
var time_counter = 0.0
|
|
584
|
+
var skip_next = false
|
|
585
|
+
var discontinuity = false
|
|
577
586
|
|
|
578
587
|
var content_protect = ''
|
|
579
588
|
if ( session.protection.content_protect ) {
|
|
580
589
|
content_protect = '&content_protect=' + session.protection.content_protect
|
|
581
590
|
}
|
|
582
591
|
|
|
583
|
-
if ( (contentId) && ((inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0]))) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
skip = 'off'
|
|
589
|
-
} else {
|
|
590
|
-
var time_counter = 0.0
|
|
591
|
-
var skip_index = 1
|
|
592
|
-
var skip_next = false
|
|
593
|
-
var discontinuity = false
|
|
594
|
-
|
|
595
|
-
var offsets = session.temp_cache[contentId].event_offsets
|
|
596
|
-
}
|
|
592
|
+
if ( (contentId) && ((inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0])) && (typeof session.temp_cache[contentId] !== 'undefined') && (typeof session.temp_cache[contentId].skip_markers !== 'undefined') ) {
|
|
593
|
+
session.debuglog('pulling skip markers from temporary cache')
|
|
594
|
+
skip_markers = session.temp_cache[contentId].skip_markers
|
|
595
|
+
} else {
|
|
596
|
+
session.debuglog('not using skip markers from temporary cache')
|
|
597
597
|
}
|
|
598
598
|
|
|
599
599
|
body = body
|
|
@@ -601,7 +601,7 @@ app.get('/playlist', async function(req, res) {
|
|
|
601
601
|
// Skip blank lines
|
|
602
602
|
if (line.trim() == '') return null
|
|
603
603
|
|
|
604
|
-
if (
|
|
604
|
+
if ( skip_markers && skip_markers[skip_marker_index] ) {
|
|
605
605
|
if ( skip_next ) {
|
|
606
606
|
skip_next = false
|
|
607
607
|
return null
|
|
@@ -609,55 +609,23 @@ app.get('/playlist', async function(req, res) {
|
|
|
609
609
|
|
|
610
610
|
if (line.indexOf('#EXTINF:') == 0) {
|
|
611
611
|
time_counter += parseFloat(line.substring(8, line.length-1))
|
|
612
|
+
session.debuglog('checking skip marker at ' + time_counter)
|
|
612
613
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if ( inning_number > 0 ) {
|
|
616
|
-
inning_index = (inning_number * 2)
|
|
617
|
-
if ( inning_half == 'top' ) inning_index = inning_index - 1
|
|
618
|
-
}
|
|
619
|
-
if ( (typeof session.temp_cache[contentId].inning_offsets[inning_index] !== 'undefined') && (typeof session.temp_cache[contentId].inning_offsets[inning_index].start !== 'undefined') && (time_counter < session.temp_cache[contentId].inning_offsets[inning_index].start) ) {
|
|
620
|
-
session.debuglog('skipping ' + time_counter + ' before ' + session.temp_cache[contentId].inning_offsets[inning_index].start)
|
|
621
|
-
// Increment skip index if our offset is less than the inning start
|
|
622
|
-
if ( offsets && (offsets[skip_index]) && (offsets[skip_index].end) && (offsets[skip_index].end < session.temp_cache[contentId].inning_offsets[inning_index].start) ) {
|
|
623
|
-
skip_index++
|
|
624
|
-
}
|
|
625
|
-
skip_next = true
|
|
626
|
-
if ( discontinuity ) {
|
|
627
|
-
return null
|
|
628
|
-
} else {
|
|
629
|
-
discontinuity = true
|
|
630
|
-
return '#EXT-X-DISCONTINUITY'
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
session.debuglog('inning start time not found or duplicate request made, ignoring: ' + u)
|
|
634
|
-
inning_half = VALID_INNING_HALF[0]
|
|
635
|
-
inning_number = VALID_INNING_NUMBER[0]
|
|
636
|
-
}
|
|
614
|
+
while (skip_markers[skip_marker_index] && (skip_markers[skip_marker_index].break_end < time_counter)) {
|
|
615
|
+
skip_marker_index++
|
|
637
616
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
if ( (typeof offsets[skip_index] === 'undefined') || (typeof offsets[skip_index].start === 'undefined') || (typeof offsets[skip_index].end === 'undefined') || ((time_counter > offsets[skip_index].start) && (time_counter < offsets[skip_index].end)) ) {
|
|
645
|
-
session.debuglog('keeping ' + time_counter)
|
|
646
|
-
skip_this = false
|
|
647
|
-
} else {
|
|
648
|
-
session.debuglog('skipping ' + time_counter)
|
|
649
|
-
}
|
|
650
|
-
if ( skip_this ) {
|
|
651
|
-
skip_next = true
|
|
652
|
-
if ( discontinuity ) {
|
|
653
|
-
return null
|
|
654
|
-
} else {
|
|
655
|
-
discontinuity = true
|
|
656
|
-
return '#EXT-X-DISCONTINUITY'
|
|
657
|
-
}
|
|
617
|
+
if (skip_markers[skip_marker_index] && (time_counter >= skip_markers[skip_marker_index].break_start) && (time_counter < skip_markers[skip_marker_index].break_end)) {
|
|
618
|
+
session.debuglog('skipping ' + time_counter)
|
|
619
|
+
skip_next = true
|
|
620
|
+
if ( discontinuity ) {
|
|
621
|
+
return null
|
|
658
622
|
} else {
|
|
659
|
-
discontinuity =
|
|
623
|
+
discontinuity = true
|
|
624
|
+
return '#EXT-X-DISCONTINUITY'
|
|
660
625
|
}
|
|
626
|
+
} else {
|
|
627
|
+
session.debuglog('keeping ' + time_counter)
|
|
628
|
+
discontinuity = false
|
|
661
629
|
}
|
|
662
630
|
}
|
|
663
631
|
}
|
|
@@ -689,6 +657,19 @@ app.get('/playlist', async function(req, res) {
|
|
|
689
657
|
})
|
|
690
658
|
.join('\n')+'\n'
|
|
691
659
|
|
|
660
|
+
if ( pad != VALID_PAD[0] ) {
|
|
661
|
+
let body_array = body.trim().split('\n')
|
|
662
|
+
let last_segment_index = body_array.length-1
|
|
663
|
+
if ( body_array[last_segment_index] == '#EXT-X-ENDLIST' ) {
|
|
664
|
+
session.debuglog('padding archive stream with extra segments')
|
|
665
|
+
last_segment_index--
|
|
666
|
+
let pad_lines = '#EXT-X-DISCONTINUITY' + '\n' + body_array[last_segment_index-1] + '\n' + body_array[last_segment_index] + '\n'
|
|
667
|
+
session.debuglog(pad_lines)
|
|
668
|
+
for (i=0; i<pad; i++) {
|
|
669
|
+
body += pad_lines
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
692
673
|
if ( force_vod != VALID_FORCE_VOD[0] ) body += '#EXT-X-ENDLIST' + '\n'
|
|
693
674
|
session.debuglog(body)
|
|
694
675
|
respond(response, res, Buffer.from(body))
|
|
@@ -795,6 +776,7 @@ app.get('/', async function(req, res) {
|
|
|
795
776
|
|
|
796
777
|
let gameDate = session.liveDate()
|
|
797
778
|
let todayUTCHours = session.getTodayUTCHours()
|
|
779
|
+
let curDate = new Date()
|
|
798
780
|
if ( req.query.date ) {
|
|
799
781
|
if ( req.query.date == VALID_DATES[1] ) {
|
|
800
782
|
gameDate = session.yesterdayDate()
|
|
@@ -802,7 +784,6 @@ app.get('/', async function(req, res) {
|
|
|
802
784
|
gameDate = req.query.date
|
|
803
785
|
}
|
|
804
786
|
} else {
|
|
805
|
-
let curDate = new Date()
|
|
806
787
|
let utcHours = curDate.getUTCHours()
|
|
807
788
|
if ( (utcHours >= todayUTCHours) && (utcHours < YESTERDAY_UTC_HOURS) ) {
|
|
808
789
|
gameDate = session.yesterdayDate()
|
|
@@ -823,6 +804,10 @@ app.get('/', async function(req, res) {
|
|
|
823
804
|
if ( req.query.startFrom ) {
|
|
824
805
|
startFrom = req.query.startFrom
|
|
825
806
|
}
|
|
807
|
+
var controls = VALID_CONTROLS[0]
|
|
808
|
+
if ( req.query.controls ) {
|
|
809
|
+
controls = req.query.controls
|
|
810
|
+
}
|
|
826
811
|
var scores = VALID_SCORES[0]
|
|
827
812
|
if ( req.query.scores ) {
|
|
828
813
|
scores = req.query.scores
|
|
@@ -855,9 +840,9 @@ app.get('/', async function(req, res) {
|
|
|
855
840
|
if ( req.query.skip ) {
|
|
856
841
|
skip = req.query.skip
|
|
857
842
|
}
|
|
858
|
-
var
|
|
859
|
-
if ( req.query.
|
|
860
|
-
|
|
843
|
+
var pad = VALID_PAD[0]
|
|
844
|
+
if ( req.query.pad ) {
|
|
845
|
+
pad = req.query.pad
|
|
861
846
|
}
|
|
862
847
|
// audio_url is disabled here, now used in multiview instead
|
|
863
848
|
/*var audio_url = ''
|
|
@@ -890,13 +875,13 @@ app.get('/', async function(req, res) {
|
|
|
890
875
|
body += '</style><script type="text/javascript">' + "\n";
|
|
891
876
|
|
|
892
877
|
// Define option variables in page
|
|
893
|
-
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
|
|
878
|
+
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"
|
|
894
879
|
// audio_url is disabled here, now used in multiview instead
|
|
895
880
|
//body += 'var audio_url="' + audio_url + '";' + "\n"
|
|
896
881
|
|
|
897
882
|
// Reload function, called after options change
|
|
898
883
|
// audio_url is disabled here, now used in multiview instead
|
|
899
|
-
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 (
|
|
884
|
+
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=="Embed"){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+"&"}window.location=newurl.substring(0,newurl.length-1)}' + "\n"
|
|
900
885
|
|
|
901
886
|
// Ajax function for multiview and highlights
|
|
902
887
|
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"
|
|
@@ -941,7 +926,15 @@ app.get('/', async function(req, res) {
|
|
|
941
926
|
|
|
942
927
|
body += '<p>'
|
|
943
928
|
if ( linkType == 'Embed' ) {
|
|
944
|
-
body += '<span class="tooltip">
|
|
929
|
+
body += '<p><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>: '
|
|
930
|
+
for (var i = 0; i < VALID_CONTROLS.length; i++) {
|
|
931
|
+
body += '<button '
|
|
932
|
+
if ( controls == VALID_CONTROLS[i] ) body += 'class="default" '
|
|
933
|
+
body += 'onclick="controls=\'' + VALID_CONTROLS[i] + '\';reload()">' + VALID_CONTROLS[i] + '</button> '
|
|
934
|
+
}
|
|
935
|
+
body += '</p>' + "\n"
|
|
936
|
+
|
|
937
|
+
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>: '
|
|
945
938
|
for (var i = 0; i < VALID_START_FROM.length; i++) {
|
|
946
939
|
body += '<button '
|
|
947
940
|
if ( startFrom == VALID_START_FROM[i] ) body += 'class="default" '
|
|
@@ -1017,7 +1010,14 @@ app.get('/', async function(req, res) {
|
|
|
1017
1010
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
1018
1011
|
let querystring = '?event=biginning'
|
|
1019
1012
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1013
|
+
if ( linkType == 'embed' ) {
|
|
1014
|
+
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
1015
|
+
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
1016
|
+
}
|
|
1020
1017
|
if ( resolution != VALID_RESOLUTIONS[0] ) querystring += '&resolution=' + resolution
|
|
1018
|
+
if ( linkType == 'stream' ) {
|
|
1019
|
+
if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
|
|
1020
|
+
}
|
|
1021
1021
|
querystring += content_protect_b
|
|
1022
1022
|
multiviewquerystring += content_protect_b
|
|
1023
1023
|
body += '<a href="' + thislink + querystring + '">Big Inning</a>'
|
|
@@ -1229,7 +1229,11 @@ app.get('/', async function(req, res) {
|
|
|
1229
1229
|
station += '*'
|
|
1230
1230
|
}
|
|
1231
1231
|
if ( (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON') || (cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ARCHIVE') || cache_data.dates[0].games[j].gameUtils.isFinal ) {
|
|
1232
|
-
|
|
1232
|
+
let gameTime = new Date(cache_data.dates[0].games[j].gameDate)
|
|
1233
|
+
gameTime.setMinutes(gameTime.getMinutes()-10)
|
|
1234
|
+
if ( curDate >= gameTime ) {
|
|
1235
|
+
game_started = true
|
|
1236
|
+
}
|
|
1233
1237
|
let mediaId = cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaId
|
|
1234
1238
|
if ( (mediaType == 'MLBTV') && 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()) ) {
|
|
1235
1239
|
body += teamabbr + ': <s>' + station + '</s>'
|
|
@@ -1238,13 +1242,13 @@ app.get('/', async function(req, res) {
|
|
|
1238
1242
|
querystring = '?mediaId=' + mediaId
|
|
1239
1243
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + '&audio_track=' + DEFAULT_MULTIVIEW_AUDIO_TRACK
|
|
1240
1244
|
if ( linkType == 'embed' ) {
|
|
1241
|
-
if ( startFrom !=
|
|
1245
|
+
if ( startFrom != VALID_START_FROM[0] ) querystring += '&startFrom=' + startFrom
|
|
1246
|
+
if ( controls != VALID_CONTROLS[0] ) querystring += '&controls=' + controls
|
|
1242
1247
|
}
|
|
1243
1248
|
if ( mediaType == 'MLBTV' ) {
|
|
1244
1249
|
if ( inning_half != VALID_INNING_HALF[0] ) querystring += '&inning_half=' + inning_half
|
|
1245
|
-
if ( inning_number !=
|
|
1246
|
-
if ( skip !=
|
|
1247
|
-
if ( skip_adjust != '0' ) querystring += '&skip_adjust=' + skip_adjust
|
|
1250
|
+
if ( inning_number != VALID_INNING_NUMBER[0] ) querystring += '&inning_number=' + relative_inning
|
|
1251
|
+
if ( skip != VALID_SKIP[0] ) querystring += '&skip=' + skip
|
|
1248
1252
|
if ( (inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0]) ) {
|
|
1249
1253
|
let contentId = cache_data.dates[0].games[j].content.media.epg[k].items[x].contentId
|
|
1250
1254
|
querystring += '&contentId=' + contentId
|
|
@@ -1254,6 +1258,7 @@ app.get('/', async function(req, res) {
|
|
|
1254
1258
|
// audio_url is disabled here, now used in multiview instead
|
|
1255
1259
|
//if ( audio_url != '' ) querystring += '&audio_url=' + encodeURIComponent(audio_url)
|
|
1256
1260
|
}
|
|
1261
|
+
if ( pad != VALID_PAD[0] ) querystring += '&pad=' + pad
|
|
1257
1262
|
if ( linkType == 'stream' ) {
|
|
1258
1263
|
if ( cache_data.dates[0].games[j].content.media.epg[k].items[x].mediaState == 'MEDIA_ON' ) {
|
|
1259
1264
|
if ( force_vod != VALID_FORCE_VOD[0] ) querystring += '&force_vod=' + force_vod
|
|
@@ -1295,7 +1300,7 @@ app.get('/', async function(req, res) {
|
|
|
1295
1300
|
if ( body.substr(-2) == ', ' ) {
|
|
1296
1301
|
body = body.slice(0, -2)
|
|
1297
1302
|
}
|
|
1298
|
-
if ( (mediaType == 'MLBTV') && (game_started) ) {
|
|
1303
|
+
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 ) {
|
|
1299
1304
|
body += '<br/><a href="javascript:showhighlights(\'' + cache_data.dates[0].games[j].gamePk + '\',\'' + gameDate + '\')">Highlights</a>'
|
|
1300
1305
|
}
|
|
1301
1306
|
if ( body.substr(-2) == ', ' ) {
|
|
@@ -1336,15 +1341,24 @@ app.get('/', async function(req, res) {
|
|
|
1336
1341
|
//body += '<br/><span class="tooltip">or enter a separate audio stream URL<span class="tooltiptext">EXPERIMENTAL! May not actually work. For video streams only: you can also include a separate audio stream URL as an alternate audio track. This is useful if you want to pair the road radio feed with a national TV broadcast (which only includes home radio feeds by default).<br/><br/>After entering the audio stream URL, click the Update button to include it in the video links above; click the Reset button when done with this option.<br/><br/>Warning: does not support inning start or skip options.</span></span>: <span class="tinytext">(copy one from the <button onclick="mediaType=\'Audio\';reload()">Audio</button> page</a>)</span><br/><textarea id="audio_url" rows=2 cols=60 oninput="this.value=stream_substitution(this.value)">' + audio_url + '</textarea><br/><button onclick="audio_url=document.getElementById(\'audio_url\').value;reload()">Update Audio URL</button> <button onclick="audio_url=\'\';reload()">Reset Audio URL</button><br/>'
|
|
1337
1342
|
body += '</p>' + "\n"
|
|
1338
1343
|
|
|
1339
|
-
body += '<p><span class="tooltip">Skip<span class="tooltiptext">For video streams only (use the video "none" option above to apply it to audio streams): you can remove breaks or non-
|
|
1344
|
+
body += '<p><span class="tooltip">Skip<span class="tooltiptext">For video streams only (use the video "none" option above to apply it to audio streams): you can remove breaks, idle time, or non-action pitches from the stream (useful to make your own "condensed games").<br/><br/>NOTE: 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.</span></span>: '
|
|
1340
1345
|
for (var i = 0; i < VALID_SKIP.length; i++) {
|
|
1341
1346
|
body += '<button '
|
|
1342
1347
|
if ( skip == VALID_SKIP[i] ) body += 'class="default" '
|
|
1343
1348
|
body += 'onclick="skip=\'' + VALID_SKIP[i] + '\';reload()">' + VALID_SKIP[i] + '</button> '
|
|
1344
1349
|
}
|
|
1345
|
-
body += ' <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="5" 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"/>'
|
|
1346
1350
|
body += '</p>' + "\n"
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
body += '<p><span class="tooltip">Pad<span class="tooltiptext">You can pad archive streams with random extra time at the end, to help conceal timeline spoilers.</span></span>: '
|
|
1354
|
+
for (var i = 0; i < VALID_PAD.length; i++) {
|
|
1355
|
+
body += '<button '
|
|
1356
|
+
if ( pad == VALID_PAD[i] ) body += 'class="default" '
|
|
1357
|
+
body += 'onclick="pad=\'' + VALID_PAD[i] + '\';reload()">' + VALID_PAD[i] + '</button> '
|
|
1358
|
+
}
|
|
1359
|
+
body += '</p>' + "\n"
|
|
1347
1360
|
|
|
1361
|
+
if ( mediaType == 'Video' ) {
|
|
1348
1362
|
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'
|
|
1349
1363
|
if ( ffmpeg_status ) body += 'ed'
|
|
1350
1364
|
body += '</a> | <a id="stopmultiview" href="" onclick="stopmultiview(this);return false">Stop'
|
|
@@ -1394,7 +1408,7 @@ app.get('/', async function(req, res) {
|
|
|
1394
1408
|
|
|
1395
1409
|
body += '<p><span class="tooltip">Include (or exclude) LIDOM<span class="tooltiptext">Dominican Winter League, aka Liga de Beisbol Dominicano. Live stream only, does not support starting from the beginning or certain innings, skip options, etc.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=lidom' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=lidom' + content_protect_b + '">guide.xml</a></p>' + "\n"
|
|
1396
1410
|
|
|
1397
|
-
body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show
|
|
1411
|
+
body += '<p><span class="tooltip">Include (or exclude) Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=biginning' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=biginning' + content_protect_b + '">guide.xml</a></p>' + "\n"
|
|
1398
1412
|
|
|
1399
1413
|
body += '<p><span class="tooltip">Include (or exclude) Multiview<span class="tooltiptext">Requires starting and stopping the multiview stream from the web interface.</span></span>: <a href="/channels.m3u?mediaType=' + mediaType + '&resolution=' + resolution + '&includeTeams=multiview' + content_protect_b + '">channels.m3u</a> and <a href="/guide.xml?mediaType=' + mediaType + '&includeTeams=multiview' + content_protect_b + '">guide.xml</a></p>' + "\n"
|
|
1400
1414
|
|
|
@@ -1529,10 +1543,14 @@ app.get('/embed.html', async function(req, res) {
|
|
|
1529
1543
|
|
|
1530
1544
|
delete req.headers.host
|
|
1531
1545
|
|
|
1532
|
-
let startFrom =
|
|
1546
|
+
let startFrom = VALID_START_FROM[0]
|
|
1533
1547
|
if ( req.query.startFrom ) {
|
|
1534
1548
|
startFrom = req.query.startFrom
|
|
1535
1549
|
}
|
|
1550
|
+
let controls = VALID_CONTROLS[0]
|
|
1551
|
+
if ( req.query.controls ) {
|
|
1552
|
+
controls = req.query.controls
|
|
1553
|
+
}
|
|
1536
1554
|
|
|
1537
1555
|
let video_url = '/stream.m3u8'
|
|
1538
1556
|
if ( req.query.src ) {
|
|
@@ -1546,9 +1564,13 @@ app.get('/embed.html', async function(req, res) {
|
|
|
1546
1564
|
session.debuglog('embed src : ' + video_url)
|
|
1547
1565
|
|
|
1548
1566
|
// Adapted from https://hls-js.netlify.app/demo/basic-usage.html
|
|
1549
|
-
var body = '<html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><title>' + appname + ' player</title><link rel="icon" href="favicon.svg"><style type="text/css">input[type=text],input[type=button]{-webkit-appearance:none;-webkit-border-radius:0}body{background-color:black;color:lightgrey;font-family:Arial,Helvetica,sans-serif}video{width:100% !important;height:auto !important;max-width:1280px}input[type=number]::-webkit-inner-spin-button{opacity:1}button{color:lightgray;background-color:black}button.default{color:black;background-color:lightgray}</style><script>function goBack(){var prevPage=window.location.href;window.history.go(-1);setTimeout(function(){if(window.location.href==prevPage){window.location.href="/"}}, 500)}function toggleAudio(x){var elements=document.getElementsByClassName("audioButton");for(var i=0;i<elements.length;i++){elements[i].className="audioButton"}document.getElementById("audioButton"+x).className+=" default";hls.audioTrack=x}function changeTime(x){video.currentTime+=x}function changeRate(x){let newRate=Math.round((Number(document.getElementById("playback_rate").value)+x)*10)/10;if((newRate<=document.getElementById("playback_rate").max) && (newRate>=document.getElementById("playback_rate").min)){document.getElementById("playback_rate").value=newRate.toFixed(1);video.defaultPlaybackRate=video.playbackRate=document.getElementById("playback_rate").value}}function myKeyPress(e){if(e.key=="ArrowRight"){changeTime(10)}else if(e.key=="ArrowLeft"){changeTime(-10)}else if(e.key=="ArrowUp"){changeRate(0.1)}else if(e.key=="ArrowDown"){changeRate(-0.1)}}</script></head><body onkeydown="myKeyPress(event)"><script src="https://hls-js.netlify.app/dist/hls.js"></script><video id="video"
|
|
1567
|
+
var body = '<html><head><meta charset="UTF-8"><meta http-equiv="Content-type" content="text/html;charset=UTF-8"><title>' + appname + ' player</title><link rel="icon" href="favicon.svg"><style type="text/css">input[type=text],input[type=button]{-webkit-appearance:none;-webkit-border-radius:0}body{background-color:black;color:lightgrey;font-family:Arial,Helvetica,sans-serif}video{width:100% !important;height:auto !important;max-width:1280px}input[type=number]::-webkit-inner-spin-button{opacity:1}button{color:lightgray;background-color:black}button.default{color:black;background-color:lightgray}</style><script>function goBack(){var prevPage=window.location.href;window.history.go(-1);setTimeout(function(){if(window.location.href==prevPage){window.location.href="/"}}, 500)}function toggleAudio(x){var elements=document.getElementsByClassName("audioButton");for(var i=0;i<elements.length;i++){elements[i].className="audioButton"}document.getElementById("audioButton"+x).className+=" default";hls.audioTrack=x}function changeTime(x){video.currentTime+=x}function changeRate(x){let newRate=Math.round((Number(document.getElementById("playback_rate").value)+x)*10)/10;if((newRate<=document.getElementById("playback_rate").max) && (newRate>=document.getElementById("playback_rate").min)){document.getElementById("playback_rate").value=newRate.toFixed(1);video.defaultPlaybackRate=video.playbackRate=document.getElementById("playback_rate").value}}function myKeyPress(e){if(e.key=="ArrowRight"){changeTime(10)}else if(e.key=="ArrowLeft"){changeTime(-10)}else if(e.key=="ArrowUp"){changeRate(0.1)}else if(e.key=="ArrowDown"){changeRate(-0.1)}}</script></head><body onkeydown="myKeyPress(event)"><script src="https://hls-js.netlify.app/dist/hls.js"></script><video id="video"'
|
|
1568
|
+
if ( controls == VALID_CONTROLS[0] ) {
|
|
1569
|
+
body += ' controls'
|
|
1570
|
+
}
|
|
1571
|
+
body += '></video><script>var video=document.getElementById("video");if(Hls.isSupported()){var hls=new Hls('
|
|
1550
1572
|
|
|
1551
|
-
if ( startFrom !=
|
|
1573
|
+
if ( startFrom != VALID_START_FROM[1] ) {
|
|
1552
1574
|
body += '{startPosition:0,liveSyncDuration:32400,liveMaxLatencyDuration:32410}'
|
|
1553
1575
|
}
|
|
1554
1576
|
|
|
@@ -1556,7 +1578,7 @@ app.get('/embed.html', async function(req, res) {
|
|
|
1556
1578
|
|
|
1557
1579
|
body += '<button onclick="changeTime(video.duration-10)">Latest</button> '
|
|
1558
1580
|
|
|
1559
|
-
body += '<button id="airplay">AirPlay</button></p><p>Playback rate: <input type="number" value=1.0 min=0.1 max=16.0 step=0.1 id="playback_rate" size="8" style="width: 4em" onchange="video.defaultPlaybackRate=video.playbackRate=this.value"></p><p>Audio: <button onclick="video.muted=!video.muted">Toggle Mute</button> <span id="audioSpan"></span></p><p><button onclick="goBack()">Back</button></p><script>var airPlay=document.getElementById("airplay");if(window.WebKitPlaybackTargetAvailabilityEvent){video.addEventListener("webkitplaybacktargetavailabilitychanged",function(event){switch(event.availability){case "available":airPlay.style.display="inline";break;default:airPlay.style.display="none"}airPlay.addEventListener("click",function(){video.webkitShowPlaybackTargetPicker()})})}else{airPlay.style.display="none"}</script></body></html>'
|
|
1581
|
+
body += '<button id="airplay">AirPlay</button></p><p>Playback rate: <input type="number" value=1.0 min=0.1 max=16.0 step=0.1 id="playback_rate" size="8" style="width: 4em" onchange="video.defaultPlaybackRate=video.playbackRate=this.value"></p><p>Audio: <button onclick="video.muted=!video.muted">Toggle Mute</button> <span id="audioSpan"></span></p><p>Controls: <button onclick="video.controls=!video.controls">Toggle Controls</button></p><p><button onclick="goBack()">Back</button></p><script>var airPlay=document.getElementById("airplay");if(window.WebKitPlaybackTargetAvailabilityEvent){video.addEventListener("webkitplaybacktargetavailabilitychanged",function(event){switch(event.availability){case "available":airPlay.style.display="inline";break;default:airPlay.style.display="none"}airPlay.addEventListener("click",function(){video.webkitShowPlaybackTargetPicker()})})}else{airPlay.style.display="none"}</script></body></html>'
|
|
1560
1582
|
res.end(body)
|
|
1561
1583
|
})
|
|
1562
1584
|
|
package/package.json
CHANGED
package/session.js
CHANGED
|
@@ -33,6 +33,14 @@ const TODAY_UTC_HOURS = 8 // UTC hours (EST + 4) into tomorrow to still use toda
|
|
|
33
33
|
|
|
34
34
|
const TEAM_IDS = {'ARI':'109','ATL':'144','BAL':'110','BOS':'111','CHC':'112','CWS':'145','CIN':'113','CLE':'114','COL':'115','DET':'116','HOU':'117','KC':'118','LAA':'108','LAD':'119','MIA':'146','MIL':'158','MIN':'142','NYM':'121','NYY':'147','OAK':'133','PHI':'143','PIT':'134','STL':'138','SD':'135','SF':'137','SEA':'136','TB':'139','TEX':'140','TOR':'141','WSH':'120'}
|
|
35
35
|
|
|
36
|
+
// These are the events to ignore, if we're skipping breaks
|
|
37
|
+
const BREAK_TYPES = ['Game Advisory', 'Pitching Substitution', 'Offensive Substitution', 'Defensive Sub', 'Defensive Switch', 'Runner Placed On Base']
|
|
38
|
+
// These are the events to keep, in addition to the last event of each at-bat, if we're skipping pitches
|
|
39
|
+
const ACTION_TYPES = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Error', 'Out', 'Balk', 'Defensive Indiff']
|
|
40
|
+
const EVENT_START_PADDING = -5
|
|
41
|
+
const EVENT_END_PADDING = 8
|
|
42
|
+
const MINIMUM_BREAK_DURATION = 10
|
|
43
|
+
|
|
36
44
|
class sessionClass {
|
|
37
45
|
// Initialize the class
|
|
38
46
|
constructor(argv = {}) {
|
|
@@ -1571,14 +1579,15 @@ class sessionClass {
|
|
|
1571
1579
|
if ( (excludeTeams.length > 0) && (excludeTeams.includes(team) || excludeTeams.includes(opponent_team) || excludeTeams.includes(teamType)) ) {
|
|
1572
1580
|
continue
|
|
1573
1581
|
} else if ( (includeTeams.length == 0) || includeTeams.includes(team) || includeTeams.includes(teamType) ) {
|
|
1574
|
-
let icon = server
|
|
1582
|
+
/*let icon = server
|
|
1575
1583
|
if ( (teamType == 'NATIONAL') && ((includeTeams.length == 0) || ((includeTeams.length > 0) && includeTeams.includes(teamType))) ) {
|
|
1576
1584
|
team = teamType + '.' + nationalCounter[mediaTitle]
|
|
1577
1585
|
icon += '/image.svg?teamId=MLB'
|
|
1578
1586
|
} else {
|
|
1579
1587
|
icon += '/image.svg?teamId=' + cache_data.dates[i].games[j].content.media.epg[k].items[x].mediaFeedSubType
|
|
1580
1588
|
}
|
|
1581
|
-
if ( this.protection.content_protect ) icon += '&content_protect=' + this.protection.content_protect
|
|
1589
|
+
if ( this.protection.content_protect ) icon += '&content_protect=' + this.protection.content_protect*/
|
|
1590
|
+
let icon = 'https://img.mlbstatic.com/mlb-photos/image/upload/ar_167:215,c_crop/fl_relative,l_team:' + cache_data.dates[i].games[j].teams['home'].team.id + ':fill:spot.png,w_1.0,h_1,x_0.5,y_0,fl_no_overflow,e_distort:100p:0:200p:0:200p:100p:0:100p/fl_relative,l_team:' + cache_data.dates[i].games[j].teams['away'].team.id + ':logo:spot:current,w_0.38,x_-0.25,y_-0.16/fl_relative,l_team:' + cache_data.dates[i].games[j].teams['home'].team.id + ':logo:spot:current,w_0.38,x_0.25,y_0.16/w_750/team/' + cache_data.dates[i].games[j].teams['away'].team.id + '/fill/spot.png'
|
|
1582
1591
|
let channelid = mediaType + '.' + team
|
|
1583
1592
|
channels[channelid] = {}
|
|
1584
1593
|
channels[channelid].name = channelid
|
|
@@ -1661,8 +1670,9 @@ class sessionClass {
|
|
|
1661
1670
|
if ( (excludeTeams.length > 0) && excludeTeams.includes('BIGINNING') ) {
|
|
1662
1671
|
// do nothing
|
|
1663
1672
|
} else if ( (includeTeams.length == 0) || includeTeams.includes('BIGINNING') ) {
|
|
1664
|
-
let icon = server + '/image.svg?teamId=MLB'
|
|
1665
|
-
if ( this.protection.content_protect ) icon += '&content_protect=' + this.protection.content_protect
|
|
1673
|
+
/*let icon = server + '/image.svg?teamId=MLB'
|
|
1674
|
+
if ( this.protection.content_protect ) icon += '&content_protect=' + this.protection.content_protect*/
|
|
1675
|
+
let icon = 'https://img.mlbstatic.com/mlb-images/image/private/ar_16:9,g_auto,q_auto:good,w_372,c_fill,f_jpg/mlb/uwr8vepua4t1fe8uwyki'
|
|
1666
1676
|
let channelid = mediaType + '.BIGINNING'
|
|
1667
1677
|
channels[channelid] = {}
|
|
1668
1678
|
channels[channelid].name = channelid
|
|
@@ -1998,164 +2008,140 @@ class sessionClass {
|
|
|
1998
2008
|
}
|
|
1999
2009
|
}
|
|
2000
2010
|
|
|
2001
|
-
// Get
|
|
2002
|
-
async
|
|
2011
|
+
// Get skip markers into temporary cache
|
|
2012
|
+
async getSkipMarkers(contentId, skip_type, start_inning, start_inning_half) {
|
|
2003
2013
|
try {
|
|
2004
|
-
this.debuglog('
|
|
2014
|
+
this.debuglog('getSkipMarkers')
|
|
2015
|
+
|
|
2016
|
+
let skip_markers = []
|
|
2005
2017
|
|
|
2006
|
-
|
|
2018
|
+
// assume the game starts in a break
|
|
2019
|
+
let break_start = 0
|
|
2007
2020
|
|
|
2008
2021
|
// Get the broadcast start time first -- event times will be relative to this
|
|
2009
2022
|
let broadcast_start = await this.getBroadcastStart(contentId)
|
|
2010
2023
|
let broadcast_start_offset = broadcast_start.broadcast_start_offset
|
|
2011
2024
|
let broadcast_start_timestamp = broadcast_start.broadcast_start_timestamp
|
|
2012
|
-
this.debuglog('broadcast start detected as ' + broadcast_start_timestamp + ', offset ' + broadcast_start_offset)
|
|
2013
|
-
|
|
2014
|
-
let cache_data = await this.getGamedayData(contentId)
|
|
2025
|
+
this.debuglog('getSkipMarkers broadcast start detected as ' + broadcast_start_timestamp + ', offset ' + broadcast_start_offset)
|
|
2015
2026
|
|
|
2016
|
-
//
|
|
2017
|
-
|
|
2027
|
+
// start inning 0 is simply the broadcast start offset
|
|
2028
|
+
if ((skip_type == 0) && (start_inning == 0)) {
|
|
2029
|
+
let break_end = broadcast_start_offset
|
|
2030
|
+
skip_markers.push({'break_start': break_start, 'break_end': break_end})
|
|
2031
|
+
} else {
|
|
2032
|
+
if (start_inning == '') {
|
|
2033
|
+
start_inning = 0
|
|
2034
|
+
}
|
|
2035
|
+
if (start_inning_half == '') {
|
|
2036
|
+
start_inning_half = 'top'
|
|
2037
|
+
}
|
|
2018
2038
|
|
|
2019
|
-
|
|
2020
|
-
let action_types = ['Wild Pitch', 'Passed Ball', 'Stolen Base', 'Caught Stealing', 'Pickoff', 'Out', 'Balk', 'Defensive Indiff']
|
|
2039
|
+
let cache_data = await this.getGamedayData(contentId)
|
|
2021
2040
|
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
let last_event = 0
|
|
2025
|
-
let default_event_duration = 15
|
|
2041
|
+
// make sure we have play data
|
|
2042
|
+
if (cache_data && cache_data.liveData && cache_data.liveData.plays && cache_data.liveData.plays.allPlays) {
|
|
2026
2043
|
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
let pad_adjust = 20
|
|
2044
|
+
// keep track of inning, if skipping inning breaks only
|
|
2045
|
+
let previous_inning = 0
|
|
2046
|
+
let previous_inning_half = ''
|
|
2031
2047
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
let last_inning_half = ''
|
|
2048
|
+
// calculate total skip time (for fun)
|
|
2049
|
+
let total_skip_time = 0
|
|
2035
2050
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2051
|
+
// Loop through all plays
|
|
2052
|
+
for (var i=0; i < cache_data.liveData.plays.allPlays.length; i++) {
|
|
2038
2053
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
// ignore break events
|
|
2052
|
-
} else {
|
|
2053
|
-
inning_offsets[inning_index].start = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[j].startTime) - broadcast_start_timestamp) / 1000) - pad_start + skip_adjust
|
|
2054
|
-
break
|
|
2054
|
+
// make sure start inning is valid
|
|
2055
|
+
if (start_inning > 0) {
|
|
2056
|
+
let last_play_index = cache_data.liveData.plays.allPlays.length - 1
|
|
2057
|
+
let final_inning = cache_data.liveData.plays.allPlays[last_play_index].about.inning
|
|
2058
|
+
if (start_inning >= final_inning) {
|
|
2059
|
+
if (start_inning > final_inning) {
|
|
2060
|
+
start_inning = final_inning
|
|
2061
|
+
let final_inning_half = json_source['liveData']['plays']['allPlays'][last_play_index]['about']['halfInning']
|
|
2062
|
+
if ((start_inning_half == 'bottom') && (final_inning_half == 'top')) {
|
|
2063
|
+
start_inning_half = final_inning_half
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2055
2066
|
}
|
|
2056
2067
|
}
|
|
2057
|
-
// Update inning counters
|
|
2058
|
-
last_inning = cache_data.liveData.plays.allPlays[i].about.inning
|
|
2059
|
-
last_inning_half = cache_data.liveData.plays.allPlays[i].about.halfInning
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
2068
|
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
// Loop through play events, looking for actions
|
|
2067
|
-
let actions = []
|
|
2068
|
-
for (var j=0; j < cache_data.liveData.plays.allPlays[i].playEvents.length; j++) {
|
|
2069
|
-
// If skipping breaks, everything is an action except break types
|
|
2070
|
-
// otherwise, only action types are included (skipping pitches)
|
|
2071
|
-
if ( skip_types.includes('breaks') ) {
|
|
2072
|
-
if ( cache_data.liveData.plays.allPlays[i].playEvents[j].details && cache_data.liveData.plays.allPlays[i].playEvents[j].details.event && (break_types.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.event.includes(v))) ) {
|
|
2073
|
-
// ignore break events
|
|
2074
|
-
} else {
|
|
2075
|
-
actions.push(j)
|
|
2076
|
-
}
|
|
2077
|
-
} else if ( cache_data.liveData.plays.allPlays[i].playEvents[j].details && cache_data.liveData.plays.allPlays[i].playEvents[j].details.event && (action_types.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.event.includes(v))) ) {
|
|
2078
|
-
actions.push(j)
|
|
2069
|
+
// exit loop after found inning, if not skipping any breaks
|
|
2070
|
+
if ((skip_type == 0) && (skip_markers.length == 1)) {
|
|
2071
|
+
break
|
|
2079
2072
|
}
|
|
2080
|
-
}
|
|
2081
2073
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2074
|
+
let current_inning = cache_data.liveData.plays.allPlays[i].about.inning
|
|
2075
|
+
let current_inning_half = cache_data.liveData.plays.allPlays[i].about.halfInning
|
|
2076
|
+
// make sure we're past our start inning
|
|
2077
|
+
if ((current_inning > start_inning) || ((current_inning == start_inning) && ((current_inning_half == start_inning_half) || (current_inning_half == 'bottom')))) {
|
|
2078
|
+
// loop through events within each play
|
|
2079
|
+
for (var j=0; j < cache_data.liveData.plays.allPlays[i].playEvents.length; j++) {
|
|
2080
|
+
// always exclude break types
|
|
2081
|
+
if (cache_data.liveData.plays.allPlays[i].playEvents[j].details && cache_data.liveData.plays.allPlays[i].playEvents[j].details.event && BREAK_TYPES.includes(cache_data.liveData.plays.allPlays[i].playEvents[j].details.event)) {
|
|
2082
|
+
// if we're in the process of skipping inning breaks, treat the first break type we find as another inning break
|
|
2083
|
+
if ((skip_type == 1) && (previous_inning > 0)) {
|
|
2084
|
+
break_start = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[j].startTime) - broadcast_start_timestamp) / 1000) + EVENT_END_PADDING
|
|
2085
|
+
previous_inning = 0
|
|
2086
|
+
}
|
|
2087
|
+
continue
|
|
2088
|
+
} else {
|
|
2089
|
+
let action_index
|
|
2090
|
+
// skip type 1 (breaks) && 2 (idle time) will look at all plays with an endTime
|
|
2091
|
+
if ((skip_type <= 2) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime) {
|
|
2092
|
+
action_index = j
|
|
2093
|
+
} else if (skip_type == 3) {
|
|
2094
|
+
// skip type 3 excludes non-action pitches (events that aren't last in the at-bat and don't fall under action types)
|
|
2095
|
+
if ( (j < (cache_data.liveData.plays.allPlays[i].playEvents.length - 1)) && (!cache_data.liveData.plays.allPlays[i].playEvents[j].details || !cache_data.liveData.plays.allPlays[i].playEvents[j].details.event || !ACTION_TYPES.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.event.includes(v))) ) {
|
|
2096
|
+
continue
|
|
2097
|
+
} else {
|
|
2098
|
+
// if the action is associated with another play or the event doesn't have an end time, use the previous event instead
|
|
2099
|
+
if (cache_data.liveData.plays.allPlays[i].playEvents[j].actionPlayId || ((cache_data.liveData.plays.allPlays[i].playEvents[j].endTime === 'undefined') && (j > 0))) {
|
|
2100
|
+
action_index = j - 1
|
|
2101
|
+
} else {
|
|
2102
|
+
action_index = j
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (typeof action_index === 'undefined') {
|
|
2107
|
+
continue
|
|
2108
|
+
} else {
|
|
2109
|
+
let break_end = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[action_index].startTime) - broadcast_start_timestamp) / 1000) + EVENT_START_PADDING
|
|
2110
|
+
// if the break duration should be greater than than our specified minimum
|
|
2111
|
+
// and if skip type is not 1 (inning breaks) or the inning has changed
|
|
2112
|
+
// then we'll add the skip marker
|
|
2113
|
+
// otherwise we'll ignore it and move on to the next one
|
|
2114
|
+
if ( ((break_end - break_start) >= MINIMUM_BREAK_DURATION) && ((skip_type != 1) || (current_inning != previous_inning) || (current_inning_half != previous_inning_half)) ) {
|
|
2115
|
+
skip_markers.push({'break_start': break_start, 'break_end': break_end})
|
|
2116
|
+
total_skip_time += break_end - break_start
|
|
2117
|
+
previous_inning = current_inning
|
|
2118
|
+
previous_inning_half = current_inning_half
|
|
2119
|
+
// exit loop after found inning, if not skipping breaks
|
|
2120
|
+
if (skip_type == 0) {
|
|
2121
|
+
break
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
break_start = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[action_index].endTime) - broadcast_start_timestamp) / 1000) + EVENT_END_PADDING
|
|
2125
|
+
// add extra padding for overturned review plays
|
|
2126
|
+
if (cache_data.liveData.plays.allPlays[i].reviewDetails && (cache_data.liveData.plays.allPlays[i].reviewDetails.isOverturned == true)) {
|
|
2127
|
+
break_start += 40
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2095
2130
|
}
|
|
2096
2131
|
}
|
|
2097
|
-
// Update the end time, if available
|
|
2098
|
-
if ( cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].endTime ) {
|
|
2099
|
-
this_event.end = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].endTime) - broadcast_start_timestamp) / 1000) + pad_end + skip_adjust
|
|
2100
|
-
// Otherwise use the start time to estimate the end time
|
|
2101
|
-
} else {
|
|
2102
|
-
this_event.end = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].startTime) - broadcast_start_timestamp) / 1000) + this_pad_end + skip_adjust + default_event_duration
|
|
2103
|
-
}
|
|
2104
|
-
// Check if we have skipped a play event (indicating a break inside an at-bat), in which case push this event and start another one
|
|
2105
|
-
if ( (x > 0) && (actions[x] > (actions[x-1]+1)) && (typeof this_event.end !== 'undefined') ) {
|
|
2106
|
-
// For events within at-bats, adjust the padding
|
|
2107
|
-
event_in_atbat = true
|
|
2108
|
-
this_event.end += pad_adjust
|
|
2109
|
-
event_offsets.push(this_event)
|
|
2110
|
-
this_event = {}
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
// Once we've finished our loop through a play's events, push the event as long as we got an end time
|
|
2114
|
-
if ( typeof this_event.end !== 'undefined' ) {
|
|
2115
|
-
event_offsets.push(this_event)
|
|
2116
|
-
}
|
|
2117
|
-
} else if ( skip_types.includes('pitches') ) {
|
|
2118
|
-
// If we're skipping pitches, but we didn't detect any action events, use the last play event
|
|
2119
|
-
if ( (cache_data.liveData.plays.allPlays[i].playEvents.length > 0) && ((actions.length == 0) || (actions[(actions.length-1)] < (cache_data.liveData.plays.allPlays[i].playEvents.length-1))) ) {
|
|
2120
|
-
actions.push(cache_data.liveData.plays.allPlays[i].playEvents.length-1)
|
|
2121
|
-
}
|
|
2122
|
-
// Loop through the actions
|
|
2123
|
-
for (var x=0; x < actions.length; x++) {
|
|
2124
|
-
let this_event = {}
|
|
2125
|
-
let this_pad_start = pad_start
|
|
2126
|
-
let this_pad_end = pad_end
|
|
2127
|
-
// For events within at-bats, adjust the padding
|
|
2128
|
-
if ( x < (actions.length-1) ) {
|
|
2129
|
-
this_pad_start += pad_adjust
|
|
2130
|
-
this_pad_end -= pad_adjust
|
|
2131
|
-
}
|
|
2132
|
-
this_event.start = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].startTime) - broadcast_start_timestamp) / 1000) - this_pad_start + skip_adjust
|
|
2133
|
-
// If play event end time is available, set it and push this event
|
|
2134
|
-
if ( cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].endTime ) {
|
|
2135
|
-
this_event.end = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].endTime) - broadcast_start_timestamp) / 1000) + this_pad_end + skip_adjust
|
|
2136
|
-
// Otherwise use the start time to estimate the end time
|
|
2137
|
-
} else {
|
|
2138
|
-
this_event.end = ((new Date(cache_data.liveData.plays.allPlays[i].playEvents[actions[x]].startTime) - broadcast_start_timestamp) / 1000) + this_pad_end + skip_adjust + default_event_duration
|
|
2139
|
-
}
|
|
2140
|
-
event_offsets.push(this_event)
|
|
2141
2132
|
}
|
|
2142
2133
|
}
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
2134
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2135
|
+
this.debuglog('getSkipMarkers found ' + new Date(total_skip_time * 1000).toISOString().substr(11, 8) + ' total skip time')
|
|
2136
|
+
}
|
|
2148
2137
|
}
|
|
2149
|
-
this.temp_cache[contentId].inning_offsets = inning_offsets
|
|
2150
2138
|
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
}
|
|
2154
|
-
this.temp_cache[contentId].event_offsets = event_offsets
|
|
2139
|
+
this.debuglog('getSkipMarkers skip markers: ' + JSON.stringify(skip_markers))
|
|
2140
|
+
this.temp_cache[contentId].skip_markers = skip_markers
|
|
2155
2141
|
|
|
2156
2142
|
return true
|
|
2157
2143
|
} catch(e) {
|
|
2158
|
-
this.log('
|
|
2144
|
+
this.log('getSkipMarkers error : ' + e.message)
|
|
2159
2145
|
}
|
|
2160
2146
|
}
|
|
2161
2147
|
|
|
@@ -2347,7 +2333,7 @@ class sessionClass {
|
|
|
2347
2333
|
break
|
|
2348
2334
|
} else {
|
|
2349
2335
|
if ( cache_data.items[i].title ) {
|
|
2350
|
-
if ( (eventName == 'BIGINNING') &&
|
|
2336
|
+
if ( (eventName == 'BIGINNING') && cache_data.items[i].title.startsWith('LIVE') && cache_data.items[i].title.includes('Big Inning') ) {
|
|
2351
2337
|
this.debuglog('active big inning url')
|
|
2352
2338
|
return cache_data.items[i].fields.url
|
|
2353
2339
|
} else if ( cache_data.items[i].title.toUpperCase().endsWith(' VS. ' + eventName) ) {
|