From 478e9b682a9994a9aa0faf4ee3befdc330c35204 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Thu, 15 Jan 2026 20:39:31 +0100 Subject: [PATCH] fix(playback): Avoid MPD manifest parsing to fix regression with new TIDAL group="main" manifests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TIDAL appears to have changed the MPEG-DASH MPD manifests returned for some streams to include non-numeric values in `` (e.g. group="main"). The current `mpegdash` parser expects group to be an integer and raises a `ValueError`, which `tidalapi` wraps as `ManifestDecodeError`. This caused `mopidy-tidal` playback to fail and tracks to be marked “not playable”. This PR updates mopidy-tidal’s playback provider to avoid parsing MPD manifests via tidalapi/mpegdash. For MPD manifests we only need to persist the MPD XML and return a `file://` URI, so parsing is unnecessary. **Root cause** - [`tidalapi.StreamManifest`](https://github.com/EbbLabs/python-tidal/blob/08742362ee8cef7df1362387cef60c2846aa84ee/tidalapi/media.py#L644) → [`DashInfo.from_mpd()`](https://github.com/EbbLabs/python-tidal/blob/08742362ee8cef7df1362387cef60c2846aa84ee/tidalapi/media.py#L770) → `mpegdash` parser - New MPD contains: `` - [`mpegdash` tries to parse group as int](https://github.com/sangwonl/python-mpegdash/blob/48b52122cdbcaebe1804b79f77d05dfc1ce32900/mpegdash/nodes.py#L782) → `ValueError` → `tidalapi.exceptions.ManifestDecodeError` - Mopidy logs: backend exception + _Track is not playable_ **Changes** - **MPD path** (`ManifestMimeType.MPD`) - Stop calling `stream.get_stream_manifest()` (this triggers MPD parsing and the crash) - Use `stream.get_manifest_data()` to fetch raw MPD XML - Write the MPD to `manifest.mpd` in the extension cache directory - Return `file://…/manifest.mpd` for playback (same end result as before, but without parsing) - **BTS path** (ManifestMimeType.BTS) - Keep existing behavior using stream.get_stream_manifest() **Why this is safe** mopidy-tidal already ultimately plays MPD by writing it to disk and handing Mopidy a `file://` URI. The parsed MPD data from tidalapi wasn’t required for playback logic here; it was effectively only used for logging (codecs). Closes: EbbLabs/python-tidal#397 --- mopidy_tidal/playback.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/mopidy_tidal/playback.py b/mopidy_tidal/playback.py index 3e8af5d2..1a44524f 100755 --- a/mopidy_tidal/playback.py +++ b/mopidy_tidal/playback.py @@ -38,19 +38,18 @@ def translate_uri(self, uri): ) stream = session.track(track_id).get_stream() - manifest = stream.get_stream_manifest() logger.info("MimeType:{}".format(stream.manifest_mime_type)) - logger.info( - "Starting playback of track:{}, (quality:{}, codec:{}, {}bit/{}Hz)".format( - track_id, - stream.audio_quality, - manifest.get_codecs(), - stream.bit_depth, - stream.sample_rate, - ) - ) if stream.manifest_mime_type == ManifestMimeType.MPD: + logger.info( + "Starting playback of track:{}, (quality:{}, {}bit/{}Hz)".format( + track_id, + stream.audio_quality, + stream.bit_depth, + stream.sample_rate, + ) + ) + data = stream.get_manifest_data() if data: mpd_path = Path( @@ -63,4 +62,18 @@ def translate_uri(self, uri): else: raise AttributeError("No MPD manifest available!") elif stream.manifest_mime_type == ManifestMimeType.BTS: - return manifest.get_urls() + manifest = stream.get_stream_manifest() + logger.info( + "Starting playback of track:{}, (quality:{}, codec:{}, {}bit/{}Hz)".format( + track_id, + stream.audio_quality, + manifest.get_codecs(), + stream.bit_depth, + stream.sample_rate, + ) + ) + urls = manifest.get_urls() + if isinstance(urls, list): + return urls[0] + else: + return urls