mlbserver 2025.6.12 → 2025.6.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/index.js +27 -40
- package/package.json +1 -1
- package/session.js +54 -14
package/index.js
CHANGED
|
@@ -365,10 +365,8 @@ app.get('/stream.m3u8', async function(req, res) {
|
|
|
365
365
|
let skip_adjust = parseInt(req.query.skip_adjust) || DEFAULT_SKIP_ADJUST
|
|
366
366
|
|
|
367
367
|
let skip_type = VALID_SKIP.indexOf(options.skip)
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
await session.getSkipMarkers(gamePk, skip_type, options.inning_number, options.inning_half, streamURL, streamURLToken, skip_adjust)
|
|
371
|
-
}
|
|
368
|
+
// look up markers
|
|
369
|
+
await session.getSkipMarkers(gamePk, skip_type, options.inning_number, options.inning_half, streamURL, streamURLToken, skip_adjust)
|
|
372
370
|
}
|
|
373
371
|
}
|
|
374
372
|
|
|
@@ -549,11 +547,6 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
549
547
|
return
|
|
550
548
|
}
|
|
551
549
|
|
|
552
|
-
// Omit subtitles when skipping
|
|
553
|
-
if ( line.startsWith('#EXT-X-MEDIA:TYPE=SUBTITLES') && (skip != 'none') ) {
|
|
554
|
-
return
|
|
555
|
-
}
|
|
556
|
-
|
|
557
550
|
// Parse audio tracks to only include matching one, if specified
|
|
558
551
|
if ( line.startsWith('#EXT-X-MEDIA:TYPE=AUDIO') ) {
|
|
559
552
|
// if we've already returned our desired audio track, we can skip subsequent ones
|
|
@@ -649,11 +642,19 @@ function getMasterPlaylist(streamURL, req, res, options = {}) {
|
|
|
649
642
|
}
|
|
650
643
|
|
|
651
644
|
// Pass through any remaining caption tracks
|
|
652
|
-
if ( line.startsWith('#EXT-X-MEDIA:TYPE=SUBTITLES,
|
|
645
|
+
if ( line.startsWith('#EXT-X-MEDIA:TYPE=SUBTITLES,') ) {
|
|
653
646
|
var parsed = line.match(',URI="([^"]+)"')
|
|
654
647
|
if ( parsed[1] ) {
|
|
655
|
-
newurl = http_root + '/playlist.m3u8?url='+encodeURIComponent(url.resolve(streamURL, parsed[1].trim()))
|
|
656
|
-
|
|
648
|
+
newurl = http_root + '/playlist.m3u8?url='+encodeURIComponent(url.resolve(streamURL, parsed[1].trim()))
|
|
649
|
+
if ( force_vod != VALID_FORCE_VOD[0] ) newurl += '&force_vod=on'
|
|
650
|
+
if ( inning_half != VALID_INNING_HALF[0] ) newurl += '&inning_half=' + inning_half
|
|
651
|
+
if ( inning_number != VALID_INNING_NUMBER[0] ) newurl += '&inning_number=' + inning_number
|
|
652
|
+
if ( skip != VALID_SKIP[0] ) newurl += '&skip=' + skip
|
|
653
|
+
if ( skip_adjust != DEFAULT_SKIP_ADJUST ) newurl += '&skip_adjust=' + skip_adjust
|
|
654
|
+
if ( pad != VALID_PAD[0] ) newurl += '&pad=' + pad
|
|
655
|
+
if ( gamePk ) newurl += '&gamePk=' + gamePk
|
|
656
|
+
newurl += content_protect + referer_parameter + token_parameter
|
|
657
|
+
return line.replace(parsed[1], newurl)
|
|
657
658
|
}
|
|
658
659
|
return
|
|
659
660
|
}
|
|
@@ -768,23 +769,7 @@ app.get('/playlist.m3u8', async function(req, res) {
|
|
|
768
769
|
content_protect = '&content_protect=' + session.protection.content_protect
|
|
769
770
|
}
|
|
770
771
|
|
|
771
|
-
|
|
772
|
-
if ( skip == 'commercials' ) {
|
|
773
|
-
session.debuglog('filtering commercial breaks')
|
|
774
|
-
let new_body = []
|
|
775
|
-
let break_active = false
|
|
776
|
-
for (var i=0; i<body.length; i++) {
|
|
777
|
-
if ( (break_active == false) && body[i].startsWith('#EXT-OATCLS-SCTE35:') ) {
|
|
778
|
-
break_active = true
|
|
779
|
-
new_body.push('#EXT-X-DISCONTINUITY')
|
|
780
|
-
} else if ( (break_active == true) && body[i].startsWith('#EXT-X-CUE-IN') ) {
|
|
781
|
-
break_active = false
|
|
782
|
-
} else if ( break_active == false ) {
|
|
783
|
-
new_body.push(body[i])
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
body = new_body
|
|
787
|
-
} else if ( (gamePk) && ((inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0])) && (typeof session.temp_cache[gamePk] !== 'undefined') && (typeof session.temp_cache[gamePk].skip_markers !== 'undefined') ) {
|
|
772
|
+
if ( (gamePk) && ((inning_half != VALID_INNING_HALF[0]) || (inning_number != VALID_INNING_NUMBER[0]) || (skip != VALID_SKIP[0])) && (typeof session.temp_cache[gamePk] !== 'undefined') && (typeof session.temp_cache[gamePk].skip_markers !== 'undefined') ) {
|
|
788
773
|
session.debuglog('pulling skip markers from temporary cache')
|
|
789
774
|
skip_markers = session.temp_cache[gamePk].skip_markers
|
|
790
775
|
} else {
|
|
@@ -978,19 +963,22 @@ app.get('/subtitles.vtt', async function(req, res) {
|
|
|
978
963
|
var u = req.query.url
|
|
979
964
|
session.debuglog('subtitles.vtt url : ' + u)
|
|
980
965
|
|
|
966
|
+
var headers = {}
|
|
967
|
+
|
|
981
968
|
var referer = false
|
|
982
969
|
if ( req.query.referer ) {
|
|
970
|
+
session.debuglog('found subtitles.vtt referer : ' + req.query.referer)
|
|
983
971
|
referer = decodeURIComponent(req.query.referer)
|
|
984
|
-
|
|
972
|
+
headers.referer = referer
|
|
973
|
+
headers.origin = getOriginFromURL(referer)
|
|
985
974
|
}
|
|
986
975
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
headers.origin = getOriginFromURL(referer)
|
|
992
|
-
}
|
|
976
|
+
if ( req.query.streamURLToken ) {
|
|
977
|
+
token = decodeURIComponent(req.query.streamURLToken)
|
|
978
|
+
headers['x-cdn-token'] = token
|
|
979
|
+
}
|
|
993
980
|
|
|
981
|
+
var req = function () {
|
|
994
982
|
requestRetry(u, headers, function(err, response) {
|
|
995
983
|
if (err) return res.error(err)
|
|
996
984
|
|
|
@@ -1689,7 +1677,6 @@ app.get('/', async function(req, res) {
|
|
|
1689
1677
|
let compareEnd = new Date(big_inning.end)
|
|
1690
1678
|
compareEnd.setHours(compareEnd.getHours()+1)
|
|
1691
1679
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
1692
|
-
body += '<tr><td><span class="tooltip">Big Inning<span class="tooltiptext">Big Inning is the live look-in and highlights show. <a href="https://support.mlb.com/s/article/What-Is-MLB-Big-Inning">See here for more information</a>.</span></span></td><td>'
|
|
1693
1680
|
let querystring = '?event=biginning'
|
|
1694
1681
|
let multiviewquerystring = querystring + '&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION
|
|
1695
1682
|
if ( linkType == VALID_LINK_TYPES[0] ) {
|
|
@@ -1725,8 +1712,8 @@ app.get('/', async function(req, res) {
|
|
|
1725
1712
|
compareEnd.setHours(compareEnd.getHours()+4)
|
|
1726
1713
|
body += '<tr><td><span class="tooltip">' + compareStart.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + ' - ' + compareEnd.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }) + '<span class="tooltiptext">The game changer stream will automatically switch between the highest leverage active live non-blackout games, and should be available whenever there are such games available. Does not support adaptive bitrate switching, will default to 720p60 resolution if not specified.</span></span></td><td>'
|
|
1727
1714
|
if ( (currentDate >= compareStart) && (currentDate < compareEnd) ) {
|
|
1728
|
-
let streamURL = server + '/gamechanger.m3u8
|
|
1729
|
-
let multiviewquerystring =
|
|
1715
|
+
let streamURL = server + '/gamechanger.m3u8'
|
|
1716
|
+
let multiviewquerystring = '/gamechanger.m3u8?resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
|
|
1730
1717
|
streamURL += content_protect_a
|
|
1731
1718
|
if ( resolution != VALID_RESOLUTIONS[0] ) streamURL += 'resolution=' + resolution + '&'
|
|
1732
1719
|
if ( linkType != VALID_LINK_TYPES[1] ) {
|
|
@@ -2214,7 +2201,7 @@ app.get('/', async function(req, res) {
|
|
|
2214
2201
|
}
|
|
2215
2202
|
body += '</p>' + "\n"
|
|
2216
2203
|
|
|
2217
|
-
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 all breaks, idle time, non-action pitches, or only commercial breaks 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
|
|
2204
|
+
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 all breaks, idle time, non-action pitches, or only commercial breaks 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>: '
|
|
2218
2205
|
for (var i = 0; i < VALID_SKIP.length; i++) {
|
|
2219
2206
|
body += '<button '
|
|
2220
2207
|
if ( skip == VALID_SKIP[i] ) body += 'class="default" '
|
package/package.json
CHANGED
package/session.js
CHANGED
|
@@ -3036,10 +3036,10 @@ class sessionClass {
|
|
|
3036
3036
|
}
|
|
3037
3037
|
}
|
|
3038
3038
|
|
|
3039
|
-
// Get
|
|
3040
|
-
async
|
|
3039
|
+
// Get variant playlist
|
|
3040
|
+
async getVariantPlaylist(streamURL, streamURLToken) {
|
|
3041
3041
|
try {
|
|
3042
|
-
this.debuglog('
|
|
3042
|
+
this.debuglog('getVariantPlaylist')
|
|
3043
3043
|
|
|
3044
3044
|
// MLB version
|
|
3045
3045
|
let variant = '_5600K'
|
|
@@ -3059,6 +3059,19 @@ class sessionClass {
|
|
|
3059
3059
|
}
|
|
3060
3060
|
var response = await this.httpGet(reqObj, false)
|
|
3061
3061
|
var body = response.replace(/^\s+|\s+$/g, '').split('\n')
|
|
3062
|
+
|
|
3063
|
+
return body
|
|
3064
|
+
} catch(e) {
|
|
3065
|
+
this.log('getVariantPlaylist error : ' + e.message)
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
// Get broadcast start timestamp
|
|
3070
|
+
async getBroadcastStart(variantPlaylist) {
|
|
3071
|
+
try {
|
|
3072
|
+
this.debuglog('getBroadcastStart')
|
|
3073
|
+
|
|
3074
|
+
var body = variantPlaylist
|
|
3062
3075
|
|
|
3063
3076
|
// check if HLS
|
|
3064
3077
|
if ( body[0] != '#EXTM3U' ) {
|
|
@@ -3103,7 +3116,8 @@ class sessionClass {
|
|
|
3103
3116
|
let break_start = 0
|
|
3104
3117
|
|
|
3105
3118
|
// Get the broadcast start time first -- event times will be relative to this
|
|
3106
|
-
let
|
|
3119
|
+
let variantPlaylist = await this.getVariantPlaylist(streamURL, streamURLToken)
|
|
3120
|
+
let broadcast_start_timestamp = await this.getBroadcastStart(variantPlaylist)
|
|
3107
3121
|
|
|
3108
3122
|
if ( broadcast_start_timestamp ) {
|
|
3109
3123
|
this.debuglog('getSkipMarkers broadcast start detected as ' + broadcast_start_timestamp)
|
|
@@ -3145,8 +3159,8 @@ class sessionClass {
|
|
|
3145
3159
|
// Loop through all plays
|
|
3146
3160
|
for (var i=0; i < cache_data.liveData.plays.allPlays.length; i++) {
|
|
3147
3161
|
|
|
3148
|
-
// exit loop after found inning, if not skipping any breaks
|
|
3149
|
-
if ((skip_type == 0) && (skip_markers.length == 1)) {
|
|
3162
|
+
// exit loop after found inning, if not skipping any play-defined breaks
|
|
3163
|
+
if ( ((skip_type == 0) || (skip_type == 4)) && (skip_markers.length == 1) ) {
|
|
3150
3164
|
break
|
|
3151
3165
|
}
|
|
3152
3166
|
|
|
@@ -3170,8 +3184,8 @@ class sessionClass {
|
|
|
3170
3184
|
event_end_padding = pitch_end_padding
|
|
3171
3185
|
}
|
|
3172
3186
|
let action_index
|
|
3173
|
-
// skip type 0 (none, inning start)
|
|
3174
|
-
if ((skip_type <= 1) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime) {
|
|
3187
|
+
// skip type 0 (none, inning start), 1 (breaks), and 4 (commercials) will look at all plays with an endTime
|
|
3188
|
+
if ( ((skip_type <= 1) || (skip_type == 4)) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime ) {
|
|
3175
3189
|
action_index = j
|
|
3176
3190
|
// skip type 2 (idle time) will look at all non-idle plays with an endTime
|
|
3177
3191
|
} else if ((skip_type == 2) && cache_data.liveData.plays.allPlays[i].playEvents[j].endTime && (!cache_data.liveData.plays.allPlays[i].playEvents[j].details || !cache_data.liveData.plays.allPlays[i].playEvents[j].details.description || !IDLE_TYPES.some(v => cache_data.liveData.plays.allPlays[i].playEvents[j].details.description.includes(v)))) {
|
|
@@ -3217,8 +3231,8 @@ class sessionClass {
|
|
|
3217
3231
|
total_skip_time += break_end - break_start
|
|
3218
3232
|
previous_inning = current_inning
|
|
3219
3233
|
previous_inning_half = current_inning_half
|
|
3220
|
-
// exit loop after found inning, if not skipping breaks
|
|
3221
|
-
if (skip_type == 0) {
|
|
3234
|
+
// exit loop after found inning, if not skipping play-defined breaks
|
|
3235
|
+
if ( (skip_type == 0) || (skip_type == 4)) {
|
|
3222
3236
|
break
|
|
3223
3237
|
}
|
|
3224
3238
|
}
|
|
@@ -3243,6 +3257,32 @@ class sessionClass {
|
|
|
3243
3257
|
}
|
|
3244
3258
|
}
|
|
3245
3259
|
}
|
|
3260
|
+
|
|
3261
|
+
// if skipping commercials, look at the variant playlist to detect insertions
|
|
3262
|
+
if ( skip_type == 4 ) {
|
|
3263
|
+
this.debuglog('detecting commercial breaks')
|
|
3264
|
+
let body = variantPlaylist
|
|
3265
|
+
let break_active = false
|
|
3266
|
+
let break_end = 0
|
|
3267
|
+
let time_counter = 0
|
|
3268
|
+
if ( skip_markers.length > 0 ) {
|
|
3269
|
+
break_end = skip_markers[skip_markers.length-1].break_end
|
|
3270
|
+
}
|
|
3271
|
+
for (var i=0; i<body.length; i++) {
|
|
3272
|
+
if ( body[i].startsWith('#EXTINF:') ) {
|
|
3273
|
+
time_counter += parseFloat(body[i].substring(8, body[i].length-1))
|
|
3274
|
+
}
|
|
3275
|
+
if ( (time_counter > break_end) && (break_active == false) && body[i].startsWith('#EXT-OATCLS-SCTE35:') ) {
|
|
3276
|
+
break_active = true
|
|
3277
|
+
break_start = time_counter
|
|
3278
|
+
} else if ( (break_active == true) && body[i].startsWith('#EXT-X-CUE-IN') ) {
|
|
3279
|
+
break_end = time_counter
|
|
3280
|
+
break_active = false
|
|
3281
|
+
skip_markers.push({'break_start': break_start, 'break_end': break_end})
|
|
3282
|
+
total_skip_time += break_end - break_start
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3246
3286
|
|
|
3247
3287
|
this.debuglog('getSkipMarkers found ' + new Date(total_skip_time * 1000).toISOString().substr(11, 8) + ' total skip time')
|
|
3248
3288
|
}
|
|
@@ -3299,13 +3339,13 @@ class sessionClass {
|
|
|
3299
3339
|
|
|
3300
3340
|
if ( response.data ) {
|
|
3301
3341
|
for (var i=0; i < response.data.length; i++) {
|
|
3302
|
-
if ( response.data[i].airings && (response.data[i].airings.length > 0) && response.data[i].airings[0] && response.data[i].airings[0].
|
|
3303
|
-
let est_date = new Date(response.data[i].airings[0].
|
|
3342
|
+
if ( response.data[i].airings && (response.data[i].airings.length > 0) && response.data[i].airings[0] && response.data[i].airings[0].accessRightsV2 && response.data[i].airings[0].accessRightsV2.live ) {
|
|
3343
|
+
let est_date = new Date(response.data[i].airings[0].accessRightsV2.live.startTime).toLocaleString("en-US", {timeZone: 'America/New_York'})
|
|
3304
3344
|
let date_array = est_date.split(',')[0].split('/')
|
|
3305
3345
|
let this_datestring = date_array[2] + '-' + date_array[0].padStart(2, '0') + '-' + date_array[1].padStart(2, '0')
|
|
3306
3346
|
this.cache.bigInningSchedule[this_datestring] = {
|
|
3307
|
-
start: response.data[i].airings[0].
|
|
3308
|
-
end: response.data[i].airings[0].
|
|
3347
|
+
start: response.data[i].airings[0].accessRightsV2.live.startTime,
|
|
3348
|
+
end: response.data[i].airings[0].accessRightsV2.live.endTime
|
|
3309
3349
|
}
|
|
3310
3350
|
}
|
|
3311
3351
|
}
|