Skip to content

Commit f72e189

Browse files
Overhaul time management
1 parent c3236cd commit f72e189

4 files changed

Lines changed: 54 additions & 33 deletions

File tree

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ History
88
* Pulls `tune_time` from `wallClockRenderTime`
99
* Adds `primary_hls` and `seconary_hls`
1010
* Adds quality selection
11+
* Overhauls time/datetime management
1112

1213
0.2.4 (2021-08-15)
1314
------------------

sxm/client.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,20 @@ async def get_now_playing(self, channel: XMChannel) -> Union[Dict[str, Any], Non
407407
SXM channel to look up live channel data for
408408
"""
409409

410+
now = time.time()
411+
now_dt = datetime.datetime.fromtimestamp(now).replace(
412+
tzinfo=datetime.timezone.utc
413+
)
414+
410415
params = {
411416
"assetGUID": channel.guid,
412417
"ccRequestType": "AUDIO_VIDEO",
413418
"channelId": channel.id,
414419
"hls_output_mode": "custom",
415420
"marker_mode": "all_separate_cue_points",
416421
"result-template": "web",
417-
"time": str(int(round(time.time() * 1000.0))),
418-
"timestamp": datetime.datetime.utcnow().isoformat("T") + "Z",
422+
"time": str(int(round(now * 1000.0))),
423+
"timestamp": now_dt.isoformat("T") + "Z",
419424
}
420425

421426
return await self._get("tune/now-playing-live", params)
@@ -428,7 +433,7 @@ async def close_session(self):
428433
def reset_session(self) -> None:
429434
"""Resets session used by client"""
430435

431-
self._session_start = time.time()
436+
self._session_start = time.monotonic()
432437
self._session = httpx.AsyncClient()
433438
self._session.headers.update({"User-Agent": self._ua["string"]})
434439

@@ -511,7 +516,7 @@ async def _request(
511516
method = method.upper()
512517

513518
if authenticate:
514-
now = time.time()
519+
now = time.monotonic()
515520
if (now - self._session_start) > SESSION_MAX_LIFE:
516521
self._log.info("Session exceed max time, reseting")
517522
await self.close_session()
@@ -584,7 +589,7 @@ async def _get_playlist_url(
584589
self._log.info(f"No channel for {channel_id}")
585590
return None
586591

587-
now = time.time()
592+
now = time.monotonic()
588593

589594
if use_cache and channel.id in self._playlists:
590595
if (
@@ -651,7 +656,7 @@ async def _get_playlist_url(
651656
playlist = await self._get_playlist_variant_url(live_channel.primary_hls.url)
652657
if playlist is not None:
653658
self._playlists[channel.id] = playlist
654-
self.last_renew = time.time()
659+
self.last_renew = time.monotonic()
655660

656661
if self.update_handler is not None:
657662
self.update_handler(live_channel_raw)

sxm/models.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

3-
import datetime
4-
import time
3+
from datetime import datetime, timedelta, timezone
54
from enum import Enum
65
from typing import List, Optional, Tuple, Union
76

@@ -32,6 +31,16 @@
3231
LIVE_PRIMARY_HLS = "https://siriusxm-priprodlive.akamaized.net"
3332

3433

34+
def parse_xm_datetime(dt_string: str):
35+
dt_string = dt_string.replace("+0000", "")
36+
dt = datetime.fromisoformat(dt_string)
37+
return dt.replace(tzinfo=timezone.utc)
38+
39+
40+
def parse_xm_timestamp(timestamp: int):
41+
return datetime.utcfromtimestamp(timestamp / 1000).replace(tzinfo=timezone.utc)
42+
43+
3544
class QualitySize(str, Enum):
3645
SMALL_64k = "SMALL"
3746
MEDIUM_128k = "MEDIUM"
@@ -96,15 +105,15 @@ def from_dict(data: dict) -> XMCategory:
96105

97106
class XMMarker(BaseModel):
98107
guid: str
99-
time: int
100-
duration: int
108+
time: datetime
109+
duration: timedelta
101110

102111
@staticmethod
103112
def from_dict(data: dict) -> XMMarker:
104113
return XMMarker(
105114
guid=data["assetGUID"],
106-
time=data["time"],
107-
duration=data["duration"],
115+
time=parse_xm_timestamp(data["time"]),
116+
duration=timedelta(seconds=data["duration"]),
108117
)
109118

110119

@@ -162,8 +171,8 @@ class XMEpisodeMarker(XMMarker):
162171
def from_dict(data: dict) -> XMEpisodeMarker:
163172
return XMEpisodeMarker(
164173
guid=data["assetGUID"],
165-
time=data["time"],
166-
duration=data["duration"],
174+
time=parse_xm_timestamp(data["time"]),
175+
duration=timedelta(seconds=data["duration"]),
167176
episode=XMEpisode.from_dict(data["episode"]),
168177
)
169178

@@ -250,23 +259,20 @@ def from_dict(data: dict) -> XMCutMarker:
250259

251260
return XMCutMarker(
252261
guid=data["assetGUID"],
253-
time=data["time"],
254-
duration=data["duration"],
262+
time=parse_xm_timestamp(data["time"]),
263+
duration=timedelta(seconds=data["duration"]),
255264
cut=cut,
256265
)
257266

258267

259268
class XMPosition(BaseModel):
260-
timestamp: datetime.datetime
269+
timestamp: datetime
261270
position: str
262271

263272
@staticmethod
264273
def from_dict(data: dict) -> XMPosition:
265-
dt_string = data["timestamp"].replace("+0000", "")
266-
dt = datetime.datetime.fromisoformat(dt_string)
267-
268274
return XMPosition(
269-
timestamp=dt.replace(tzinfo=datetime.timezone.utc),
275+
timestamp=parse_xm_datetime(data["timestamp"]),
270276
position=data["position"],
271277
)
272278

@@ -352,7 +358,7 @@ class XMLiveChannel(BaseModel):
352358
custom_hls_infos: List[XMHLSInfo]
353359
episode_markers: List[XMEpisodeMarker]
354360
cut_markers: List[XMCutMarker]
355-
tune_time: Optional[int] = None
361+
tune_time: Optional[datetime] = None
356362
# ... plus many unused
357363

358364
_stream_quality: QualitySize = PrivateAttr(QualitySize.LARGE_256k)
@@ -374,10 +380,10 @@ def from_dict(data: dict) -> XMLiveChannel:
374380
)
375381

376382
return XMLiveChannel(
377-
id=data["channelId"],
383+
id=data["moduleResponse"]["liveChannelData"]["channelId"],
378384
hls_infos=hls_infos,
379385
custom_hls_infos=custom_hls_infos,
380-
tune_time=data["wallClockRenderTime"],
386+
tune_time=parse_xm_datetime(data["wallClockRenderTime"]),
381387
episode_markers=episode_markers,
382388
cut_markers=cut_markers,
383389
)
@@ -475,7 +481,7 @@ def sort_markers(markers: List[XMMarker]) -> List[XMMarker]:
475481
return sorted(markers, key=lambda x: x.time)
476482

477483
def _latest_marker(
478-
self, marker_attr: str, now: Optional[int] = None
484+
self, marker_attr: str, now: Optional[datetime] = None
479485
) -> Union[XMMarker, None]:
480486
"""Returns the latest `XMMarker` based on type relative to now"""
481487

@@ -484,7 +490,7 @@ def _latest_marker(
484490
return None
485491

486492
if now is None:
487-
now = int(time.time() * 1000)
493+
now = datetime.now(timezone.utc)
488494

489495
latest = None
490496
for marker in markers:
@@ -495,7 +501,7 @@ def _latest_marker(
495501
return latest
496502

497503
def get_latest_episode(
498-
self, now: Optional[int] = None
504+
self, now: Optional[datetime] = None
499505
) -> Union[XMEpisodeMarker, None]:
500506
"""Returns the latest :class:`XMEpisodeMarker` based
501507
on type relative to now
@@ -507,7 +513,9 @@ def get_latest_episode(
507513
"""
508514
return self._latest_marker("episode_markers", now) # type: ignore
509515

510-
def get_latest_cut(self, now: Optional[int] = None) -> Union[XMCutMarker, None]:
516+
def get_latest_cut(
517+
self, now: Optional[datetime] = None
518+
) -> Union[XMCutMarker, None]:
511519
"""Returns the latest :class:`XMCutMarker` based
512520
on type relative to now
513521

tests/test_models.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime, timezone
12
from unittest.mock import patch
23

34
from sxm.models import XMLiveChannel
@@ -46,14 +47,20 @@ def test_channels(sxm_client):
4647
def test_live_channel(sxm_client):
4748
data = sxm_client.get_now_playing("octane")
4849

49-
channel = XMLiveChannel.from_dict(
50-
data["moduleList"]["modules"][0]["moduleResponse"]["liveChannelData"]
51-
)
50+
channel = XMLiveChannel.from_dict(data["moduleList"]["modules"][0])
5251

5352
assert channel.id == "octane"
5453

55-
with patch("sxm.models.time") as mock_time:
56-
mock_time.time.return_value = 1630000000
54+
with patch("sxm.models.datetime") as mock_time:
55+
mock_time.now.return_value = datetime(
56+
year=2021,
57+
month=8,
58+
day=26,
59+
hour=17,
60+
minute=40,
61+
second=40,
62+
tzinfo=timezone.utc,
63+
)
5764
cut = channel.get_latest_cut()
5865

5966
assert cut.cut.title == "Ten Thousand Fists"

0 commit comments

Comments
 (0)