ANNOUNCE_ALERT_FILE,
CONF_BIND_IP,
CONF_BIND_PORT,
+ CONF_BYPASS_NORMALIZATION_RADIO,
+ CONF_BYPASS_NORMALIZATION_SHORT,
CONF_CROSSFADE,
CONF_CROSSFADE_DURATION,
CONF_HTTP_PROFILE,
FLOW_DEFAULT_SAMPLE_RATE = 48000
FLOW_DEFAULT_BIT_DEPTH = 24
+
# pylint:disable=too-many-locals
isfile = wrap(os.path.isfile)
"not be adjusted in regular setups.",
category="advanced",
),
+ ConfigEntry(
+ key=CONF_BYPASS_NORMALIZATION_RADIO,
+ type=ConfigEntryType.BOOLEAN,
+ default_value=True,
+ label="Bypass volume normalization for radio streams",
+ description="Radio streams are often already normalized according "
+ "to the EBU standard, so it doesn't make a lot of sense to normalize them again "
+ "in Music Assistant unless you hear big jumps in volume during playback, "
+ "such as commercials.",
+ category="advanced",
+ ),
+ ConfigEntry(
+ key=CONF_BYPASS_NORMALIZATION_SHORT,
+ type=ConfigEntryType.BOOLEAN,
+ default_value=True,
+ label="Bypass volume normalization for effects and short sounds",
+ description="The volume normalizer of ffmpeg (used in Music Assistant), "
+ "is designed to work best with longer audio streams and can have troubles when "
+ "its applied to very short sound clips (< 60 seconds), "
+ "for example sound effects. With this option enabled, the volume normalizer "
+ "will be bypassed for all audio that has a duration of less than 60 seconds.",
+ category="advanced",
+ ),
)
async def setup(self, config: CoreConfig) -> None:
)
elif streamdetails.stream_type == StreamType.ICY:
audio_source = get_icy_stream(self.mass, streamdetails.path, streamdetails)
- # pad some silence before the radio stream starts to create some headroom
- # for radio stations that do not provide any look ahead buffer
- # without this, some radio streams jitter a lot
- async for chunk in get_silence(2, pcm_format):
- yield chunk
elif streamdetails.stream_type == StreamType.HLS:
# we simply select the best quality substream here
# if we ever want to support adaptive stream selection based on bandwidth
# the user wants the best quality possible at all times.
substream = await get_hls_substream(self.mass, streamdetails.path)
audio_source = substream.path
+ if streamdetails.media_type == MediaType.RADIO:
+ # ffmpeg sometimes has trouble with HLS radio streams stopping
+ # abruptly for no reason so this is a workaround to keep the stream alive
+ extra_input_args += ["-stream_loop", "-1"]
elif streamdetails.stream_type == StreamType.ENCRYPTED_HTTP:
audio_source = streamdetails.path
extra_input_args += ["-decryption_key", streamdetails.decryption_key]
if streamdetails.seek_position:
extra_input_args += ["-ss", str(int(streamdetails.seek_position))]
+ if streamdetails.media_type == MediaType.RADIO:
+ # pad some silence before the radio stream starts to create some headroom
+ # for radio stations that do not provide any look ahead buffer
+ # without this, some radio streams jitter a lot
+ async for chunk in get_silence(2, pcm_format):
+ yield chunk
+
logger.debug("start media stream for: %s", streamdetails.uri)
bytes_sent = 0
finished = False
from music_assistant.common.models.media_items import AudioFormat, ContentType
from music_assistant.common.models.streamdetails import LoudnessMeasurement, StreamDetails
from music_assistant.constants import (
+ CONF_BYPASS_NORMALIZATION_RADIO,
+ CONF_BYPASS_NORMALIZATION_SHORT,
CONF_EQ_BASS,
CONF_EQ_MID,
CONF_EQ_TREBLE,
streamdetails.seek_position = seek_position
streamdetails.fade_in = fade_in
# handle volume normalization details
- if not streamdetails.loudness:
+ is_radio = streamdetails.media_type == MediaType.RADIO or not streamdetails.duration
+ bypass_normalization = (
+ is_radio
+ and await mass.config.get_core_config_value("streams", CONF_BYPASS_NORMALIZATION_RADIO)
+ ) or (
+ streamdetails.duration is not None
+ and streamdetails.duration < 60
+ and await mass.config.get_core_config_value("streams", CONF_BYPASS_NORMALIZATION_SHORT)
+ )
+ if not bypass_normalization and not streamdetails.loudness:
streamdetails.loudness = await mass.music.get_track_loudness(
streamdetails.item_id, streamdetails.provider
)
player_settings = await mass.config.get_player_config(streamdetails.queue_id)
- if player_settings.get_value(CONF_VOLUME_NORMALIZATION):
- streamdetails.target_loudness = player_settings.get_value(CONF_VOLUME_NORMALIZATION_TARGET)
- else:
+ if bypass_normalization or not player_settings.get_value(CONF_VOLUME_NORMALIZATION):
streamdetails.target_loudness = None
+ else:
+ streamdetails.target_loudness = player_settings.get_value(CONF_VOLUME_NORMALIZATION_TARGET)
if not streamdetails.duration:
streamdetails.duration = queue_item.duration
"1",
"-reconnect_streamed",
"1",
- "-reconnect_delay_max",
- "10",
]
if major_version > 4:
# these options are only supported in ffmpeg > 5