# Source code for mopidy.core.mixer

    from __future__ import annotations

    import contextlib
    import logging
    from collections.abc import Generator, Iterable
    from typing import TYPE_CHECKING, Any

    from pykka.typing import proxy_method

    from mopidy import exceptions
    from mopidy.internal import validation
    from mopidy.internal.models import MixerState
    from mopidy.types import Percentage

    if TYPE_CHECKING:
        from mopidy.mixer import MixerProxy

    logger = logging.getLogger(__name__)

    @contextlib.contextmanager
    def _mixer_error_handling(mixer: MixerProxy) -> Generator[None, Any, None]:
        try:
            yield
        except exceptions.ValidationError as e:
            logger.error(
                "%s mixer returned bad data: %s",
                mixer.actor_ref.actor_class.__name__,
                e,
            )
        except Exception:
            logger.exception(
                "%s mixer caused an exception.",
                mixer.actor_ref.actor_class.__name__,
            )

    class MixerController:
        def __init__(self, mixer: MixerProxy | None) -> None:
            self._mixer = mixer

        def get_volume(self) -> Percentage | None:
            """Get the volume.

            Integer in range [0..100] or :class:`None` if unknown.

            The volume scale is linear.
            """
            if self._mixer is None:
                return None

            with _mixer_error_handling(self._mixer):
                volume = self._mixer.get_volume().get()
                if volume is not None:
                    validation.check_integer(volume, min=0, max=100)
                return volume

            return None

        def set_volume(self, volume: Percentage) -> bool:
            """Set the volume.

            The volume is defined as an integer in range [0..100].

            The volume scale is linear.

            Returns :class:`True` if call is successful, otherwise :class:`False`.
            """
            validation.check_integer(volume, min=0, max=100)

            if self._mixer is None:
                return False  # TODO: 2.0 return None

            with _mixer_error_handling(self._mixer):
                result = self._mixer.set_volume(volume).get()
                validation.check_instance(result, bool)
                return result

            return False

        def get_mute(self) -> bool | None:
            """Get mute state.

            :class:`True` if muted, :class:`False` unmuted, :class:`None` if
            unknown.
            """
            if self._mixer is None:
                return None

            with _mixer_error_handling(self._mixer):
                mute = self._mixer.get_mute().get()
                if mute is not None:
                    validation.check_instance(mute, bool)
                return mute

            return None

        def set_mute(self, mute: bool) -> bool:
            """Set mute state.

            :class:`True` to mute, :class:`False` to unmute.

            Returns :class:`True` if call is successful, otherwise :class:`False`.
            """
            validation.check_boolean(mute)
            if self._mixer is None:
                return False  # TODO: 2.0 return None

            with _mixer_error_handling(self._mixer):
                result = self._mixer.set_mute(bool(mute)).get()
                validation.check_instance(result, bool)
                return result

            return False

        def _save_state(self) -> MixerState:
            return MixerState(
                volume=self.get_volume(),
                mute=self.get_mute(),
            )

        def _load_state(self, state: MixerState, coverage: Iterable[str]) -> None:
            if state and "mixer" in coverage:
                if state.mute is not None:
                    self.set_mute(state.mute)
                if state.volume is not None:
                    self.set_volume(Percentage(state.volume))

    class MixerControllerProxy:
        get_volume = proxy_method(MixerController.get_volume)
        set_volume = proxy_method(MixerController.set_volume)
        get_mute = proxy_method(MixerController.get_mute)
        set_mute = proxy_method(MixerController.set_mute)