From 0b5ac7eb7c560683e887085839878a742e7b1efb Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sat, 20 Dec 2025 11:46:38 +0100 Subject: [PATCH] More fixes for crossfading across sample rates --- music_assistant/constants.py | 10 +++++----- music_assistant/controllers/players/sync_groups.py | 1 - .../controllers/streams/streams_controller.py | 14 ++++++++++---- music_assistant/providers/sonos/const.py | 1 - music_assistant/providers/sonos/player.py | 1 - music_assistant/providers/sonos_s1/constants.py | 1 - music_assistant/providers/squeezelite/player.py | 10 +++------- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/music_assistant/constants.py b/music_assistant/constants.py index d14f7a2e..906d4154 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -638,14 +638,14 @@ CONF_ENTRY_ICY_METADATA_HIDDEN_DISABLED = ConfigEntry.from_dict( } ) -CONF_ENTRY_SUPPORT_CROSSFADE_DIFFERENT_SAMPLE_RATES = ConfigEntry( - key="crossfade_different_sample_rates", +CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES = ConfigEntry( + key="gapless_different_sample_rates", type=ConfigEntryType.BOOLEAN, - label="Allow crossfade between tracks with different sample rates", - description="Enable this option to allow crossfading between tracks that have different " + label="Allow gapless playback (and crossfades) between tracks of different sample rates", + description="Enable this option to allow gapless playback between tracks that have different " "sample rates (e.g. 44.1kHz to 48kHz). \n\n " "Only enable this option if your player actually support this, otherwise you may " - "experience audio glitches during crossfades.", + "experience audio glitches during transitioning between tracks.", default_value=False, category="advanced", ) diff --git a/music_assistant/controllers/players/sync_groups.py b/music_assistant/controllers/players/sync_groups.py index 1bfd04be..53b324bf 100644 --- a/music_assistant/controllers/players/sync_groups.py +++ b/music_assistant/controllers/players/sync_groups.py @@ -59,7 +59,6 @@ SUPPORT_DYNAMIC_LEADER = { OPTIONAL_FEATURES = { PlayerFeature.ENQUEUE, PlayerFeature.GAPLESS_PLAYBACK, - PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, PlayerFeature.NEXT_PREVIOUS, PlayerFeature.PAUSE, PlayerFeature.PLAY_ANNOUNCEMENT, diff --git a/music_assistant/controllers/streams/streams_controller.py b/music_assistant/controllers/streams/streams_controller.py index 0df15462..6ca7926a 100644 --- a/music_assistant/controllers/streams/streams_controller.py +++ b/music_assistant/controllers/streams/streams_controller.py @@ -44,6 +44,7 @@ from music_assistant.constants import ( CONF_CROSSFADE_DURATION, CONF_ENTRY_ENABLE_ICY_METADATA, CONF_ENTRY_LOG_LEVEL, + CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES, CONF_HTTP_PROFILE, CONF_OUTPUT_CHANNELS, CONF_OUTPUT_CODEC, @@ -1892,9 +1893,9 @@ class StreamsController(CoreController): ), ) supported_sample_rates = tuple(int(x[0]) for x in supported_rates_conf) - # use highest supported rate below content rate + # use highest supported rate within content rate output_sample_rate = max( - (r for r in supported_sample_rates if r < streamdetails.audio_format.sample_rate), + (r for r in supported_sample_rates if r <= streamdetails.audio_format.sample_rate), default=48000, ) # work out pcm format based on streamdetails @@ -1909,7 +1910,11 @@ class StreamsController(CoreController): pcm_format.channels = 2 # force stereo for crossfading # allows upsample to same sample rate for crossfade # if the device does not support gapless playback with different sample rates - if PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE not in player.supported_features: + if not self.mass.config.get_raw_player_config_value( + player.player_id, + CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES.key, + CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES.default_value, + ): new_sample_rate = max( pcm_format.sample_rate, 48000, # sane/safe default @@ -1917,7 +1922,8 @@ class StreamsController(CoreController): if new_sample_rate != pcm_format.sample_rate: self.logger.debug( "Player does not support crossfade with different sample rates, " - "content will be (HQ) upsampled to at least 48kHz for crossfade." + "content will be (HQ) upsampled to %s for crossfade.", + new_sample_rate, ) pcm_format.sample_rate = new_sample_rate diff --git a/music_assistant/providers/sonos/const.py b/music_assistant/providers/sonos/const.py index 375f34bf..9b4c8a9f 100644 --- a/music_assistant/providers/sonos/const.py +++ b/music_assistant/providers/sonos/const.py @@ -22,7 +22,6 @@ PLAYER_FEATURES_BASE = { PlayerFeature.SEEK, PlayerFeature.SELECT_SOURCE, PlayerFeature.GAPLESS_PLAYBACK, - PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, } SOURCE_LINE_IN = "line_in" diff --git a/music_assistant/providers/sonos/player.py b/music_assistant/providers/sonos/player.py index 3ec0bd31..c3b7d552 100644 --- a/music_assistant/providers/sonos/player.py +++ b/music_assistant/providers/sonos/player.py @@ -63,7 +63,6 @@ SUPPORTED_FEATURES = { PlayerFeature.SELECT_SOURCE, PlayerFeature.SET_MEMBERS, PlayerFeature.GAPLESS_PLAYBACK, - PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, } diff --git a/music_assistant/providers/sonos_s1/constants.py b/music_assistant/providers/sonos_s1/constants.py index 711f5fce..9ca1c190 100644 --- a/music_assistant/providers/sonos_s1/constants.py +++ b/music_assistant/providers/sonos_s1/constants.py @@ -22,7 +22,6 @@ PLAYER_FEATURES = ( PlayerFeature.VOLUME_SET, PlayerFeature.ENQUEUE, PlayerFeature.GAPLESS_PLAYBACK, - PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, ) # Source Mapping diff --git a/music_assistant/providers/squeezelite/player.py b/music_assistant/providers/squeezelite/player.py index 9613fe83..17882e50 100644 --- a/music_assistant/providers/squeezelite/player.py +++ b/music_assistant/providers/squeezelite/player.py @@ -23,10 +23,7 @@ from music_assistant_models.enums import ( PlayerType, RepeatMode, ) -from music_assistant_models.errors import ( - InvalidCommand, - MusicAssistantError, -) +from music_assistant_models.errors import InvalidCommand, MusicAssistantError from music_assistant_models.media_items import AudioFormat from music_assistant.constants import ( @@ -35,7 +32,7 @@ from music_assistant.constants import ( CONF_ENTRY_DEPRECATED_EQ_TREBLE, CONF_ENTRY_HTTP_PROFILE_FORCED_2, CONF_ENTRY_OUTPUT_CODEC, - CONF_ENTRY_SUPPORT_CROSSFADE_DIFFERENT_SAMPLE_RATES, + CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES, CONF_ENTRY_SYNC_ADJUST, INTERNAL_PCM_FORMAT, VERBOSE_LOG_LEVEL, @@ -91,7 +88,6 @@ class SqueezelitePlayer(Player): PlayerFeature.PAUSE, PlayerFeature.ENQUEUE, PlayerFeature.GAPLESS_PLAYBACK, - PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, } self._attr_can_group_with = {provider.instance_id} self.multi_client_stream: MultiClientStream | None = None @@ -170,7 +166,7 @@ class SqueezelitePlayer(Player): create_sample_rates_config_entry( max_sample_rate=max_sample_rate, max_bit_depth=24, safe_max_bit_depth=24 ), - CONF_ENTRY_SUPPORT_CROSSFADE_DIFFERENT_SAMPLE_RATES, + CONF_ENTRY_SUPPORT_GAPLESS_DIFFERENT_SAMPLE_RATES, ] async def power(self, powered: bool) -> None: -- 2.34.1