+++ /dev/null
-"""Built-in HTTP-based Player Provider for Music Assistant.
-
-This provider creates a standards HTTP audio streaming endpoint that can be utilized
-by the MA web interface, accessed directly as a URL, consumed by Home Assistant media
-browser, or integrated with other plugins without requiring third-party protocols.
-
-Usage requires registering a player through the 'builtin_player/register' API command.
-The registered player must regularly update its state via 'builtin_player/update_state'
-to maintain the connection. Players can be manually disconnected with 'builtin_player/unregister'
-when no longer needed.
-
-Communication with the player occurs via events. The provider sends commands (play media url, pause,
-stop, volume changes, etc.) through the BUILTIN_PLAYER event type. Client implementations must
-listen for these events and respond accordingly to control playback and handle media changes.
-"""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from music_assistant_models.enums import ProviderFeature
-
-from music_assistant.mass import MusicAssistant
-from music_assistant.models import ProviderInstanceType
-
-from .provider import BuiltinPlayerProvider
-
-if TYPE_CHECKING:
- from music_assistant_models.config_entries import ConfigEntry, ConfigValueType, ProviderConfig
- from music_assistant_models.provider import ProviderManifest
-
-SUPPORTED_FEATURES = {ProviderFeature.REMOVE_PLAYER}
-
-
-async def setup(
- mass: MusicAssistant, manifest: ProviderManifest, config: ProviderConfig
-) -> ProviderInstanceType:
- """Initialize provider(instance) with given configuration."""
- return BuiltinPlayerProvider(mass, manifest, config, SUPPORTED_FEATURES)
-
-
-async def get_config_entries(
- mass: MusicAssistant,
- instance_id: str | None = None,
- action: str | None = None,
- values: dict[str, ConfigValueType] | None = None,
-) -> tuple[ConfigEntry, ...]:
- """
- Return Config entries to setup this provider.
-
- instance_id: id of an existing provider instance (None if new instance setup).
- action: [optional] action key called from config entries UI.
- values: the (intermediate) raw values for config entries sent with the action.
- """
- # ruff: noqa: ARG001
- return ()
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- width="240"
- height="240"
- viewBox="0 0 240 240"
- fill="none"
- version="1.1"
- id="svg18"
- sodipodi:docname="icon.svg"
- inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
- inkscape:export-filename="icon.svg"
- inkscape:export-xdpi="96"
- inkscape:export-ydpi="96"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:svg="http://www.w3.org/2000/svg">
- <sodipodi:namedview
- id="namedview18"
- pagecolor="#ffffff"
- bordercolor="#000000"
- borderopacity="0.25"
- inkscape:showpageshadow="2"
- inkscape:pageopacity="0.0"
- inkscape:pagecheckerboard="0"
- inkscape:deskcolor="#d1d1d1"
- inkscape:zoom="2.8284271"
- inkscape:cx="94.575532"
- inkscape:cy="119.32427"
- inkscape:window-width="1920"
- inkscape:window-height="1129"
- inkscape:window-x="-8"
- inkscape:window-y="-8"
- inkscape:window-maximized="1"
- inkscape:current-layer="svg18" />
- <g
- clip-path="url(#clipPath19)"
- id="g17"
- transform="translate(0,2)">
- <path
- d="m 109.394,4.3814 c 5.848,-5.84187 15.394,-5.84187 21.212,0 l 98.788,98.8876 c 5.848,5.842 10.606,17.374 10.606,25.638 v 90.11 l -0.005,0.356 c -0.206,8.086 -6.881,14.628 -14.995,14.628 H 15 C 6.75759,234.001 2.40473e-5,227.22 0,218.987 v -90.11 c 1.20331e-4,-8.264 4.78834,-19.796 10.6064,-25.638 z"
- fill="#f2f4f9"
- id="path1" />
- <path
- d="m 109.394,4.3814 c 5.848,-5.84187 15.394,-5.84187 21.212,0 l 98.788,98.8876 c 5.848,5.842 10.606,17.374 10.606,25.638 v 90.11 l -0.005,0.356 c -0.206,8.086 -6.881,14.628 -14.995,14.628 H 15 C 6.75759,234.001 2.40473e-5,227.22 0,218.987 v -90.11 c 1.20331e-4,-8.264 4.78834,-19.796 10.6064,-25.638 z M 36,120.001 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.5817,-8 -8,-8 z m 32,0 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.5817,-8 -8,-8 z m 32,0 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.582,-8 -8,-8 z m 58.393,0.426 c -4.193,-1.395 -8.722,0.873 -10.118,5.065 l -26.796,80.509 h 16.863 l 25.114,-75.457 c 1.395,-4.192 -0.872,-8.721 -5.063,-10.117 z m 30.315,5.065 c -1.395,-4.192 -5.925,-6.46 -10.117,-5.065 -4.192,1.396 -6.46,5.925 -5.065,10.117 l 25.116,75.457 h 16.862 z"
- fill="#18bcf2"
- id="path2" />
- <g
- clip-path="url(#clip1_3_51)"
- id="g16">
- <path
- d="m 482.363,47.06 v 146.89 h -26.91 v -85.83 l -33.86,85.82 h -26.03 l -33.47,-85.14 v 85.14 h -26.23 V 47.06 h 26.23 l 46.48,114.99 46.88,-114.99 z"
- fill="#f2f4f9"
- id="path3" />
- <path
- d="m 581.292,193.94 c -0.85,-2.74 -1.47,-6.07 -1.86,-9.98 -5.94,8.42 -15.46,12.62 -28.58,12.62 -10.96,0 -20.65,-3.16 -29.06,-9.49 -8.02,-6.46 -12.04,-17.12 -12.04,-32 V 94.51 h 26.23 v 55.29 c 0,8.94 2.09,15.1 6.26,18.5 4.11,3.33 8.84,4.99 14.19,4.99 5.74,0 10.89,-1.79 15.46,-5.38 4.63,-3.65 6.95,-9.75 6.95,-18.3 V 94.52 h 26.32 v 81.03 c 0,6.65 0.65,12.79 1.96,18.4 h -25.83 z"
- fill="#f2f4f9"
- id="path4" />
- <path
- d="m 663.403,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path5" />
- <path
- d="m 720.062,60.66 c 0,-4.37 1.53,-8.07 4.6,-11.11 3.07,-3.03 6.82,-4.55 11.25,-4.55 4.43,0 8.19,1.52 11.25,4.55 3.07,3.03 4.6,6.74 4.6,11.11 0,4.37 -1.53,8.27 -4.6,11.3 -3.07,3.03 -6.82,4.55 -11.25,4.55 -4.43,0 -8.19,-1.52 -11.25,-4.55 -3.07,-3.03 -4.6,-6.8 -4.6,-11.3 z m 29.06,33.86 v 99.43 h -26.23 V 94.52 Z"
- fill="#f2f4f9"
- id="path6" />
- <path
- d="m 818.513,91.48 c 11.42,0 21.28,2.84 29.6,8.51 8.32,5.67 13.88,13.21 16.69,22.61 l -22.41,9 c -1.63,-4.96 -4.58,-8.94 -8.86,-11.94 -4.27,-3 -9.15,-4.5 -14.63,-4.5 -7.9,0 -14.11,2.63 -18.64,7.88 -4.54,5.25 -6.8,12.31 -6.8,21.19 0,8.88 2.3,15.87 6.9,20.99 4.6,5.12 10.78,7.68 18.54,7.68 5.61,0 10.57,-1.43 14.87,-4.31 4.31,-2.87 7.31,-6.72 9,-11.55 l 22.02,8.22 c -3.2,9.59 -9,17.21 -17.42,22.85 -8.42,5.64 -18.1,8.46 -29.06,8.46 -15.2,0 -27.52,-4.83 -36.94,-14.48 -9.43,-9.66 -14.14,-22.28 -14.14,-37.87 0,-15.59 4.73,-28.36 14.19,-38.12 9.46,-9.75 21.82,-14.63 37.09,-14.63 z"
- fill="#f2f4f9"
- id="path7" />
- <path
- d="m 1020.39,160.67 h -53.137 l -11.65,33.27 h -28.48 l 52.95,-146.88 h 27.687 l 52.84,146.89 h -29.06 l -11.16,-33.27 z m -8.02,-22.8 -18.497,-53.72 -18.59,53.72 z"
- fill="#f2f4f9"
- id="path8" />
- <path
- d="m 1109.45,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path9" />
- <path
- d="m 1202.41,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path10" />
- <path
- d="m 1259.07,60.66 c 0,-4.37 1.53,-8.07 4.6,-11.11 3.07,-3.03 6.82,-4.55 11.25,-4.55 4.43,0 8.19,1.52 11.25,4.55 3.07,3.03 4.6,6.74 4.6,11.11 0,4.37 -1.53,8.27 -4.6,11.3 -3.07,3.03 -6.82,4.55 -11.25,4.55 -4.43,0 -8.19,-1.52 -11.25,-4.55 -3.07,-3.03 -4.6,-6.8 -4.6,-11.3 z m 29.07,33.86 v 99.43 h -26.23 V 94.52 Z"
- fill="#f2f4f9"
- id="path11" />
- <path
- d="m 1347.25,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path12" />
- <path
- d="m 1462.53,193.94 c -6.39,1.63 -12.43,2.45 -18.1,2.45 -10.63,0 -18.87,-2.85 -24.71,-8.56 -5.84,-5.71 -8.76,-14.37 -8.76,-25.98 v -44.82 h -15.17 V 94.52 h 15.17 V 63.69 h 25.83 v 30.83 h 22.9 v 22.51 h -22.9 v 40.02 c -0.2,10.7 4.01,16.05 12.62,16.05 3.07,0 6.43,-0.59 10.08,-1.76 l 3.03,22.61 z"
- fill="#f2f4f9"
- id="path13" />
- <path
- d="m 1542.67,193.94 c -0.59,-3.13 -1.08,-6.52 -1.47,-10.18 -2.81,3.85 -6.77,6.95 -11.89,9.3 -5.12,2.35 -10.98,3.52 -17.57,3.52 -10.83,0 -19.56,-2.9 -26.18,-8.71 -6.62,-5.81 -9.93,-13.67 -9.93,-23.58 0,-9.91 3.21,-17.53 9.64,-23.05 6.42,-5.51 15.25,-8.27 26.47,-8.27 h 28.87 v -3.33 c 0,-5.74 -1.73,-10.01 -5.19,-12.82 -3.46,-2.8 -8.29,-4.21 -14.48,-4.21 -10.96,0 -18.82,4.67 -23.58,13.99 l -19.08,-10.86 c 8.55,-16.18 23.32,-24.27 44.33,-24.27 12.66,0 23.06,3.3 31.22,9.88 8.15,6.59 12.23,17.29 12.23,32.1 v 38.07 c 0,10.83 0.62,18.3 1.86,22.41 h -25.25 z m -1.86,-43.06 h -22.8 c -5.02,0 -8.92,1.16 -11.69,3.47 -2.77,2.32 -4.16,5.5 -4.16,9.54 0,4.04 1.45,7.31 4.35,9.59 2.9,2.29 7,3.43 12.28,3.43 6.46,0 11.74,-1.96 15.85,-5.87 4.11,-3.91 6.17,-9.98 6.17,-18.2 z"
- fill="#f2f4f9"
- id="path14" />
- <path
- d="m 1682.71,132.78 v 61.16 h -26.13 v -55.49 c 0,-9.07 -2.05,-15.17 -6.17,-18.3 -4.18,-3.2 -8.81,-4.79 -13.9,-4.79 -5.94,0 -11.19,1.79 -15.75,5.38 -4.7,3.72 -7.05,9.82 -7.05,18.3 v 54.9 h -26.03 V 94.52 h 24.66 l 0.78,9.88 c 2.8,-3.91 6.83,-7.05 12.09,-9.39 5.25,-2.35 10.91,-3.52 16.98,-3.52 12.85,0 22.82,3.61 29.9,10.81 7.08,7.21 10.62,17.37 10.62,30.48 z"
- fill="#f2f4f9"
- id="path15" />
- <path
- d="m 1762.08,193.94 c -6.39,1.63 -12.43,2.45 -18.1,2.45 -10.63,0 -18.87,-2.85 -24.71,-8.56 -5.84,-5.71 -8.76,-14.37 -8.76,-25.98 v -44.82 h -15.17 V 94.52 h 15.17 V 63.69 h 25.83 v 30.83 h 22.9 v 22.51 h -22.9 v 40.02 c -0.2,10.7 4.01,16.05 12.62,16.05 3.07,0 6.43,-0.59 10.08,-1.76 l 3.03,22.61 z"
- fill="#f2f4f9"
- id="path16" />
- </g>
- </g>
- <defs
- id="defs18">
- <clipPath
- id="clip0_3_51">
- <rect
- width="1762"
- height="240"
- fill="#ffffff"
- id="rect17"
- x="0"
- y="0" />
- </clipPath>
- <clipPath
- id="clip1_3_51">
- <rect
- width="1426"
- height="154"
- fill="#ffffff"
- transform="translate(336,43)"
- id="rect18"
- x="0"
- y="0" />
- </clipPath>
- <clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath19">
- <rect
- style="fill:none;fill-opacity:1;stroke-width:0.904839"
- id="rect20"
- width="239.88728"
- height="239.55457"
- x="0.14213564"
- y="0.010407645" />
- </clipPath>
- </defs>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- width="240"
- height="240"
- viewBox="0 0 240 240"
- fill="none"
- version="1.1"
- id="svg18"
- sodipodi:docname="icon_monochrome.svg"
- inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
- inkscape:export-filename="icon.svg"
- inkscape:export-xdpi="96"
- inkscape:export-ydpi="96"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:svg="http://www.w3.org/2000/svg">
- <sodipodi:namedview
- id="namedview18"
- pagecolor="#000000"
- bordercolor="#000000"
- borderopacity="0.25"
- inkscape:showpageshadow="2"
- inkscape:pageopacity="0.0"
- inkscape:pagecheckerboard="0"
- inkscape:deskcolor="#d1d1d1"
- inkscape:zoom="2.8284271"
- inkscape:cx="120.38493"
- inkscape:cy="119.32427"
- inkscape:window-width="1920"
- inkscape:window-height="1129"
- inkscape:window-x="-8"
- inkscape:window-y="-8"
- inkscape:window-maximized="1"
- inkscape:current-layer="svg18" />
- <path
- d="m 109.394,6.3814 c 5.848,-5.84187 15.394,-5.84187 21.212,0 l 98.788,98.8876 c 5.848,5.842 10.606,17.374 10.606,25.638 v 90.11 l -0.005,0.356 c -0.206,8.086 -6.881,14.628 -14.995,14.628 H 15 C 6.75759,236.001 2.40473e-5,229.22 0,220.987 v -90.11 c 1.20331e-4,-8.264 4.78834,-19.796 10.6064,-25.638 z M 36,122.001 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.5817,-8 -8,-8 z m 32,0 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.5817,-8 -8,-8 z m 32,0 c -4.4183,0 -8,3.581 -8,8 v 78 h 16 v -78 c 0,-4.419 -3.582,-8 -8,-8 z m 58.393,0.426 c -4.193,-1.395 -8.722,0.873 -10.118,5.065 l -26.796,80.509 h 16.863 l 25.114,-75.457 c 1.395,-4.192 -0.872,-8.721 -5.063,-10.117 z m 30.315,5.065 c -1.395,-4.192 -5.925,-6.46 -10.117,-5.065 -4.192,1.396 -6.46,5.925 -5.065,10.117 l 25.116,75.457 h 16.862 z"
- fill="#18bcf2"
- id="path2"
- clip-path="none"
- style="fill:#ffffff;fill-opacity:1" />
- <g
- id="g3">
- <rect
- style="fill:none;fill-opacity:1;stroke-width:0.904839"
- id="rect2"
- width="239.88728"
- height="239.55457"
- x="0.14213564"
- y="2.0104077" />
- </g>
- <g
- clip-path="url(#clipPath1)"
- id="g16"
- transform="translate(0,2)">
- <path
- d="m 482.363,47.06 v 146.89 h -26.91 v -85.83 l -33.86,85.82 h -26.03 l -33.47,-85.14 v 85.14 h -26.23 V 47.06 h 26.23 l 46.48,114.99 46.88,-114.99 z"
- fill="#f2f4f9"
- id="path3" />
- <path
- d="m 581.292,193.94 c -0.85,-2.74 -1.47,-6.07 -1.86,-9.98 -5.94,8.42 -15.46,12.62 -28.58,12.62 -10.96,0 -20.65,-3.16 -29.06,-9.49 -8.02,-6.46 -12.04,-17.12 -12.04,-32 V 94.51 h 26.23 v 55.29 c 0,8.94 2.09,15.1 6.26,18.5 4.11,3.33 8.84,4.99 14.19,4.99 5.74,0 10.89,-1.79 15.46,-5.38 4.63,-3.65 6.95,-9.75 6.95,-18.3 V 94.52 h 26.32 v 81.03 c 0,6.65 0.65,12.79 1.96,18.4 h -25.83 z"
- fill="#f2f4f9"
- id="path4" />
- <path
- d="m 663.403,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path5" />
- <path
- d="m 720.062,60.66 c 0,-4.37 1.53,-8.07 4.6,-11.11 3.07,-3.03 6.82,-4.55 11.25,-4.55 4.43,0 8.19,1.52 11.25,4.55 3.07,3.03 4.6,6.74 4.6,11.11 0,4.37 -1.53,8.27 -4.6,11.3 -3.07,3.03 -6.82,4.55 -11.25,4.55 -4.43,0 -8.19,-1.52 -11.25,-4.55 -3.07,-3.03 -4.6,-6.8 -4.6,-11.3 z m 29.06,33.86 v 99.43 h -26.23 V 94.52 Z"
- fill="#f2f4f9"
- id="path6" />
- <path
- d="m 818.513,91.48 c 11.42,0 21.28,2.84 29.6,8.51 8.32,5.67 13.88,13.21 16.69,22.61 l -22.41,9 c -1.63,-4.96 -4.58,-8.94 -8.86,-11.94 -4.27,-3 -9.15,-4.5 -14.63,-4.5 -7.9,0 -14.11,2.63 -18.64,7.88 -4.54,5.25 -6.8,12.31 -6.8,21.19 0,8.88 2.3,15.87 6.9,20.99 4.6,5.12 10.78,7.68 18.54,7.68 5.61,0 10.57,-1.43 14.87,-4.31 4.31,-2.87 7.31,-6.72 9,-11.55 l 22.02,8.22 c -3.2,9.59 -9,17.21 -17.42,22.85 -8.42,5.64 -18.1,8.46 -29.06,8.46 -15.2,0 -27.52,-4.83 -36.94,-14.48 -9.43,-9.66 -14.14,-22.28 -14.14,-37.87 0,-15.59 4.73,-28.36 14.19,-38.12 9.46,-9.75 21.82,-14.63 37.09,-14.63 z"
- fill="#f2f4f9"
- id="path7" />
- <path
- d="m 1020.39,160.67 h -53.137 l -11.65,33.27 h -28.48 l 52.95,-146.88 h 27.687 l 52.84,146.89 h -29.06 l -11.16,-33.27 z m -8.02,-22.8 -18.497,-53.72 -18.59,53.72 z"
- fill="#f2f4f9"
- id="path8" />
- <path
- d="m 1109.45,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path9" />
- <path
- d="m 1202.41,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path10" />
- <path
- d="m 1259.07,60.66 c 0,-4.37 1.53,-8.07 4.6,-11.11 3.07,-3.03 6.82,-4.55 11.25,-4.55 4.43,0 8.19,1.52 11.25,4.55 3.07,3.03 4.6,6.74 4.6,11.11 0,4.37 -1.53,8.27 -4.6,11.3 -3.07,3.03 -6.82,4.55 -11.25,4.55 -4.43,0 -8.19,-1.52 -11.25,-4.55 -3.07,-3.03 -4.6,-6.8 -4.6,-11.3 z m 29.07,33.86 v 99.43 h -26.23 V 94.52 Z"
- fill="#f2f4f9"
- id="path11" />
- <path
- d="m 1347.25,175.74 c 4.44,0 7.93,-0.86 10.47,-2.59 2.54,-1.73 3.82,-4.22 3.82,-7.49 0,-4.83 -3.23,-8.03 -9.69,-9.59 l -13.21,-3.62 c -10.05,-2.61 -17.37,-6.36 -21.97,-11.25 -4.6,-4.89 -6.9,-10.89 -6.9,-18.01 0,-9.39 3.39,-17.03 10.18,-22.9 6.78,-5.87 15.95,-8.81 27.5,-8.81 10.18,0 18.69,2.33 25.54,7 6.85,4.67 11.02,11.11 12.53,19.33 l -23.39,5.38 c -0.39,-3.26 -1.86,-5.84 -4.4,-7.73 -2.54,-1.89 -5.71,-2.84 -9.49,-2.84 -3.98,0 -7.11,0.9 -9.39,2.69 -2.28,1.79 -3.42,4.29 -3.42,7.49 0,2.41 0.88,4.4 2.64,5.97 1.76,1.57 4.63,2.9 8.61,4.01 l 13.02,3.43 c 9.2,2.41 16.18,6.13 20.94,11.16 4.76,5.02 7.14,11.12 7.14,18.3 0,9.39 -3.59,16.9 -10.76,22.51 -7.18,5.61 -16.96,8.42 -29.36,8.42 -12.4,0 -21.22,-2.53 -28.23,-7.58 -7.01,-5.05 -11.14,-11.95 -12.38,-20.7 l 23.98,-4.01 c 0.33,3.52 1.92,6.31 4.8,8.37 2.87,2.05 6.69,3.08 11.45,3.08 z"
- fill="#f2f4f9"
- id="path12" />
- <path
- d="m 1462.53,193.94 c -6.39,1.63 -12.43,2.45 -18.1,2.45 -10.63,0 -18.87,-2.85 -24.71,-8.56 -5.84,-5.71 -8.76,-14.37 -8.76,-25.98 v -44.82 h -15.17 V 94.52 h 15.17 V 63.69 h 25.83 v 30.83 h 22.9 v 22.51 h -22.9 v 40.02 c -0.2,10.7 4.01,16.05 12.62,16.05 3.07,0 6.43,-0.59 10.08,-1.76 l 3.03,22.61 z"
- fill="#f2f4f9"
- id="path13" />
- <path
- d="m 1542.67,193.94 c -0.59,-3.13 -1.08,-6.52 -1.47,-10.18 -2.81,3.85 -6.77,6.95 -11.89,9.3 -5.12,2.35 -10.98,3.52 -17.57,3.52 -10.83,0 -19.56,-2.9 -26.18,-8.71 -6.62,-5.81 -9.93,-13.67 -9.93,-23.58 0,-9.91 3.21,-17.53 9.64,-23.05 6.42,-5.51 15.25,-8.27 26.47,-8.27 h 28.87 v -3.33 c 0,-5.74 -1.73,-10.01 -5.19,-12.82 -3.46,-2.8 -8.29,-4.21 -14.48,-4.21 -10.96,0 -18.82,4.67 -23.58,13.99 l -19.08,-10.86 c 8.55,-16.18 23.32,-24.27 44.33,-24.27 12.66,0 23.06,3.3 31.22,9.88 8.15,6.59 12.23,17.29 12.23,32.1 v 38.07 c 0,10.83 0.62,18.3 1.86,22.41 h -25.25 z m -1.86,-43.06 h -22.8 c -5.02,0 -8.92,1.16 -11.69,3.47 -2.77,2.32 -4.16,5.5 -4.16,9.54 0,4.04 1.45,7.31 4.35,9.59 2.9,2.29 7,3.43 12.28,3.43 6.46,0 11.74,-1.96 15.85,-5.87 4.11,-3.91 6.17,-9.98 6.17,-18.2 z"
- fill="#f2f4f9"
- id="path14" />
- <path
- d="m 1682.71,132.78 v 61.16 h -26.13 v -55.49 c 0,-9.07 -2.05,-15.17 -6.17,-18.3 -4.18,-3.2 -8.81,-4.79 -13.9,-4.79 -5.94,0 -11.19,1.79 -15.75,5.38 -4.7,3.72 -7.05,9.82 -7.05,18.3 v 54.9 h -26.03 V 94.52 h 24.66 l 0.78,9.88 c 2.8,-3.91 6.83,-7.05 12.09,-9.39 5.25,-2.35 10.91,-3.52 16.98,-3.52 12.85,0 22.82,3.61 29.9,10.81 7.08,7.21 10.62,17.37 10.62,30.48 z"
- fill="#f2f4f9"
- id="path15" />
- <path
- d="m 1762.08,193.94 c -6.39,1.63 -12.43,2.45 -18.1,2.45 -10.63,0 -18.87,-2.85 -24.71,-8.56 -5.84,-5.71 -8.76,-14.37 -8.76,-25.98 v -44.82 h -15.17 V 94.52 h 15.17 V 63.69 h 25.83 v 30.83 h 22.9 v 22.51 h -22.9 v 40.02 c -0.2,10.7 4.01,16.05 12.62,16.05 3.07,0 6.43,-0.59 10.08,-1.76 l 3.03,22.61 z"
- fill="#f2f4f9"
- id="path16" />
- </g>
- <defs
- id="defs18">
- <clipPath
- id="clip0_3_51">
- <rect
- width="1762"
- height="240"
- fill="#ffffff"
- id="rect17"
- x="0"
- y="0" />
- </clipPath>
- <clipPath
- id="clip1_3_51">
- <rect
- width="1426"
- height="154"
- fill="#ffffff"
- transform="translate(336,43)"
- id="rect18"
- x="0"
- y="0" />
- </clipPath>
- <clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath1">
- <g
- id="g2"
- transform="translate(0,-2)">
- <rect
- style="fill:none;fill-opacity:1;stroke-width:0.904839"
- id="rect1"
- width="239.88728"
- height="239.55457"
- x="0.14213564"
- y="2.0104077" />
- </g>
- </clipPath>
- <clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath3">
- <g
- id="g4">
- <rect
- style="fill:none;fill-opacity:1;stroke-width:0.904839"
- id="rect3"
- width="239.88728"
- height="239.55457"
- x="0.14213564"
- y="2.0104077" />
- </g>
- </clipPath>
- </defs>
-</svg>
+++ /dev/null
-{
- "type": "player",
- "domain": "builtin_player",
- "stage": "alpha",
- "name": "Builtin Web Player",
- "description": "Control playback and listen directly through the Music Assistant web interface.",
- "codeowners": ["@music-assistant"],
- "documentation": "https://music-assistant.io/player-support/builtin/",
- "multi_instance": false,
- "builtin": true,
- "allow_disable": false
-}
+++ /dev/null
-"""Player model implementation for the Built-in Player."""
-
-from __future__ import annotations
-
-from collections.abc import Callable
-from time import time
-
-from aiohttp import web
-from music_assistant_models.builtin_player import BuiltinPlayerEvent, BuiltinPlayerState
-from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
-from music_assistant_models.constants import PLAYER_CONTROL_NATIVE
-from music_assistant_models.enums import (
- BuiltinPlayerEventType,
- ConfigEntryType,
- ContentType,
- EventType,
- PlaybackState,
- PlayerFeature,
- PlayerType,
-)
-from music_assistant_models.media_items import AudioFormat
-
-from music_assistant.constants import (
- CONF_ENTRY_FLOW_MODE_ENFORCED,
- CONF_ENTRY_HTTP_PROFILE_HIDDEN,
- CONF_ENTRY_OUTPUT_CODEC_HIDDEN,
- CONF_MUTE_CONTROL,
- CONF_POWER_CONTROL,
- CONF_VOLUME_CONTROL,
- DEFAULT_STREAM_HEADERS,
- INTERNAL_PCM_FORMAT,
- create_sample_rates_config_entry,
-)
-from music_assistant.helpers.audio import get_player_filter_params
-from music_assistant.helpers.ffmpeg import get_ffmpeg_stream
-from music_assistant.models.player import DeviceInfo, Player, PlayerMedia
-from music_assistant.models.player_provider import PlayerProvider
-
-# If the player does not send an update within this time, it will be considered offline
-DURATION_UNTIL_TIMEOUT = 120 # 60 second extra headroom
-POLL_INTERVAL = 30
-
-
-class BuiltinPlayer(Player):
- """Representation of a Builtin Player."""
-
- last_update: float
- unregister_cbs: list[Callable[[], None]] = []
-
- def __init__(
- self,
- player_id: str,
- provider: PlayerProvider,
- name: str,
- features: tuple[PlayerFeature, ...],
- ) -> None:
- """Initialize the Builtin Player."""
- super().__init__(provider, player_id)
- self._attr_type = PlayerType.PLAYER
- self._attr_power_control = PLAYER_CONTROL_NATIVE
- self._attr_device_info = DeviceInfo()
- self._attr_supported_features = set(features)
- self._attr_needs_poll = True
- self._attr_poll_interval = POLL_INTERVAL
- self._attr_hidden_by_default = True
- self._attr_expose_to_ha_by_default = False
- self.register(name, False)
-
- def unregister_routes(self) -> None:
- """Unregister all routes associated with this player."""
- for cb in self.unregister_cbs:
- cb()
- self.unregister_cbs.clear()
- self._attr_available = False
- self._attr_playback_state = PlaybackState.IDLE
- self._attr_powered = False
- self._attr_needs_poll = False
- self.update_state()
-
- def register(self, player_name: str, update_state: bool = True) -> None:
- """Register the player for playback."""
- if not self.unregister_cbs:
- self.unregister_cbs = [
- self.mass.webserver.register_dynamic_route(
- f"/builtin_player/flow/{self.player_id}.mp3", self._serve_audio_stream
- ),
- ]
-
- self._attr_playback_state = PlaybackState.IDLE
- self._attr_name = player_name
- self._attr_available = True
- self._attr_powered = False
- self._attr_needs_poll = True
- self.last_update = time()
- if update_state:
- self.update_state()
-
- async def get_config_entries(
- self,
- action: str | None = None,
- values: dict[str, ConfigValueType] | None = None,
- ) -> list[ConfigEntry]:
- """Return all (provider/player specific) Config Entries for the player."""
- base_entries = await super().get_config_entries(action=action, values=values)
- return [
- *base_entries,
- CONF_ENTRY_FLOW_MODE_ENFORCED,
- # Hide power/volume/mute control options since they are guaranteed to work
- ConfigEntry(
- key=CONF_POWER_CONTROL,
- type=ConfigEntryType.STRING,
- label=CONF_POWER_CONTROL,
- default_value=PLAYER_CONTROL_NATIVE,
- hidden=True,
- ),
- ConfigEntry(
- key=CONF_VOLUME_CONTROL,
- type=ConfigEntryType.STRING,
- label=CONF_VOLUME_CONTROL,
- default_value=PLAYER_CONTROL_NATIVE,
- hidden=True,
- ),
- ConfigEntry(
- key=CONF_MUTE_CONTROL,
- type=ConfigEntryType.STRING,
- label=CONF_MUTE_CONTROL,
- default_value=PLAYER_CONTROL_NATIVE,
- hidden=True,
- ),
- CONF_ENTRY_HTTP_PROFILE_HIDDEN,
- CONF_ENTRY_OUTPUT_CODEC_HIDDEN,
- create_sample_rates_config_entry([48000]),
- ]
-
- async def stop(self) -> None:
- """Send STOP command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.STOP),
- )
- self._attr_current_media = None
- self.update_state()
-
- async def play(self) -> None:
- """Send PLAY command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.PLAY),
- )
-
- async def pause(self) -> None:
- """Send PAUSE command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.PAUSE),
- )
-
- async def volume_set(self, volume_level: int) -> None:
- """Send VOLUME_SET command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.SET_VOLUME, volume=volume_level),
- )
-
- async def volume_mute(self, muted: bool) -> None:
- """Send VOLUME MUTE command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(
- type=BuiltinPlayerEventType.MUTE if muted else BuiltinPlayerEventType.UNMUTE
- ),
- )
-
- async def play_media(self, media: PlayerMedia) -> None:
- """Handle PLAY MEDIA on player."""
- url = f"builtin_player/flow/{self.player_id}.mp3"
- self._attr_current_media = media
- self._attr_playback_state = PlaybackState.PLAYING
- self.update_state()
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.PLAY_MEDIA, media_url=url),
- )
-
- async def power(self, powered: bool) -> None:
- """Send POWER ON command to player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(
- type=BuiltinPlayerEventType.POWER_ON
- if powered
- else BuiltinPlayerEventType.POWER_OFF
- ),
- )
- if not powered:
- self._attr_powered = False
- self.update_state()
-
- async def poll(self) -> None:
- """
- Poll player for state updates.
-
- This is called by the Player Manager;
- if the 'needs_poll' property is True.
- """
- last_updated = time() - self.last_update
- if last_updated > DURATION_UNTIL_TIMEOUT:
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- self.player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.TIMEOUT),
- )
- self.unregister_routes()
-
- async def on_unload(self) -> None:
- """Handle logic when the player is unloaded from the Player controller."""
- self.unregister_routes()
-
- async def _serve_audio_stream(self, request: web.Request) -> web.StreamResponse:
- """Serve the flow stream audio to a player."""
- player_id = request.path.rsplit(".")[0].rsplit("/")[-1]
- format_str = request.path.rsplit(".")[-1]
- self.logger.debug("Serving audio stream to %s", player_id)
-
- if not (player := self.mass.players.get(player_id)):
- raise web.HTTPNotFound(reason=f"Unknown player: {player_id}")
-
- headers = {
- **DEFAULT_STREAM_HEADERS,
- "Content-Type": f"audio/{format_str}",
- "Accept-Ranges": "none",
- }
-
- resp = web.StreamResponse(status=200, reason="OK", headers=headers)
- resp.enable_chunked_encoding()
-
- await resp.prepare(request)
-
- # return early if this is not a GET request
- if request.method != "GET":
- return resp
-
- # Check for a client probe request (from an iPhone/iPad)
- if (range_header := request.headers.get("Range")) and range_header == "bytes=0-1":
- self.logger.debug("Client is probing the stream.")
- # We don't early exit here since playback would otherwise never start
- # on iOS devices with Home Assistant OS installations.
-
- media = player._current_media
- if media is None:
- raise web.HTTPNotFound(reason="No active media found!")
-
- # TODO: set encoding quality using a bitrate parameter,
- # maybe even dynamic with auto/semiauto switching with bad network?
- if format_str == "mp3":
- stream_format = AudioFormat(content_type=ContentType.MP3)
- else:
- stream_format = AudioFormat(content_type=ContentType.FLAC)
-
- pcm_format = AudioFormat(
- sample_rate=stream_format.sample_rate,
- content_type=INTERNAL_PCM_FORMAT.content_type,
- bit_depth=INTERNAL_PCM_FORMAT.bit_depth,
- channels=INTERNAL_PCM_FORMAT.channels,
- )
-
- async for chunk in get_ffmpeg_stream(
- # Use get_stream helper which handles all media types including UGP streams
- audio_input=self.mass.streams.get_stream(media, pcm_format),
- input_format=pcm_format,
- output_format=stream_format,
- # Apple ignores "Accept-Ranges=none" on iOS and iPadOS for some reason,
- # so we need to slowly feed the music to avoid the Browser stopping and later
- # restarting the audio stream (from a wrong position!) by keeping the buffer short.
- extra_input_args=["-readrate", "1.02", "-readrate_initial_burst", "6"],
- filter_params=get_player_filter_params(self.mass, player_id, pcm_format, stream_format),
- ):
- try:
- await resp.write(chunk)
- except (ConnectionError, ConnectionResetError):
- break
-
- return resp
-
- def update_builtin_player_state(self, state: BuiltinPlayerState) -> None:
- """Update the current state of the player."""
- self._attr_elapsed_time_last_updated = time()
- self.last_update = time()
- self._attr_elapsed_time = float(state.position)
- self._attr_volume_muted = state.muted
- self._attr_volume_level = state.volume
- if not state.powered:
- self._attr_powered = False
- self._attr_playback_state = PlaybackState.IDLE
- elif state.playing:
- self._attr_powered = True
- self._attr_playback_state = PlaybackState.PLAYING
- elif state.paused:
- self._attr_powered = True
- self._attr_playback_state = PlaybackState.PAUSED
- else:
- self._attr_powered = True
- self._attr_playback_state = PlaybackState.IDLE
-
- self.update_state()
+++ /dev/null
-"""Provider implementation for the Built-in Player."""
-
-from __future__ import annotations
-
-from collections.abc import Callable
-from typing import TYPE_CHECKING, cast
-
-import shortuuid
-from music_assistant_models.builtin_player import BuiltinPlayerEvent, BuiltinPlayerState
-from music_assistant_models.enums import BuiltinPlayerEventType, EventType, PlayerFeature
-
-from music_assistant.controllers.webserver.helpers.auth_middleware import get_current_user
-from music_assistant.models.player import Player
-from music_assistant.models.player_provider import PlayerProvider
-
-from .player import BuiltinPlayer
-
-
-class BuiltinPlayerProvider(PlayerProvider):
- """Builtin Player Provider for playing to the Music Assistant Web Interface."""
-
- _unregister_cbs: list[Callable[[], None]]
-
- async def handle_async_init(self) -> None:
- """Handle asynchronous initialization of the provider."""
- self._unregister_cbs = [
- self.mass.register_api_command("builtin_player/register", self.register_player),
- self.mass.register_api_command("builtin_player/unregister", self.unregister_player),
- self.mass.register_api_command("builtin_player/update_state", self.update_player_state),
- ]
-
- async def unload(self, is_removed: bool = False) -> None:
- """
- Handle unload/close of the provider.
-
- Called when provider is deregistered (e.g. MA exiting or config reloading).
- is_removed will be set to True when the provider is removed from the configuration.
- """
- for unload_cb in self._unregister_cbs:
- unload_cb()
-
- async def remove_player(self, player_id: str) -> None:
- """Remove a player."""
- self.mass.signal_event(
- EventType.BUILTIN_PLAYER,
- player_id,
- BuiltinPlayerEvent(type=BuiltinPlayerEventType.TIMEOUT),
- )
- await self.unregister_player(player_id)
-
- async def register_player(self, player_name: str, player_id: str | None) -> Player:
- """Register a player.
-
- Every player must first be registered through this `builtin_player/register` API command
- before any playback can occur.
- Since players queues can time out, this command either will create a new player queue,
- or restore it from the last session.
-
- - player_name: Human readable name of the player, will only be used in case this call
- creates a new queue.
- - player_id: the id of the builtin player, set to None on new sessions. The returned player
- will have a new random player_id
- """
- if player_id is None:
- player_id = f"ma_{shortuuid.random(10).lower()}"
-
- player_features = {
- PlayerFeature.VOLUME_SET,
- PlayerFeature.VOLUME_MUTE,
- PlayerFeature.PAUSE,
- PlayerFeature.POWER,
- }
-
- # special case: user has player filter enabled but wants
- # to use the builtin player on their device, so add it to their filter
- current_user = get_current_user()
- if (
- current_user
- and current_user.player_filter
- and player_id not in current_user.player_filter
- ):
- current_user.player_filter.append(player_id)
- await self.mass.webserver.auth.update_user_filters(
- current_user, player_filter=current_user.player_filter, provider_filter=None
- )
-
- player = self.mass.players.get(player_id)
-
- if player is None:
- player = BuiltinPlayer(
- player_id=player_id,
- provider=self,
- name=player_name,
- features=tuple(player_features),
- )
- await self.mass.players.register_or_update(player)
- else:
- if TYPE_CHECKING:
- player = cast("BuiltinPlayer", player)
- player.register(player_name)
-
- return player
-
- async def unregister_player(self, player_id: str) -> None:
- """Manually unregister a player with `builtin_player/unregister`."""
- if player := self.mass.players.get(player_id):
- if TYPE_CHECKING:
- player = cast("BuiltinPlayer", player)
- player.unregister_routes()
-
- async def update_player_state(self, player_id: str, state: BuiltinPlayerState) -> bool:
- """Update current state of a player.
-
- A player must periodically update the state of through this `builtin_player/update_state`
- API command.
-
- Returns False in case the player already timed out or simply doesn't exist.
- In that case, register the player first with `builtin_player/register`.
- """
- if not (player := self.mass.players.get(player_id)):
- return False
-
- if TYPE_CHECKING:
- player = cast("BuiltinPlayer", player)
-
- player.update_builtin_player_state(state)
-
- return True