CONF_EQ_TREBLE,
CONF_FLOW_MODE,
CONF_HIDE_PLAYER,
+ CONF_HTTP_PROFILE,
CONF_ICON,
CONF_LOG_LEVEL,
CONF_OUTPUT_CHANNELS,
)
+CONF_ENTRY_HTTP_PROFILE = ConfigEntry(
+ key=CONF_HTTP_PROFILE,
+ type=ConfigEntryType.STRING,
+ options=(
+ ConfigValueOption("Profile 1 - chunked", "chunked"),
+ ConfigValueOption("Profile 2 - no content length", "no_content_length"),
+ ConfigValueOption("Profile 3 - forced content length", "forced_content_length"),
+ ),
+ default_value="chunked",
+ label="HTTP Profile used for sending audio",
+ category="advanced",
+ description="This is considered to be a very advanced setting, only adjust this if needed, "
+ "for example if your player stops playing halfway streams or if you experience "
+ "other playback related issues. In most cases the default setting, "
+ "'chunked transfer encoding', works just fine. \n\n",
+)
+
+
def create_sample_rates_config_entry(
max_sample_rate: int,
max_bit_depth: int,
CONF_ICON: Final[str] = "icon"
CONF_LANGUAGE: Final[str] = "language"
CONF_SAMPLE_RATES: Final[str] = "sample_rates"
+CONF_HTTP_PROFILE: Final[str] = "http_profile"
# config default values
DEFAULT_HOST: Final[str] = "0.0.0.0"
CONF_BIND_PORT,
CONF_CROSSFADE,
CONF_CROSSFADE_DURATION,
+ CONF_HTTP_PROFILE,
CONF_OUTPUT_CHANNELS,
CONF_PUBLISH_IP,
CONF_SAMPLE_RATES,
FFMpeg,
check_audio_support,
crossfade_pcm_parts,
+ get_chunksize,
get_ffmpeg_stream,
get_hls_substream,
get_icy_stream,
default_sample_rate=queue_item.streamdetails.audio_format.sample_rate,
default_bit_depth=queue_item.streamdetails.audio_format.bit_depth,
)
+ http_profile: str = self.mass.config.get_raw_player_config_value(
+ queue_id, CONF_HTTP_PROFILE, "chunked"
+ )
# prepare request, add some DLNA/UPNP compatible headers
headers = {
**DEFAULT_STREAM_HEADERS,
"Content-Type": f"audio/{output_format.output_format_str}",
+ "Accept-Ranges": "none",
+ "Cache-Control": "no-cache",
+ "Connection": "close",
}
resp = web.StreamResponse(
status=200,
reason="OK",
headers=headers,
)
+ if http_profile == "forced_content_length":
+ resp.content_length = get_chunksize(
+ output_format, queue_item.streamdetails.duration or 120
+ )
+ elif http_profile == "chunked":
+ resp.enable_chunked_encoding()
+
await resp.prepare(request)
# return early if this is not a GET request
) == "1" and output_format.content_type in (ContentType.MP3, ContentType.AAC)
icy_meta_interval = 16384
+ # prepare request, add some DLNA/UPNP compatible headers
+ http_profile: str = self.mass.config.get_raw_player_config_value(
+ queue_id, CONF_HTTP_PROFILE, "chunked"
+ )
# prepare request, add some DLNA/UPNP compatible headers
headers = {
**DEFAULT_STREAM_HEADERS,
"Content-Type": f"audio/{output_format.output_format_str}",
+ "Accept-Ranges": "none",
+ "Cache-Control": "no-cache",
+ "Connection": "close",
}
if enable_icy:
headers["icy-metaint"] = str(icy_meta_interval)
reason="OK",
headers=headers,
)
+ if http_profile == "forced_content_length":
+ resp.content_length = get_chunksize(output_format, 24 * 2600)
+ elif http_profile == "chunked":
+ resp.enable_chunked_encoding()
await resp.prepare(request)
# return early if this is not a GET request
CONF_ENTRY_CROSSFADE_DURATION,
CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED,
CONF_ENTRY_ENFORCE_MP3,
+ CONF_ENTRY_HTTP_PROFILE,
ConfigEntry,
ConfigValueType,
create_sample_rates_config_entry,
CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED,
CONF_ENTRY_CROSSFADE_DURATION,
CONF_ENTRY_ENFORCE_MP3,
+ CONF_ENTRY_HTTP_PROFILE,
create_sample_rates_config_entry(192000, 24, 96000, 24),
)
CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED,
CONF_ENTRY_ENFORCE_MP3_DEFAULT_ENABLED,
CONF_ENTRY_FLOW_MODE_DEFAULT_ENABLED,
+ CONF_ENTRY_HTTP_PROFILE,
ConfigEntry,
ConfigValueOption,
ConfigValueType,
CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED,
CONF_ENTRY_CROSSFADE_DURATION,
CONF_ENTRY_ENFORCE_MP3_DEFAULT_ENABLED,
+ CONF_ENTRY_HTTP_PROFILE,
)
)
from music_assistant.common.models.media_items import AudioFormat
from music_assistant.common.models.player import DeviceInfo, Player, PlayerMedia
-from music_assistant.constants import CONF_GROUP_MEMBERS, SYNCGROUP_PREFIX
+from music_assistant.constants import CONF_GROUP_MEMBERS, CONF_HTTP_PROFILE, SYNCGROUP_PREFIX
from music_assistant.server.controllers.streams import DEFAULT_STREAM_HEADERS
-from music_assistant.server.helpers.audio import get_ffmpeg_stream, get_player_filter_params
+from music_assistant.server.helpers.audio import (
+ get_chunksize,
+ get_ffmpeg_stream,
+ get_player_filter_params,
+)
from music_assistant.server.helpers.multi_client_stream import MultiClientStream
from music_assistant.server.helpers.util import TaskManager
from music_assistant.server.models.player_provider import PlayerProvider
if not (stream := self.streams.get(ugp_player_id, None)) or stream.done:
raise web.HTTPNotFound(body=f"There is no active UGP stream for {ugp_player_id}!")
- resp = web.StreamResponse(
- status=200,
- reason="OK",
- headers={
- **DEFAULT_STREAM_HEADERS,
- "Content-Type": f"audio/{fmt}",
- },
+ output_format = AudioFormat(
+ content_type=ContentType.try_parse(fmt),
+ sample_rate=stream.audio_format.sample_rate,
+ bit_depth=stream.audio_format.bit_depth,
+ )
+
+ http_profile: str = self.mass.config.get_raw_player_config_value(
+ child_player_id, CONF_HTTP_PROFILE, "chunked"
)
+ headers = {
+ **DEFAULT_STREAM_HEADERS,
+ "Content-Type": "faudio/{fmt}",
+ "Accept-Ranges": "none",
+ "Cache-Control": "no-cache",
+ "Connection": "close",
+ }
+
+ resp = web.StreamResponse(status=200, reason="OK", headers=headers)
+ if http_profile == "forced_content_length":
+ resp.content_length = get_chunksize(output_format, 24 * 3600)
+ elif http_profile == "chunked":
+ resp.enable_chunked_encoding()
await resp.prepare(request)
# return early if this is not a GET request
)
async for chunk in stream.get_stream(
- output_format=AudioFormat(content_type=ContentType.try_parse(fmt)),
+ output_format=output_format,
filter_params=get_player_filter_params(self.mass, child_player_id)
if child_player_id
else None,