mlbserver 2025.7.18 → 2025.7.21

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.
Files changed (3) hide show
  1. package/index.js +159 -5
  2. package/package.json +1 -1
  3. package/session.js +33 -25
package/index.js CHANGED
@@ -194,8 +194,19 @@ if (argv.http_root) {
194
194
  req.url = req.url.substr(http_root.length, (req.url.length - http_root.length))
195
195
  app.route(req, res);
196
196
  } catch (e) {
197
- session.log('http_root request error : ' + e.message)
198
- res.end('http_root request error, check log')
197
+ session.log('http_root get request error : ' + e.message)
198
+ res.end('http_root get request error, check log')
199
+ }
200
+ })
201
+ app.post(http_root + '/*', async function(req, res) {
202
+ if ( ! (await protect(req, res)) ) return
203
+
204
+ try {
205
+ req.url = req.url.substr(http_root.length, (req.url.length - http_root.length))
206
+ app.route(req, res);
207
+ } catch (e) {
208
+ session.log('http_root post request error : ' + e.message)
209
+ res.end('http_root post request error, check log')
199
210
  }
200
211
  })
201
212
  }
@@ -1736,7 +1747,11 @@ app.get('/', async function(req, res) {
1736
1747
  let multiviewquerystring = '/gamechanger.m3u8?resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
1737
1748
  streamURL += content_protect_a
1738
1749
  if ( resolution != VALID_RESOLUTIONS[0] ) streamURL += '&resolution=' + resolution
1739
- if ( linkType != VALID_LINK_TYPES[1] ) {
1750
+ // force Video.js embed player for Game Changer
1751
+ if ( linkType == VALID_LINK_TYPES[0] ) {
1752
+ let mylink = http_root + '/embed-videojs.html'
1753
+ streamURL = mylink + '?src=' + encodeURIComponent(streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b
1754
+ } else if ( linkType != VALID_LINK_TYPES[1] ) {
1740
1755
  streamURL = thislink + '?src=' + encodeURIComponent(streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b
1741
1756
  }
1742
1757
  if ( linkType == VALID_LINK_TYPES[4] ) {
@@ -1755,7 +1770,11 @@ app.get('/', async function(req, res) {
1755
1770
  let multiviewquerystring = '/gamechanger.m3u8?streamFinder=on&resolution=' + DEFAULT_MULTIVIEW_RESOLUTION + content_protect_b
1756
1771
  streamURL += content_protect_b
1757
1772
  if ( resolution != VALID_RESOLUTIONS[0] ) streamURL += '&resolution=' + resolution
1758
- if ( linkType != VALID_LINK_TYPES[1] ) {
1773
+ // force Video.js embed player for Stream Finder
1774
+ if ( linkType == VALID_LINK_TYPES[0] ) {
1775
+ let mylink = http_root + '/embed-videojs.html'
1776
+ streamURL = mylink + '?src=' + encodeURIComponent(streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b
1777
+ } else if ( linkType != VALID_LINK_TYPES[1] ) {
1759
1778
  streamURL = thislink + '?src=' + encodeURIComponent(streamURL) + '&startFrom=' + VALID_START_FROM[1] + content_protect_b
1760
1779
  }
1761
1780
  if ( linkType == VALID_LINK_TYPES[4] ) {
@@ -2462,7 +2481,142 @@ app.options('*', function(req, res) {
2462
2481
  return
2463
2482
  })
2464
2483
 
2484
+ // Listen for Video.js embed request, respond with embedded Video.js player
2485
+ // used for Game Changer and Stream Finder
2486
+ app.get('/embed-videojs.html', async function(req, res) {
2487
+ if ( ! (await protect(req, res)) ) return
2488
+
2489
+ session.requestlog('embed-videojs.html', req)
2490
+
2491
+ let startFrom = VALID_START_FROM[0]
2492
+ if ( req.query.startFrom ) {
2493
+ startFrom = req.query.startFrom
2494
+ }
2495
+ let controls = VALID_CONTROLS[0]
2496
+ if ( req.query.controls ) {
2497
+ controls = req.query.controls
2498
+ }
2499
+
2500
+ let video_url = http_root + '/stream.m3u8'
2501
+ if ( req.query.src ) {
2502
+ video_url = req.query.src
2503
+ } else if ( req.query.msrc ) {
2504
+ video_url += '?'
2505
+ if ( req.query.content_protect ) {
2506
+ video_url += 'content_protect=' + req.query.content_protect + '&'
2507
+ }
2508
+ video_url += 'src=' + req.query.msrc
2509
+ } else {
2510
+ let urlArray = req.url.split('?')
2511
+ if ( (urlArray.length == 2) ) {
2512
+ video_url += '?' + urlArray[1]
2513
+ }
2514
+ }
2515
+ session.debuglog('embed-videojs src : ' + video_url)
2516
+
2517
+ let content_protect = ''
2518
+ if ( session.protection.content_protect ) {
2519
+ content_protect = '?content_protect=' + session.protection.content_protect
2520
+ }
2521
+
2522
+ var body = `<html>
2523
+ <head>
2524
+ <meta charset="UTF-8">
2525
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
2526
+ <title>` + appname + ` player</title>
2527
+ <link rel="icon" href="favicon.svg` + content_protect + `">
2528
+ <style type="text/css">
2529
+ body {
2530
+ background-color:black;
2531
+ color:lightgrey;
2532
+ font-family:Arial,Helvetica,sans-serif;
2533
+ }
2534
+ .video-wrapper {
2535
+ height: 94vh;
2536
+ display: flex;
2537
+ align-items: center;
2538
+ justify-content: center;
2539
+ overflow: hidden;
2540
+ }
2541
+ button {
2542
+ color:lightgray;
2543
+ background-color:black;
2544
+ }
2545
+ </style>
2546
+ <script>
2547
+ function goBack() {
2548
+ var prevPage=window.location.href;
2549
+ window.history.go(-1);
2550
+ setTimeout(function(){if(window.location.href==prevPage){window.location.href="` + http_root + '/' + content_protect + `"}}, 500);
2551
+ }
2552
+ </script>
2553
+ <link href="https://vjs.zencdn.net/8.23.3/video-js.css" rel="stylesheet" />
2554
+ </head>
2555
+ <body onkeydown="myKeyPress(event)">
2556
+ <div class="video-wrapper">
2557
+ <video id="video" class="video-js vjs-fill"`
2558
+ if ( controls == VALID_CONTROLS[0] ) {
2559
+ body += ' controls'
2560
+ }
2561
+ body += ` muted>
2562
+ <source
2563
+ src="` + video_url + `"
2564
+ type="application/x-mpegURL">
2565
+ </video>
2566
+ </div>
2567
+ <script src="https://vjs.zencdn.net/8.23.3/video.min.js"></script>
2568
+ <script>
2569
+ var player = videojs('video', {
2570
+ "liveui": true,
2571
+ "playbackRates": [0.5, 1, 1.25, 1.5, 2]
2572
+ });
2573
+ function changeTime(skipAmount) {
2574
+ player.currentTime(player.currentTime() + parseInt(skipAmount));
2575
+ }
2576
+ var skipAmounts = ['-30s', '-10s', '+10s', '+30s', '+90s', '+120s'];
2577
+ for (var i=0; i<skipAmounts.length; i++) {
2578
+ player.getChild('ControlBar').addChild('button', {
2579
+ controlText: skipAmounts[i],
2580
+ className: 'vjs-visible-text',
2581
+ clickHandler: function(event) {
2582
+ changeTime(this.controlText());
2583
+ }
2584
+ });
2585
+ }
2586
+ player.ready(function() {
2587
+ `;
2588
+
2589
+ if ( startFrom != VALID_START_FROM[1] ) {
2590
+ body += `this.currentTime(0);
2591
+ `;
2592
+ }
2593
+
2594
+ body += `this.play();
2595
+ });
2596
+ function changeRate(x) {
2597
+ let newrate = parseFloat((player.playbackRate() + x).toFixed(1));
2598
+ player.playbackRate(newrate);
2599
+ }
2600
+ function myKeyPress(e) {
2601
+ if (e.key=="ArrowRight") {
2602
+ changeTime(10);
2603
+ } else if (e.key=="ArrowLeft") {
2604
+ changeTime(-10);
2605
+ } else if (e.key=="ArrowUp") {
2606
+ changeRate(0.1);
2607
+ } else if (e.key=="ArrowDown") {
2608
+ changeRate(-0.1);
2609
+ }
2610
+ }
2611
+ </script>
2612
+ <p><button onclick="goBack()">Back</button></p>
2613
+ </body>
2614
+ </html>`
2615
+ res.end(body)
2616
+ })
2617
+
2465
2618
  // Listen for embed request, respond with embedded hls.js player
2619
+ // used for everything except Game Changer and Stream Finder
2466
2620
  app.get('/embed.html', async function(req, res) {
2467
2621
  if ( ! (await protect(req, res)) ) return
2468
2622
 
@@ -3288,7 +3442,7 @@ app.post('/upload', async function(req, res) {
3288
3442
 
3289
3443
  req.on('body', function(body) {
3290
3444
  session.parse_stream_finder_settings(body)
3291
- let response = '<p><b>SUCCESS!</b></p><p>Your Stream Finder settings have been saved in mlbserver.</p><p>Select the Stream Finder stream to use them, or <a href="/upload">click here</a> to replace them with new settings.</p>'
3445
+ let response = '<p><b>SUCCESS!</b></p><p>Your Stream Finder settings have been saved in mlbserver.</p><p>Select the Stream Finder stream to use them, or <a href="' + http_root + '/#streamfinder">click here</a> to replace them with new settings.</p>'
3292
3446
  res.end(response)
3293
3447
  })
3294
3448
  } catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mlbserver",
3
- "version": "2025.07.18",
3
+ "version": "2025.07.21",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
package/session.js CHANGED
@@ -4074,9 +4074,9 @@ class sessionClass {
4074
4074
 
4075
4075
  setCurrentGame(cur_game_pk, game, team_data, game_changer_title, priority) {
4076
4076
  if ( cur_game_pk != game.game_pk ) {
4077
- this.debuglog(game_changer_title + ' set current game to ' + this.getCurrentGame(game, team_data) + ' due to ' + priority.type + ' ' + priority.data)
4078
- return {'game_pk': game.game_pk, 'cur_batter': game.batter}
4077
+ this.debuglog(game_changer_title + 'set current game to ' + this.getCurrentGame(game, team_data) + ' due to ' + priority.type + ' ' + priority.data)
4079
4078
  }
4079
+ return {'game_pk': game.game_pk, 'cur_batter': game.batter}
4080
4080
  }
4081
4081
 
4082
4082
  sleep(ms) {
@@ -4133,11 +4133,7 @@ class sessionClass {
4133
4133
  for (var i=0; i<cache_data.dates[0].games.length; i++) {
4134
4134
  let game = cache_data.dates[0].games[i]
4135
4135
 
4136
- let gamePk = game.gamePk.toString()
4137
- let broadcast_count = await this.count_broadcasts(game.broadcasts, 'MLBTV')
4138
- if ( this.temp_cache.gamechanger.blackouts[gamePk] && this.temp_cache.gamechanger.blackouts[gamePk].blackout_feeds && (this.temp_cache.gamechanger.blackouts[gamePk].blackout_feeds.length == broadcast_count) ) {
4139
- continue
4140
- }
4136
+ let gamePk = game.gamePk.toString()
4141
4137
 
4142
4138
  if (!game.linescore) {
4143
4139
  continue
@@ -4163,9 +4159,12 @@ class sessionClass {
4163
4159
  if (game.linescore.outs) {
4164
4160
  outs = game.linescore.outs
4165
4161
  }
4166
- let status = 'scheduled'
4167
- if ( game.status && game.status.detailedState && (game.status.detailedState == 'In Progress') ) {
4168
- status = 'active'
4162
+ let status = 'unknown'
4163
+ let broadcast_count = await this.count_broadcasts(game.broadcasts, 'MLBTV')
4164
+ if ( this.temp_cache.gamechanger.blackouts[gamePk] && this.temp_cache.gamechanger.blackouts[gamePk].blackout_feeds && (this.temp_cache.gamechanger.blackouts[gamePk].blackout_feeds.length == broadcast_count) ) {
4165
+ status = 'blackout'
4166
+ } else if ( game.status && game.status.detailedState ) {
4167
+ status = game.status.detailedState
4169
4168
  }
4170
4169
  let half = 1
4171
4170
  if ( game.linescore.inningHalf && (game.linescore.inningHalf == 'Bottom') ) {
@@ -4283,6 +4282,8 @@ class sessionClass {
4283
4282
  }
4284
4283
  })
4285
4284
  }
4285
+
4286
+ this.debuglog(game_changer_title + 'games ' + JSON.stringify(games))
4286
4287
 
4287
4288
  let active_games = []
4288
4289
  let same_batter = 'N'
@@ -4299,18 +4300,22 @@ class sessionClass {
4299
4300
  let gamePk = game.gamePk.toString()
4300
4301
 
4301
4302
  if ( !team_data[game.teams.away.team.id.toString()] || !team_data[game.teams.home.team.id.toString()] ) {
4303
+ this.debuglog(game_changer_title + 'invalid team(s) in game ' + gamePk)
4302
4304
  continue
4303
4305
  }
4304
4306
 
4305
4307
  if ( game.linescore.outs == 3 ) {
4308
+ this.debuglog(game_changer_title + 'end of half inning in game ' + gamePk)
4306
4309
  continue
4307
4310
  }
4308
4311
 
4309
4312
  if ( game.linescore.inningState && !['Top', 'Bottom'].includes(game.linescore.inningState) ) {
4313
+ this.debuglog(game_changer_title + 'between innings in game ' + gamePk)
4310
4314
  continue
4311
4315
  }
4312
4316
 
4313
- if ( !game.status || (game.status != 'active') ) {
4317
+ if ( !game.status || (game.status != 'In Progress') ) {
4318
+ this.debuglog(game_changer_title + 'non-active game ' + gamePk)
4314
4319
  continue
4315
4320
  }
4316
4321
 
@@ -4319,6 +4324,7 @@ class sessionClass {
4319
4324
  for (var j=0; j<this.stream_finder_settings.ignore.length; j++) {
4320
4325
  let ignore_team = this.stream_finder_settings.ignore[j]
4321
4326
  if ( (ignore_team == game.teams.away.team.id.toString()) || (ignore_team == game.teams.home.team.id.toString()) ) {
4327
+ this.debuglog(game_changer_title + 'ignoring team in game ' + gamePk)
4322
4328
  ignore = true
4323
4329
  break
4324
4330
  }
@@ -4330,10 +4336,10 @@ class sessionClass {
4330
4336
 
4331
4337
  if ( this.temp_cache.gamechanger[id].break_expiries[gamePk] ) {
4332
4338
  if ( this.temp_cache.gamechanger[id].break_expiries[gamePk] > now ) {
4333
- this.debuglog(game_changer_title + ' pitching change in progress in game ' + gamePk)
4339
+ this.debuglog(game_changer_title + 'pitching change in progress in game ' + gamePk)
4334
4340
  continue
4335
4341
  } else {
4336
- this.debuglog(game_changer_title + ' pitching change complete in game ' + gamePk)
4342
+ this.debuglog(game_changer_title + 'pitching change complete in game ' + gamePk)
4337
4343
  this.removeBreakExpiry(id, gamePk)
4338
4344
  }
4339
4345
  }
@@ -4351,14 +4357,13 @@ class sessionClass {
4351
4357
  this.temp_cache.gamechanger[id].cur_pitchers[gamePk] = {}
4352
4358
  }
4353
4359
  if ( !this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] ) {
4360
+ this.debuglog(game_changer_title + 'storing pitcher ' + game.linescore.defense.pitcher.id + ' for team ' + pitching_team_id + ' in game ' + gamePk)
4354
4361
  this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] = game.linescore.defense.pitcher.id
4355
- } else {
4356
- if ( game.linescore.defense.pitcher.id != this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] ) {
4357
- this.debuglog(game_changer_title + ' pitching change begun in game ' + gamePk)
4358
- this.setBreakExpiry(id, gamePk)
4359
- this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] = game.linescore.defense.pitcher.id
4360
- continue
4361
- }
4362
+ } else if ( game.linescore.defense.pitcher.id != this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] ) {
4363
+ this.debuglog(game_changer_title + 'pitching change begun in game ' + gamePk)
4364
+ this.setBreakExpiry(id, gamePk)
4365
+ this.temp_cache.gamechanger[id].cur_pitchers[gamePk][pitching_team_id] = game.linescore.defense.pitcher.id
4366
+ continue
4362
4367
  }
4363
4368
  }
4364
4369
 
@@ -4391,6 +4396,7 @@ class sessionClass {
4391
4396
 
4392
4397
  // Flag to stay on current game if same batter is still at bat
4393
4398
  if ( (game.gamePk == this.temp_cache.gamechanger[id].cur_game_pk) && (game.linescore.offense.batter.id == this.temp_cache.gamechanger[id].cur_batter) && (game.linescore.outs < 3) && ((game.linescore.balls && ![0,4].includes(game.linescore.balls)) || (game.linescore.strikes && ![0,3].includes(game.linescore.strikes))) ) {
4399
+ this.debuglog(game_changer_title + 'same batter is up in current game ' + gamePk)
4394
4400
  same_batter = 'Y'
4395
4401
  }
4396
4402
 
@@ -4425,7 +4431,7 @@ class sessionClass {
4425
4431
  }
4426
4432
 
4427
4433
  if ( !this.temp_cache.gamechanger[id].cur_game_pk && (active_games.length == 0) ) {
4428
- this.debuglog(game_changer_title + ' no active games')
4434
+ this.debuglog(game_changer_title + 'no active games')
4429
4435
  return
4430
4436
  }
4431
4437
 
@@ -4438,14 +4444,16 @@ class sessionClass {
4438
4444
  return b.LI - a.LI;
4439
4445
  })
4440
4446
 
4441
- this.debuglog(game_changer_title + ' active games ' + JSON.stringify(games))
4442
- this.debuglog(game_changer_title + ' current pitchers ' + JSON.stringify(this.temp_cache.gamechanger[id].cur_pitchers))
4443
- this.debuglog(game_changer_title + ' pitching changes ' + JSON.stringify(this.temp_cache.gamechanger[id].break_expiries))
4447
+ this.debuglog(game_changer_title + 'active games ' + JSON.stringify(games))
4448
+ this.debuglog(game_changer_title + 'current pitchers ' + JSON.stringify(this.temp_cache.gamechanger[id].cur_pitchers))
4449
+ this.debuglog(game_changer_title + 'pitching changes ' + JSON.stringify(this.temp_cache.gamechanger[id].break_expiries))
4450
+ this.debuglog(game_changer_title + 'same batter? ' + same_batter)
4444
4451
 
4445
4452
  let game_pk = null
4446
4453
  let game_info = null
4447
4454
  if ( this.stream_finder_settings.priority ) {
4448
4455
  for (const [key, priority] of Object.entries(this.stream_finder_settings.priority)) {
4456
+ this.debuglog(game_changer_title + 'checking priority ' + JSON.stringify(priority))
4449
4457
  if ( (same_batter == 'N') || (priority.immediate == 'Y') ) {
4450
4458
  for (var i=0; i<games.length; i++) {
4451
4459
  let game = games[i]
@@ -4564,7 +4572,7 @@ class sessionClass {
4564
4572
  if (game_pk != this.temp_cache.gamechanger[id].cur_game_pk) {
4565
4573
  // delay untested, disabled
4566
4574
  /*if ( this.temp_cache.gamechanger[id].cur_game_pk && this.stream_finder_settings.delay && (parseInt(this.stream_finder_settings.delay) > 0) ) {
4567
- this.debuglog(game_changer_title + ' delaying switch by ' + this.stream_finder_settings.delay)
4575
+ this.debuglog(game_changer_title + 'delaying switch by ' + this.stream_finder_settings.delay)
4568
4576
  await this.sleep(this.stream_finder_settings.delay)
4569
4577
  }*/
4570
4578
  this.log(game_changer_title + 'loading game ' + game_pk)