Skip to content

Commit

Permalink
Add config option to specify the http content length header (#1607)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt authored Aug 24, 2024
1 parent 94d26a1 commit 9313a0d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 10 deletions.
19 changes: 19 additions & 0 deletions music_assistant/common/models/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
CONF_EQ_TREBLE,
CONF_FLOW_MODE,
CONF_HIDE_PLAYER,
CONF_HTTP_PROFILE,
CONF_ICON,
CONF_LOG_LEVEL,
CONF_OUTPUT_CHANNELS,
Expand Down Expand Up @@ -592,6 +593,24 @@ class CoreConfig(Config):
)


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,
Expand Down
1 change: 1 addition & 0 deletions music_assistant/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
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"
Expand Down
26 changes: 26 additions & 0 deletions music_assistant/server/controllers/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
CONF_BIND_PORT,
CONF_CROSSFADE,
CONF_CROSSFADE_DURATION,
CONF_HTTP_PROFILE,
CONF_OUTPUT_CHANNELS,
CONF_PUBLISH_IP,
CONF_SAMPLE_RATES,
Expand All @@ -45,6 +46,7 @@
FFMpeg,
check_audio_support,
crossfade_pcm_parts,
get_chunksize,
get_ffmpeg_stream,
get_hls_substream,
get_icy_stream,
Expand Down Expand Up @@ -264,16 +266,29 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
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
Expand Down Expand Up @@ -340,10 +355,17 @@ async def serve_queue_flow_stream(self, request: web.Request) -> web.Response:
) == "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)
Expand All @@ -353,6 +375,10 @@ async def serve_queue_flow_stream(self, request: web.Request) -> web.Response:
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
Expand Down
2 changes: 2 additions & 0 deletions music_assistant/server/providers/dlna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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,
Expand Down Expand Up @@ -79,6 +80,7 @@
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),
)

Expand Down
2 changes: 2 additions & 0 deletions music_assistant/server/providers/hass_players/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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,
Expand Down Expand Up @@ -94,6 +95,7 @@ class MediaPlayerEntityFeature(IntFlag):
CONF_ENTRY_CROSSFADE_FLOW_MODE_REQUIRED,
CONF_ENTRY_CROSSFADE_DURATION,
CONF_ENTRY_ENFORCE_MP3_DEFAULT_ENABLED,
CONF_ENTRY_HTTP_PROFILE,
)


Expand Down
38 changes: 28 additions & 10 deletions music_assistant/server/providers/ugp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@
)
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
Expand Down Expand Up @@ -361,14 +365,28 @@ async def _serve_ugp_stream(self, request: web.Request) -> web.Response:
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
Expand All @@ -383,7 +401,7 @@ async def _serve_ugp_stream(self, request: web.Request) -> web.Response:
)

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,
Expand Down

0 comments on commit 9313a0d

Please sign in to comment.