Skip to content

Commit

Permalink
Prevent players buffering ahead too much
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt committed Oct 24, 2024
1 parent 576a441 commit e47700c
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 15 deletions.
2 changes: 2 additions & 0 deletions music_assistant/server/controllers/player_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,6 +1129,8 @@ def track_loaded_in_buffer(self, queue_id: str, item_id: str) -> None:
if not queue:
msg = f"PlayerQueue {queue_id} is not available"
raise PlayerUnavailableError(msg)
# store the index of the item that is currently (being) loaded in the buffer
# which helps us a bit to determine how far the player has buffered ahead
queue.index_in_buffer = self.index_by_id(queue_id, item_id)
if queue.flow_mode:
return # nothing to do when flow mode is active
Expand Down
4 changes: 4 additions & 0 deletions music_assistant/server/controllers/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
input_format=pcm_format,
output_format=output_format,
filter_params=get_player_filter_params(self.mass, queue_player.player_id),
# we don't allow the player to buffer too much ahead so we use readrate limiting
extra_input_args=["-readrate", "1.1", "-readrate_initial_burst", "10"],
):
try:
await resp.write(chunk)
Expand Down Expand Up @@ -472,6 +474,8 @@ async def serve_queue_flow_stream(self, request: web.Request) -> web.Response:
output_format=output_format,
filter_params=get_player_filter_params(self.mass, queue_player.player_id),
chunk_size=icy_meta_interval if enable_icy else None,
# we don't allow the player to buffer too much ahead so we use readrate limiting
extra_input_args=["-readrate", "1.1", "-readrate_initial_burst", "10"],
):
try:
await resp.write(chunk)
Expand Down
27 changes: 15 additions & 12 deletions music_assistant/server/helpers/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .util import TimedAsyncGenerator, close_async_generator

LOGGER = logging.getLogger("ffmpeg")
MINIMAL_FFMPEG_VERSION = 6


class FFMpeg(AsyncProcess):
Expand Down Expand Up @@ -175,7 +176,7 @@ async def get_ffmpeg_stream(
yield chunk


def get_ffmpeg_args(
def get_ffmpeg_args( # noqa: PLR0915
input_format: AudioFormat,
output_format: AudioFormat,
filter_params: list[str],
Expand All @@ -199,6 +200,12 @@ def get_ffmpeg_args(
)

major_version = int("".join(char for char in version.split(".")[0] if not char.isalpha()))
if major_version < MINIMAL_FFMPEG_VERSION:
msg = (
f"FFmpeg version {version} is not supported. "
f"Minimal version required is {MINIMAL_FFMPEG_VERSION}."
)
raise AudioError(msg)

# generic args
generic_args = [
Expand Down Expand Up @@ -227,18 +234,14 @@ def get_ffmpeg_args(
# If set then even streamed/non seekable streams will be reconnected on errors.
"-reconnect_streamed",
"1",
# Reconnect automatically in case of TCP/TLS errors during connect.
"-reconnect_on_network_error",
"1",
# A comma separated list of HTTP status codes to reconnect on.
# The list can include specific status codes (e.g. 503) or the strings 4xx / 5xx.
"-reconnect_on_http_error",
"5xx,4xx",
]
if major_version > 4:
# these options are only supported in ffmpeg > 5
input_args += [
# Reconnect automatically in case of TCP/TLS errors during connect.
"-reconnect_on_network_error",
"1",
# A comma separated list of HTTP status codes to reconnect on.
# The list can include specific status codes (e.g. 503) or the strings 4xx / 5xx.
"-reconnect_on_http_error",
"5xx,4xx",
]
if input_format.content_type.is_pcm():
input_args += [
"-ac",
Expand Down
5 changes: 2 additions & 3 deletions music_assistant/server/providers/player_group/ugp_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ async def _runner(self) -> None:
audio_input=self.audio_source,
input_format=self.input_format,
output_format=self.output_format,
# enable realtime to prevent too much buffering ahead
# TODO: enable initial burst once we have a newer ffmpeg version
extra_input_args=["-re"],
# we don't allow the player to buffer too much ahead so we use readrate limiting
extra_input_args=["-readrate", "1.1", "-readrate_initial_burst", "10"],
):
await asyncio.gather(
*[sub(chunk) for sub in self.subscribers],
Expand Down

0 comments on commit e47700c

Please sign in to comment.