/
/
/
1"""
2Tests for persisting the per-item playback speed of audiobooks/podcast episodes.
3
4The playback speed a listener picks for an audiobook or podcast episode is stored
5in the ``playlog`` table (alongside the resume position) so it can be restored when
6the item is queued again later. These integration tests use the ``mass`` fixture
7from ``tests/conftest.py`` which creates a full MusicAssistant instance with a real
8SQLite database in a temporary directory.
9"""
10
11from __future__ import annotations
12
13from uuid import uuid4
14
15from music_assistant_models.enums import MediaType
16from music_assistant_models.media_items import Audiobook, ProviderMapping
17
18from music_assistant.constants import DB_TABLE_PLAYLOG
19from music_assistant.mass import MusicAssistant
20
21
22def _library_mapping() -> set[ProviderMapping]:
23 """Create a single library provider mapping with a unique provider item id."""
24 return {
25 ProviderMapping(
26 item_id=uuid4().hex,
27 provider_domain="library",
28 provider_instance="library",
29 in_library=True,
30 )
31 }
32
33
34def _audiobook() -> Audiobook:
35 """Create a minimal audiobook with a unique item id."""
36 return Audiobook(
37 item_id=uuid4().hex,
38 provider="library",
39 name="A Book",
40 provider_mappings=_library_mapping(),
41 duration=3600,
42 )
43
44
45async def test_playback_speed_defaults_to_normal(mass: MusicAssistant) -> None:
46 """An item that never had a speed set reports the normal (1.0) speed."""
47 user = await mass.webserver.auth.create_user("speeduser_default")
48 book = _audiobook()
49 assert await mass.music.get_playback_speed(book, userid=user.user_id) == 1.0
50
51
52async def test_playback_speed_is_persisted(mass: MusicAssistant) -> None:
53 """A speed supplied while reporting progress is stored and read back."""
54 user = await mass.webserver.auth.create_user("speeduser_persist")
55 book = _audiobook()
56
57 await mass.music.mark_item_played(
58 book,
59 fully_played=False,
60 seconds_played=100,
61 userid=user.user_id,
62 user_initiated=False,
63 playback_speed=1.5,
64 )
65
66 assert await mass.music.get_playback_speed(book, userid=user.user_id) == 1.5
67
68
69async def test_progress_sync_preserves_stored_speed(mass: MusicAssistant) -> None:
70 """A speed-less progress write (e.g. a provider sync) must not reset the speed."""
71 user = await mass.webserver.auth.create_user("speeduser_preserve")
72 book = _audiobook()
73
74 # listener sets a custom speed (as MA's own report path does)
75 await mass.music.mark_item_played(
76 book,
77 fully_played=False,
78 seconds_played=100,
79 userid=user.user_id,
80 user_initiated=False,
81 playback_speed=1.5,
82 )
83 # a later progress update without a speed (e.g. Audiobookshelf/gpodder sync)
84 await mass.music.mark_item_played(
85 book,
86 fully_played=False,
87 seconds_played=200,
88 userid=user.user_id,
89 user_initiated=False,
90 )
91
92 # speed preserved, while the resume position still advanced
93 assert await mass.music.get_playback_speed(book, userid=user.user_id) == 1.5
94 row = await mass.music.database.get_row(
95 DB_TABLE_PLAYLOG,
96 {
97 "item_id": book.item_id,
98 "provider": "library",
99 "media_type": MediaType.AUDIOBOOK.value,
100 "userid": user.user_id,
101 },
102 )
103 assert row is not None
104 assert row["seconds_played"] == 200
105