Skip to content

Commit a92be70

Browse files
authored
Merge pull request #4651 from eracknaphobia/plugin.video.mlbtv@matrix
[plugin.video.mlbtv@matrix] 2025.4.8+matrix.1
2 parents ff10d48 + 5b70571 commit a92be70

8 files changed

Lines changed: 379 additions & 210 deletions

File tree

plugin.video.mlbtv/addon.xml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<addon id="plugin.video.mlbtv" name="MLB.TV®" version="2025.3.18+matrix.1" provider-name="eracknaphobia, tonywagner">
2+
<addon id="plugin.video.mlbtv" name="MLB.TV®" version="2025.4.8+matrix.1" provider-name="eracknaphobia, tonywagner">
33
<requires>
44
<import addon="xbmc.python" version="3.0.0"/>
55
<import addon="script.module.pytz" />
@@ -22,9 +22,12 @@
2222
</description>
2323
<disclaimer lang="en_GB">Requires an MLB.tv account</disclaimer>
2424
<news>
25-
- ability to change skip timing adjustment settings during playback
26-
- initial 2025 updates
27-
- fix for Japan exhibitions
25+
- automatic blackout/entitlement detection
26+
- support for SNLA and SNY subscriptions
27+
- skip timing, game changer, and hide scores ticker updates for 2025
28+
- jump to live when skipping start dialog and allowing spoilers
29+
- restored Big Inning schedule
30+
- fixed Catch Up option
2831
</news>
2932
<language>en</language>
3033
<platform>all</platform>

plugin.video.mlbtv/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@
162162
elif mode == 301:
163163
featured_stream_select(featured_video, name, description, start_inning, game_pk)
164164

165+
# Linear channel stream select
166+
elif mode == 302:
167+
linear_channel_stream_select(featured_video, name, description)
168+
165169
# Logout
166170
elif mode == 400:
167171
from resources.lib.account import Account

plugin.video.mlbtv/resources/language/resource.language.en_gb/strings.po

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,4 +420,20 @@ msgctxt "#30439"
420420
msgid "MLB Network live stream"
421421
msgstr ""
422422

423+
msgctxt "#30440"
424+
msgid "SportsNet LA"
425+
msgstr ""
426+
427+
msgctxt "#30441"
428+
msgid "SNLA live stream"
429+
msgstr ""
430+
431+
msgctxt "#30442"
432+
msgid "SNY"
433+
msgstr ""
434+
435+
msgctxt "#30443"
436+
msgid "SNY live stream"
437+
msgstr ""
438+
423439

plugin.video.mlbtv/resources/lib/account.py

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import time, uuid
66
import random
77
import string
8+
import base64
89

910
if sys.version_info[0] > 2:
1011
from urllib.parse import quote
@@ -67,6 +68,7 @@ def login(self):
6768
self.addon.setSetting('device_id', '')
6869
self.addon.setSetting('session_key', '')
6970
self.addon.setSetting('entitlements', '')
71+
self.addon.setSetting('okta_id', '')
7072
sys.exit()
7173

7274

@@ -77,6 +79,7 @@ def logout(self):
7779
self.addon.setSetting('device_id', '')
7880
self.addon.setSetting('session_key', '')
7981
self.addon.setSetting('entitlements', '')
82+
self.addon.setSetting('okta_id', '')
8083
self.addon.setSetting('username', '')
8184
self.addon.setSetting('password', '')
8285

@@ -92,7 +95,7 @@ def login_token(self):
9295
def access_token(self):
9396
return self.login_token()
9497

95-
def get_stream(self, content_id):
98+
def get_playback(self, content_id):
9699
if self.addon.getSetting('device_id') == '' or self.addon.getSetting('session_key') == '':
97100
self.get_device_session_id()
98101
headers = {
@@ -167,6 +170,12 @@ def get_stream(self, content_id):
167170
stream_url = r.json()['data']['initPlaybackSession']['playback']['url']
168171
xbmc.log(f'Stream URL: {stream_url}')
169172
headers = 'User-Agent=' + UA_PC
173+
token = r.json()['data']['initPlaybackSession']['playback']['token']
174+
xbmc.log(f'Token: {token}')
175+
return stream_url, headers, token
176+
177+
def get_stream(self, content_id):
178+
stream_url, headers, token = self.get_playback(content_id)
170179
return stream_url, headers
171180

172181
def get_device_session_id(self):
@@ -240,4 +249,141 @@ def get_broadcast_start_time(self, stream_url):
240249
return parse(line[25:])
241250
except Exception as e:
242251
xbmc.log('error getting get_broadcast_start_time ' + str(e))
243-
return None
252+
return None
253+
254+
def okta_id(self):
255+
if self.addon.getSetting('okta_id') == '':
256+
self.get_okta_id()
257+
return self.addon.getSetting('okta_id')
258+
259+
def get_okta_id(self):
260+
try:
261+
# get a new playback token for a past free game
262+
url, headers, token = self.get_playback('b7f0fff7-266f-4171-aa2d-af7988dc9302')
263+
if token:
264+
encoded_okta_id = token.split('_')[1]
265+
okta_id = base64.b64decode(encoded_okta_id.encode('ascii') + b'==').decode('ascii')
266+
self.addon.setSetting('okta_id', okta_id)
267+
except Exception as e:
268+
xbmc.log('error getting okta_id ' + str(e))
269+
sys.exit(0)
270+
271+
def get_event_stream(self, url):
272+
xbmc.log('fetching event video stream from url')
273+
274+
headers = {
275+
'User-Agent': UA_PC,
276+
'Authorization': 'Bearer ' + self.login_token(),
277+
'Accept': '*/*',
278+
'Origin': 'https://www.mlb.com',
279+
'Referer': 'https://www.mlb.com'
280+
}
281+
r = requests.get(url, headers=headers, verify=VERIFY)
282+
283+
text_source = r.text
284+
#xbmc.log(text_source)
285+
286+
# sometimes the returned content is already a stream playlist
287+
if text_source.startswith('#EXTM3U'):
288+
xbmc.log('video url is already a stream playlist')
289+
video_stream_url = url
290+
# otherwise it is JSON containing the stream URL
291+
else:
292+
json_source = r.json()
293+
if 'data' in json_source and len(json_source['data']) > 0 and 'value' in json_source['data'][0]:
294+
video_stream_url = json_source['data'][0]['value']
295+
xbmc.log('found video stream url : ' + video_stream_url)
296+
return video_stream_url
297+
298+
299+
def get_linear_stream(self, network):
300+
if self.addon.getSetting('device_id') == '' or self.addon.getSetting('session_key') == '':
301+
self.get_device_session_id()
302+
url = 'https://media-gateway.mlb.com/graphql'
303+
headers = {
304+
'User-Agent': UA_PC,
305+
'Authorization': 'Bearer ' + self.login_token(),
306+
'Content-Type': 'application/json',
307+
'Accept': 'application/json, text/plain, */*',
308+
'accept-encoding': 'gzip, deflate, br',
309+
'accept-language': 'en-US,en;q=0.5',
310+
'connection': 'keep-alive',
311+
'content-type': 'application/json',
312+
'x-client-name': 'WEB',
313+
'x-client-version': '7.8.1',
314+
'cache-control': 'no-cache',
315+
'origin': 'https://www.mlb.com',
316+
'pragma': 'no-cache',
317+
'priority': 'u=1, i',
318+
'referer': 'https://www.mlb.com/',
319+
'sec-ch-ua': '"Chromium";v="133", "Google Chrome";v="133", "Not-A.Brand";v="8"',
320+
'sec-ch-ua-mobile': '?0',
321+
'sec-ch-ua-platform': '"macOS"',
322+
'sec-fetch-dest': 'empty',
323+
'sec-fetch-mode': 'cors',
324+
'sec-fetch-site': 'same-site'
325+
}
326+
data = {
327+
"operationName": "contentCollections",
328+
"query": '''query contentCollections(
329+
$categories: [ContentGroupCategory!]
330+
$includeRestricted: Boolean = false
331+
$includeSpoilers: Boolean = false
332+
$limit: Int = 10,
333+
$skip: Int = 0\n ) {
334+
contentCollections(
335+
categories: $categories
336+
includeRestricted: $includeRestricted
337+
includeSpoilers: $includeSpoilers
338+
limit: $limit
339+
skip: $skip
340+
) {
341+
title
342+
category
343+
contents {
344+
assetTrackingKey
345+
contentDate
346+
contentId
347+
contentRestrictions
348+
description
349+
duration
350+
language
351+
mediaId
352+
officialDate
353+
title
354+
mediaState {
355+
state
356+
mediaType
357+
}
358+
thumbnails {
359+
thumbnailType
360+
templateUrl
361+
thumbnailUrl
362+
}
363+
}
364+
}
365+
}''',
366+
"variables": {
367+
"categories": network,
368+
"limit": "25"
369+
}
370+
}
371+
xbmc.log(str(data))
372+
r = requests.post(url, headers=headers, json=data, verify=VERIFY)
373+
xbmc.log(r.text)
374+
if not r.ok:
375+
dialog = xbmcgui.Dialog()
376+
msg = ""
377+
for item in r.json()['errors']:
378+
msg += item['code'] + '\n'
379+
dialog.notification(LOCAL_STRING(30270), msg, self.icon, 5000, False)
380+
sys.exit()
381+
382+
availableStreams = r.json()['data']['contentCollections'][0]['contents']
383+
for stream in availableStreams:
384+
try:
385+
stream_url, headers = self.get_stream(stream['mediaId'])
386+
xbmc.log(f'Stream URL: {stream_url}')
387+
return stream_url
388+
except:
389+
pass

plugin.video.mlbtv/resources/lib/globals.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
FINAL = 'FF666666'
7171
FREE = 'FF43CD80'
7272

73-
73+
#Score ticker network prefix
74+
SCORES_TICKER_NETWORK = 'FDSN'
7475

7576
#Images
7677
ICON = os.path.join(ROOTDIR,"icon.png")

0 commit comments

Comments
 (0)