<reponame>stravinci/AIS-home-assistant<gh_stars>0
"""
Support to interact with a ExoPlayer on Android via HTTO and MQTT.

"""
import asyncio
import json
import logging
from typing import Optional

import homeassistant.components.ais_dom.ais_global as ais_global
from homeassistant.components.media_player import (
    SUPPORT_NEXT_TRACK,
    SUPPORT_PAUSE,
    SUPPORT_PLAY,
    SUPPORT_PLAY_MEDIA,
    SUPPORT_PREVIOUS_TRACK,
    SUPPORT_SEEK,
    SUPPORT_SELECT_SOUND_MODE,
    SUPPORT_SELECT_SOURCE,
    SUPPORT_SHUFFLE_SET,
    SUPPORT_STOP,
    MediaPlayerEntity,
)
from homeassistant.components.media_player.const import (
    SUPPORT_BROWSE_MEDIA,
    SUPPORT_VOLUME_MUTE,
    SUPPORT_VOLUME_SET,
    SUPPORT_VOLUME_STEP,
)
from homeassistant.const import (
    CONF_IP_ADDRESS,
    CONF_NAME,
    STATE_IDLE,
    STATE_OFF,
    STATE_PAUSED,
    STATE_PLAYING,
    STATE_UNAVAILABLE,
)
import homeassistant.util.dt as dt_util

from .media_browser import browse_media

_LOGGER = logging.getLogger(__name__)

SUPPORT_EXO = (
    SUPPORT_PAUSE
    | SUPPORT_PREVIOUS_TRACK
    | SUPPORT_NEXT_TRACK
    | SUPPORT_PLAY_MEDIA
    | SUPPORT_PLAY
    | SUPPORT_STOP
    | SUPPORT_VOLUME_SET
    | SUPPORT_VOLUME_MUTE
    | SUPPORT_VOLUME_STEP
    | SUPPORT_SEEK
    | SUPPORT_SELECT_SOURCE
    | SUPPORT_SELECT_SOUND_MODE
    | SUPPORT_SHUFFLE_SET
    | SUPPORT_BROWSE_MEDIA
)

DEFAULT_NAME = "<NAME>"
ATTR_AIS_GROUP = "ais_exo_player_group"
ATTR_MASTER = "master"


def _publish_command_to_frame(hass, device_ip, key, val):
    if device_ip == "localhost":
        hass.services.call(
            "ais_ai_service",
            "publish_command_to_frame",
            {"key": key, "val": val, "ip": "localhost"},
        )
        # set command to group
        for s in ais_global.G_SPEAKERS_GROUP_LIST:
            if s != "media_player.wbudowany_glosnik":
                attr = hass.states.get(s).attributes
                if "device_ip" in attr:
                    # exo player
                    ip = attr["device_ip"]
                    if ip != "localhost":
                        hass.services.call(
                            "ais_ai_service",
                            "publish_command_to_frame",
                            {"key": key, "val": val, "ip": ip},
                        )
                else:
                    state = hass.states.get(s).state
                    if state not in (STATE_UNAVAILABLE, STATE_OFF):
                        # other player (not exo)
                        if key == "setVolume":
                            hass.services.call(
                                "media_player",
                                "volume_set",
                                {"entity_id": s, "volume_level": val},
                            )
                        elif key == "setPlayerShuffle":
                            hass.services.call(
                                "media_player",
                                "shuffle_set",
                                {"entity_id": s, "shuffle": val},
                            )
                        elif key == "seekTo":
                            hass.services.call(
                                "media_player",
                                "media_seek",
                                {"entity_id": s, "seek_position": val},
                            )
                        elif key == "upVolume":
                            hass.services.call(
                                "media_player", "volume_up", {"entity_id": s}
                            )
                        elif key == "downVolume":
                            hass.services.call(
                                "media_player", "volume_down", {"entity_id": s}
                            )
                        elif key == "pauseAudio":
                            if val:
                                hass.services.call(
                                    "media_player", "media_pause", {"entity_id": s}
                                )
                            else:
                                hass.services.call(
                                    "media_player", "media_play", {"entity_id": s}
                                )
                        elif key == "playAudio":
                            hass.services.call(
                                "media_player",
                                "play_media",
                                {
                                    "entity_id": s,
                                    "media_content_id": val,
                                    "media_content_type": "music",
                                },
                            )
                        elif key == "playAudioFullInfo":
                            hass.services.call(
                                "media_player",
                                "play_media",
                                {
                                    "entity_id": s,
                                    "media_content_id": val["media_content_id"],
                                    "media_content_type": "music",
                                },
                            )

        # set setAudioInfo to all ais speakers
        if key in ("setAudioInfo", "playAudioFullInfo"):
            for entity in hass.states.async_all():
                if entity.entity_id.startswith("media_player."):
                    if (
                        "device_ip" in entity.attributes
                        and entity.attributes["device_ip"] != "localhost"
                    ):
                        if entity.entity_id not in ais_global.G_SPEAKERS_GROUP_LIST:
                            hass.services.call(
                                "ais_ai_service",
                                "publish_command_to_frame",
                                {
                                    "key": "setAudioInfoNoPlay",
                                    "val": val,
                                    "ip": entity.attributes["device_ip"],
                                },
                            )
    else:
        hass.services.call(
            "ais_ai_service",
            "publish_command_to_frame",
            {"key": key, "val": val, "ip": device_ip},
        )


def _update_state(hass):
    # to keep groups in sync with player
    state = hass.states.get("media_player.wbudowany_glosnik").state
    attributes = hass.states.get("media_player.wbudowany_glosnik").attributes
    new_attr = {}
    list_idx = -1
    for itm in attributes:
        list_idx = list_idx + 1
        new_attr[list_idx] = attributes[itm]
    new_attr["ais_exo_player_group"] = ais_global.G_SPEAKERS_GROUP_LIST
    hass.states.async_set("media_player.wbudowany_glosnik", state, new_attr)


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the ExoPlayer platform."""

    # register services
    def join(service):
        def j(entity_id):
            if entity_id not in ais_global.G_SPEAKERS_GROUP_LIST:
                ais_global.G_SPEAKERS_GROUP_LIST.append(entity_id)

        player = service.data["entity_id"]
        if type(player) is str:
            j(player)
        if type(player) is list:
            for e in player:
                j(e)
        _update_state(hass)

    def unjoin(service):
        player = None
        if "entity_id" in service.data:
            if type(service.data["entity_id"]) is str:
                player = service.data["entity_id"]
            if type(service.data["entity_id"]) is list:
                player = None

        if player is None:
            ais_global.G_SPEAKERS_GROUP_LIST = ["media_player.wbudowany_glosnik"]
        else:
            if player in ais_global.G_SPEAKERS_GROUP_LIST:
                ais_global.G_SPEAKERS_GROUP_LIST.remove(player)
        _update_state(hass)

    def update_attributes(service):
        entity_id = service.data["entity_id"]
        ip = service.data[CONF_IP_ADDRESS]

        # update state
        state = hass.states.get("media_player." + entity_id).state
        attributes = hass.states.get("media_player." + entity_id).attributes
        new_attr = {}
        list_idx = -1
        for itm in attributes:
            list_idx = list_idx + 1
            new_attr[list_idx] = attributes[itm]
        new_attr[CONF_IP_ADDRESS] = ip
        hass.states.async_set("media_player." + entity_id, state, new_attr)

    def player_status_ask(service):
        # get player info and sent to all in network
        attributes = hass.states.get("media_player.wbudowany_glosnik").attributes
        j_media_info = {
            "media_title": attributes.get("media_title", ""),
            "media_source": attributes.get("source", ""),
            "media_stream_image": attributes.get("media_stream_image", ""),
            "media_album_name": attributes.get("media_album_name", ""),
            "setPlayerShuffle": attributes.get("shuffle", ""),
            "setMediaPosition": attributes.get("media_position", ""),
        }
        _LOGGER.info("j_media_info: " + str(j_media_info))
        for entity in hass.states.async_all():
            if entity.entity_id.startswith("media_player."):
                if (
                    "device_ip" in entity.attributes
                    and entity.attributes["device_ip"] != "localhost"
                ):
                    _LOGGER.info(
                        "publish_command_to_frame: "
                        + str(entity.attributes["device_ip"])
                    )
                    hass.services.call(
                        "ais_ai_service",
                        "publish_command_to_frame",
                        {
                            "key": "setAudioInfoNoPlay",
                            "val": j_media_info,
                            "ip": entity.attributes["device_ip"],
                        },
                    )

    def redirect_media(service):
        # ais_cloud.send_audio_to_speaker
        if "entity_id" not in service.data:
            return

        entity_id = service.data["entity_id"]
        state = hass.states.get(ais_global.G_LOCAL_EXO_PLAYER_ENTITY_ID)
        attr = state.attributes
        media_content_id = attr.get("media_content_id")
        if media_content_id is None:
            return

        # 1. pause internal player
        hass.services.call(
            "media_player",
            "media_pause",
            {"entity_id": ais_global.G_LOCAL_EXO_PLAYER_ENTITY_ID},
        )

        # 2. play on selected player
        # TODO
        # selected_player_state = hass.states.get(entity_id)
        # selected_player_attr = selected_player_state.attributes
        # if "device_ip" in selected_player_attr:
        #     # full info to exo player
        #     j_media_info = {
        #         "media_title": attr.get("media_title", ""),
        #         "media_source": attr.get("source", ""),
        #         "media_stream_image": attr.get("media_stream_image", ""),
        #         "media_album_name": attr.get("media_album_name", ""),
        #         "media_content_id": attr.get("media_content_id", ""),
        #         "setPlayerShuffle": attr.get("shuffle", ""),
        #         "setMediaPosition": attr.get("media_position", ""),
        #     }
        #     hass.services.call(
        #         "media_player",
        #         "play_media",
        #         {
        #             "entity_id": entity_id,
        #             "media_content_type": "music",
        #             "media_content_id": json.dumps(j_media_info),
        #         },
        #     )
        # else:
        hass.services.call(
            "media_player",
            "play_media",
            {
                "entity_id": entity_id,
                "media_content_type": "music",
                "media_content_id": media_content_id,
            },
        )

    def play_text_or_url(service):
        # text
        text = service.data["text"].strip()
        # TODO parse url better
        if text.startswith("http"):
            hass.services.call(
                "media_player",
                "play_media",
                {
                    "entity_id": "",
                    "media_content_type": "music",
                    "media_content_id": text,
                },
            )
        else:
            # nothing special here - just say (grouping is done in say_it service)
            hass.services.call("ais_ai_service", "say_it", {"text": text})

    if discovery_info is not None:
        name = discovery_info.get(CONF_NAME)
        _ip = discovery_info.get(CONF_IP_ADDRESS)
        _unique_id = discovery_info.get("unique_id")
    else:
        name = config.get(CONF_NAME)
        _ip = config.get(CONF_IP_ADDRESS)
        _unique_id = config.get("unique_id")
        hass.services.async_register("ais_exo_player", "join", join)
        hass.services.async_register("ais_exo_player", "unjoin", unjoin)
        hass.services.async_register(
            "ais_exo_player", "update_attributes", update_attributes
        )
        hass.services.async_register(
            "ais_exo_player", "player_status_ask", player_status_ask
        )
        hass.services.async_register("ais_exo_player", "redirect_media", redirect_media)
        hass.services.async_register(
            "ais_exo_player", "play_text_or_url", play_text_or_url
        )

    device = ExoPlayerDevice(_ip, _unique_id, name)
    _LOGGER.info("device: " + str(device))
    async_add_devices([device], True)


class ExoPlayerDevice(MediaPlayerEntity):
    """Representation of a ExoPlayer ."""

    # pylint: disable=no-member
    def __init__(self, device_ip, unique_id, name):
        """Initialize the ExoPlayer device."""
        self._device_ip = device_ip
        self._unique_id = unique_id
        self._name = name
        self._status = None
        self._playing = False
        self._qos = 2
        self._stream_image = None
        self._media_title = None
        self._media_source = None
        self._album_name = None
        self._sound_mode = "NORMAL"
        self._playlists = [
            ais_global.G_AN_RADIO,
            ais_global.G_AN_PODCAST,
            ais_global.G_AN_MUSIC,
            ais_global.G_AN_AUDIOBOOK,
            ais_global.G_AN_NEWS,
            ais_global.G_AN_LOCAL,
            ais_global.G_AN_SPOTIFY,
            ais_global.G_AN_FAVORITE,
        ]
        self._currentplaylist = None
        self._media_status_received_time = None
        self._media_position = 0
        self._duration = 0
        self._media_content_id = None
        self._volume_level = 0.5
        self._shuffle = False
        self._assistant_audio = False

        if device_ip == "localhost":
            self._is_master = True
        else:
            self._is_master = False

    def turn_on(self):
        pass

    def turn_off(self):
        pass

    def set_volume_level(self, volume):
        self._volume_level = volume
        vol = int(volume * 100)
        _publish_command_to_frame(self.hass, self._device_ip, "setVolume", vol)

    def select_sound_mode(self, sound_mode):
        self._sound_mode = sound_mode
        self.hass.services.call(
            "ais_amplifier_service", "change_sound_mode", {"mode": sound_mode}
        )

    def clear_playlist(self):
        pass

    @asyncio.coroutine
    def async_added_to_hass(self):
        pass

    def _fetch_status(self):
        """Fetch status from ExoPlayer."""
        # INFO - we are not fetching the status from player
        # exp player and spotify is pushing the status to asystent domowy

    @property
    def source(self):
        """Name of the current input source."""
        return self._currentplaylist

    @property
    def source_list(self):
        """Return the list of available input sources."""
        return self._playlists

    def select_source(self, source):
        """Choose a different available playlist and play it."""
        # TODO
        pass

    @property
    def volume_level(self):
        """Return the volume level of the Player"""
        return self._volume_level

    @property
    def media_duration(self):
        """Return the duration of current playing media in seconds."""
        return float(self._duration) // 1000

    @property
    def shuffle(self):
        """Boolean if shuffle is enabled."""
        return self._shuffle

    def set_shuffle(self, shuffle):
        """Enable/disable shuffle mode."""
        self._shuffle = shuffle
        _publish_command_to_frame(
            self.hass, self._device_ip, "setPlayerShuffle", self._shuffle
        )

    def media_seek(self, position):
        """Seek the media to a specific location."""
        if position == 0:
            _publish_command_to_frame(self.hass, self._device_ip, "seekTo", -5000)
        elif position == 1:
            _publish_command_to_frame(self.hass, self._device_ip, "seekTo", 5000)
        else:
            _publish_command_to_frame(
                self.hass, self._device_ip, "skipTo", position * 1000
            )
            self._media_status_received_time = dt_util.utcnow()
            self._media_position = position * 1000

    def volume_up(self):
        """Service to send the exo the command for volume up."""
        self._volume_level = min(self._volume_level + 0.0667, 1)
        _publish_command_to_frame(self.hass, self._device_ip, "upVolume", True)

    def volume_down(self):
        """Service to send the exo the command for volume down."""
        self._volume_level = max(self._volume_level - 0.0667, 0)
        _publish_command_to_frame(self.hass, self._device_ip, "downVolume", True)

    def mute_volume(self, mute):
        """Service to send the exo the command for mute."""
        pass
        # TODO

    @property
    def sound_mode(self):
        """Return the current matched sound mode."""
        return self._sound_mode

    @property
    def sound_mode_list(self):
        """Return a list of available sound modes."""
        return [
            "NORMAL",
            "BOOST",
            "TREBLE",
            "POP",
            "ROCK",
            "CLASSIC",
            "JAZZ",
            "DANCE",
            "R&P",
        ]

    @property
    def available(self):
        """True if ExoPlayer is available and connected."""
        return True

    def update(self):
        """Get the latest data and update the state."""
        self._fetch_status()

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def media_image_url(self):
        """Return the image url of current playing media."""
        if self._stream_image is None:
            return "http://localhost:8180/static/icons/tile-win-310x150.png"
        return self._stream_image

    @property
    def state(self):
        """Return the media state."""

        if self._playing is False:
            return STATE_PAUSED
        else:
            # STATE_IDLE == 1
            # STATE_BUFFERING == 2
            # Player.STATE_READY == 3
            # STATE_ENDED == 4
            if self._status == 1:
                return STATE_IDLE
            if self._status == 2:
                return STATE_PAUSED
            if self._status == 3:
                return STATE_PLAYING
            if self._status == 4:
                return STATE_PAUSED

        return STATE_OFF

    @property
    def media_title(self):
        """Return the title of current playing media."""
        return self._media_title

    @property
    def media_album_name(self):
        """Album of current playing media"""
        return self._album_name

    @property
    def app_name(self):
        """Name of the current running app."""
        app = None
        if self._media_source is not None:
            app = self._media_source
            if self._album_name is not None:
                app = str(app) + " " + str(self._album_name)
        return app

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return SUPPORT_EXO

    @property
    def media_content_id(self):
        """The media content id"""
        return self._media_content_id

    @property
    def media_stream_image(self):
        """The media content id"""
        return self._stream_image

    @property
    def media_position(self):
        """Position of current playing media in seconds."""
        position = float(self._media_position) // 1000
        # if self._status == 3 and self._media_status_received_time is not None:
        #     position += (dt_util.utcnow() - self._media_status_received_time).total_seconds()
        return int(position)

    @property
    def device_state_attributes(self):
        """Return the specific state attributes of the player."""
        attr = {
            "device_ip": self._device_ip,
            "unique_id": self._unique_id,
            "media_stream_image": self._stream_image,
        }
        """List members in group."""
        attr[ATTR_AIS_GROUP] = ais_global.G_SPEAKERS_GROUP_LIST

        if self._device_ip == "localhost":
            attr["friendly_name"] = "Pod<NAME>"
        else:
            attr["friendly_name"] = self._name
        attr[ATTR_MASTER] = self._is_master

        return attr

    @property
    def unique_id(self) -> Optional[str]:
        """Return a unique ID."""
        return self._unique_id

    @property
    def media_position_updated_at(self):
        """When was the position of the current playing media valid.
        Returns value from homeassistant.util.dt.utcnow().
        """
        return self._media_status_received_time

    def media_play(self):
        """Service to send the ExoPlayer the command for play/pause."""
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", False)
        self._playing = True
        self._status = 3

    def media_pause(self):
        """Service to send the ExoPlayer the command for play/pause."""
        # to have more accurate media_position
        self._fetch_status()
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", True)
        self._playing = False

    def media_stop(self):
        """Service to send the ExoPlayer the command for stop."""
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", True)
        self._playing = False

    def media_next_track(self):
        """Service to send the ExoPlayer the command for next track."""
        if self._media_source == ais_global.G_AN_LOCAL:
            self.hass.services.call("ais_drives_service", "play_next")
        else:
            self.hass.services.call(
                "ais_cloud", "play_next", {"media_source": self._media_source}
            )

    def media_previous_track(self):
        """Service to send the ExoPlayer the command for previous track."""
        if self._media_source == ais_global.G_AN_LOCAL:
            self.hass.services.call("ais_drives_service", "play_prev")
        else:
            self.hass.services.call(
                "ais_cloud", "play_prev", {"media_source": self._media_source}
            )

    def play_media(self, media_type, media_content_id, **kwargs):
        """Send the media player the command for playing a media."""
        if media_type == "ais_content_info":
            j_info = json.loads(media_content_id)
            # set image and name and id on list to be able to set correct bookmark and favorites
            ais_global.G_CURR_MEDIA_CONTENT = j_info
            # play
            self._media_content_id = j_info["media_content_id"]
            self._media_status_received_time = dt_util.utcnow()
            if "media_position_ms" in j_info:
                self._media_position = j_info["media_position_ms"]
            else:
                self._media_position = 0
            if "IMAGE_URL" not in j_info:
                self._stream_image = "/static/icons/tile-win-310x150.png"
            else:
                self._stream_image = j_info["IMAGE_URL"]
            self._media_title = j_info["NAME"]
            if j_info["MEDIA_SOURCE"] == ais_global.G_AN_GOOGLE_ASSISTANT:
                # do no change self._media_source
                self._assistant_audio = True
            else:
                self._assistant_audio = False
                self._media_source = j_info["MEDIA_SOURCE"]
            self._currentplaylist = j_info["MEDIA_SOURCE"]
            if "ALBUM_NAME" in j_info:
                self._album_name = j_info["ALBUM_NAME"]
            else:
                self._album_name = 0
            if "DURATION" in j_info:
                self._duration = j_info["DURATION"]

            j_media_info = {
                "media_title": self._media_title,
                "media_source": self._media_source,
                "media_stream_image": self._stream_image,
                "media_album_name": self._album_name,
                "media_content_id": self._media_content_id,
                "setPlayerShuffle": self._shuffle,
                "setMediaPosition": self._media_position,
            }

            _publish_command_to_frame(
                self.hass, self._device_ip, "playAudioFullInfo", j_media_info
            )

            self._playing = True
            self._status = 3

            # go to media player context on localhost
            if self._device_ip == "localhost":
                # refresh state
                old_state = self.hass.states.get("media_player.wbudowany_glosnik")
                self.hass.states.set(
                    "media_player.wbudowany_glosnik",
                    STATE_PLAYING,
                    old_state.attributes,
                    force_update=True,
                )
                self.hass.services.call(
                    "ais_ai_service",
                    "process_command_from_frame",
                    {"topic": "ais/go_to_player", "payload": ""},
                )

        elif media_type == "ais_info":
            # TODO remove this - it is only used in one case - for local media_extractor
            # set only the audio info
            j_info = json.loads(media_content_id)
            ais_global.G_CURR_MEDIA_CONTENT = j_info
            if "IMAGE_URL" not in j_info:
                self._stream_image = "/static/icons/tile-win-310x150.png"
            else:
                self._stream_image = j_info["IMAGE_URL"]
            self._media_title = j_info["NAME"]
            self._media_source = j_info["MEDIA_SOURCE"]
            self._currentplaylist = j_info["MEDIA_SOURCE"]
            if "ALBUM_NAME" in j_info:
                self._album_name = j_info["ALBUM_NAME"]
            else:
                self._album_name = None
            if "DURATION" in j_info:
                self._duration = j_info["DURATION"]

            try:
                j_media_info = {
                    "media_title": self._media_title,
                    "media_source": self._media_source,
                    "media_stream_image": self._stream_image,
                    "media_album_name": self._album_name,
                }
                _publish_command_to_frame(
                    self.hass, self._device_ip, "setAudioInfo", j_media_info
                )
            except Exception as e:
                _LOGGER.debug("problem to publish setAudioInfo: " + str(e))

            self._playing = True
            self._status = 3
            # go to media player context on localhost
            if self._device_ip == "localhost":
                # refresh state
                old_state = self.hass.states.get("media_player.wbudowany_glosnik")
                self.hass.states.set(
                    "media_player.wbudowany_glosnik",
                    STATE_PLAYING,
                    old_state.attributes,
                    force_update=True,
                )
                self.hass.services.call(
                    "ais_ai_service",
                    "process_command_from_frame",
                    {"topic": "ais/go_to_player", "payload": ""},
                )

        elif media_type == "exo_info":
            self._media_status_received_time = dt_util.utcnow()
            try:
                message = json.loads(media_content_id.decode("utf8").replace("'", '"'))
            except Exception as e:
                message = json.loads(media_content_id)
            try:
                self._volume_level = message.get("currentVolume", 0) / 100
            except Exception as e:
                _LOGGER.warning("no currentVolume info: " + str(e))
            self._status = message.get("currentStatus", 0)
            self._playing = message.get("playing", False)
            self._media_position = message.get("currentPosition", 0)
            if "duration" in message:
                self._duration = message.get("duration", 0)
            temp_stream_image = message.get("media_stream_image", None)
            if temp_stream_image is not None:
                if temp_stream_image.startswith("spotify:image:"):
                    temp_stream_image = temp_stream_image.replace("spotify:image:", "")
                    self._stream_image = "https://i.scdn.co/image/" + temp_stream_image
                else:
                    self._stream_image = temp_stream_image
            self._media_title = message.get("currentMedia", "AI-Speaker")
            self._media_source = message.get("media_source", self._media_source)
            self._album_name = message.get("media_album_name", "AI-Speaker")

            # spotify info
            if self._media_source == ais_global.G_AN_SPOTIFY:
                # shuffle
                self._shuffle = message.get("isShuffling", False)
                # mark track on Spotify list
                state = self.hass.states.get("sensor.spotifylist")
                attr = state.attributes
                for i in range(len(attr)):
                    track = attr.get(i)
                    if track["uri"] == message.get("media_content_id", ""):
                        self.hass.states.async_set("sensor.spotifylist", i, attr)
                        break

            if "giveMeNextOne" in message:
                play_next = message.get("giveMeNextOne", False)
                # play next audio only if the current was not from assistant
                if play_next is True and self._assistant_audio is False:
                    # TODO remove bookmark
                    self.hass.async_add_job(
                        self.hass.services.async_call(
                            "media_player",
                            "media_next_track",
                            {"entity_id": "media_player.wbudowany_glosnik"},
                        )
                    )
        else:
            # do not call async from threads - this will it can lead to a deadlock!!!
            # if media_content_id.startswith("ais_"):
            #     from homeassistant.components.ais_exo_player import media_browser
            #
            #     media_content_id = media_browser.get_media_content_id_form_ais(
            #         self.hass, media_content_id
            #     )
            # this is currently used when the media are taken from gallery
            if media_content_id.startswith("ais_"):
                import requests

                if media_content_id.startswith("ais_tunein"):
                    url_to_call = media_content_id.split("/", 3)[3]
                    try:
                        response_text = requests.get(url_to_call, timeout=2).text
                        response_text = response_text.split("\n")[0]
                        if response_text.endswith(".pls"):
                            response_text = requests.get(response_text, timeout=2).text
                            media_content_id = response_text.split("\n")[1].replace(
                                "File1=", ""
                            )
                        if response_text.startswith("mms:"):
                            response_text = requests.get(
                                response_text.replace("mms:", "http:"), timeout=2
                            ).text
                            media_content_id = response_text.split("\n")[1].replace(
                                "Ref1=", ""
                            )
                    except Exception as e:
                        pass
                elif media_content_id.startswith("ais_spotify"):
                    media_content_id = media_content_id.replace("ais_spotify/", "")
            self._media_content_id = media_content_id
            self._media_position = 0
            self._media_status_received_time = dt_util.utcnow()
            _publish_command_to_frame(
                self.hass, self._device_ip, "playAudio", media_content_id
            )

    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""
        result = await browse_media(self.hass, media_content_type, media_content_id)
        return result

"""
Support to interact with a ExoPlayer on Android via HTTO and MQTT.

"""
import asyncio
import json
import logging
from typing import Optional

import homeassistant.components.ais_dom.ais_global as ais_global
from homeassistant.components.media_player import (
    SUPPORT_NEXT_TRACK,
    SUPPORT_PAUSE,
    SUPPORT_PLAY,
    SUPPORT_PLAY_MEDIA,
    SUPPORT_PREVIOUS_TRACK,
    SUPPORT_SEEK,
    SUPPORT_SELECT_SOUND_MODE,
    SUPPORT_SELECT_SOURCE,
    SUPPORT_SHUFFLE_SET,
    SUPPORT_STOP,
    MediaPlayerEntity,
)
from homeassistant.components.media_player.const import (
    SUPPORT_BROWSE_MEDIA,
    SUPPORT_VOLUME_MUTE,
    SUPPORT_VOLUME_SET,
    SUPPORT_VOLUME_STEP,
)
from homeassistant.const import (
    CONF_IP_ADDRESS,
    CONF_NAME,
    STATE_IDLE,
    STATE_OFF,
    STATE_PAUSED,
    STATE_PLAYING,
    STATE_UNAVAILABLE,
)
import homeassistant.util.dt as dt_util

from .media_browser import browse_media

_LOGGER = logging.getLogger(__name__)

SUPPORT_EXO = (
    SUPPORT_PAUSE
    | SUPPORT_PREVIOUS_TRACK
    | SUPPORT_NEXT_TRACK
    | SUPPORT_PLAY_MEDIA
    | SUPPORT_PLAY
    | SUPPORT_STOP
    | SUPPORT_VOLUME_SET
    | SUPPORT_VOLUME_MUTE
    | SUPPORT_VOLUME_STEP
    | SUPPORT_SEEK
    | SUPPORT_SELECT_SOURCE
    | SUPPORT_SELECT_SOUND_MODE
    | SUPPORT_SHUFFLE_SET
    | SUPPORT_BROWSE_MEDIA
)

DEFAULT_NAME = "<NAME>"
ATTR_AIS_GROUP = "ais_exo_player_group"
ATTR_MASTER = "master"


def _publish_command_to_frame(hass, device_ip, key, val):
    if device_ip == "localhost":
        hass.services.call(
            "ais_ai_service",
            "publish_command_to_frame",
            {"key": key, "val": val, "ip": "localhost"},
        )
        # set command to group
        for s in ais_global.G_SPEAKERS_GROUP_LIST:
            if s != "media_player.wbudowany_glosnik":
                attr = hass.states.get(s).attributes
                if "device_ip" in attr:
                    # exo player
                    ip = attr["device_ip"]
                    if ip != "localhost":
                        hass.services.call(
                            "ais_ai_service",
                            "publish_command_to_frame",
                            {"key": key, "val": val, "ip": ip},
                        )
                else:
                    state = hass.states.get(s).state
                    if state not in (STATE_UNAVAILABLE, STATE_OFF):
                        # other player (not exo)
                        if key == "setVolume":
                            hass.services.call(
                                "media_player",
                                "volume_set",
                                {"entity_id": s, "volume_level": val},
                            )
                        elif key == "setPlayerShuffle":
                            hass.services.call(
                                "media_player",
                                "shuffle_set",
                                {"entity_id": s, "shuffle": val},
                            )
                        elif key == "seekTo":
                            hass.services.call(
                                "media_player",
                                "media_seek",
                                {"entity_id": s, "seek_position": val},
                            )
                        elif key == "upVolume":
                            hass.services.call(
                                "media_player", "volume_up", {"entity_id": s}
                            )
                        elif key == "downVolume":
                            hass.services.call(
                                "media_player", "volume_down", {"entity_id": s}
                            )
                        elif key == "pauseAudio":
                            if val:
                                hass.services.call(
                                    "media_player", "media_pause", {"entity_id": s}
                                )
                            else:
                                hass.services.call(
                                    "media_player", "media_play", {"entity_id": s}
                                )
                        elif key == "playAudio":
                            hass.services.call(
                                "media_player",
                                "play_media",
                                {
                                    "entity_id": s,
                                    "media_content_id": val,
                                    "media_content_type": "music",
                                },
                            )
                        elif key == "playAudioFullInfo":
                            hass.services.call(
                                "media_player",
                                "play_media",
                                {
                                    "entity_id": s,
                                    "media_content_id": val["media_content_id"],
                                    "media_content_type": "music",
                                },
                            )

        # set setAudioInfo to all ais speakers
        if key in ("setAudioInfo", "playAudioFullInfo"):
            for entity in hass.states.async_all():
                if entity.entity_id.startswith("media_player."):
                    if (
                        "device_ip" in entity.attributes
                        and entity.attributes["device_ip"] != "localhost"
                    ):
                        if entity.entity_id not in ais_global.G_SPEAKERS_GROUP_LIST:
                            hass.services.call(
                                "ais_ai_service",
                                "publish_command_to_frame",
                                {
                                    "key": "setAudioInfoNoPlay",
                                    "val": val,
                                    "ip": entity.attributes["device_ip"],
                                },
                            )
    else:
        hass.services.call(
            "ais_ai_service",
            "publish_command_to_frame",
            {"key": key, "val": val, "ip": device_ip},
        )


def _update_state(hass):
    # to keep groups in sync with player
    state = hass.states.get("media_player.wbudowany_glosnik").state
    attributes = hass.states.get("media_player.wbudowany_glosnik").attributes
    new_attr = {}
    list_idx = -1
    for itm in attributes:
        list_idx = list_idx + 1
        new_attr[list_idx] = attributes[itm]
    new_attr["ais_exo_player_group"] = ais_global.G_SPEAKERS_GROUP_LIST
    hass.states.async_set("media_player.wbudowany_glosnik", state, new_attr)


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
    """Set up the ExoPlayer platform."""

    # register services
    def join(service):
        def j(entity_id):
            if entity_id not in ais_global.G_SPEAKERS_GROUP_LIST:
                ais_global.G_SPEAKERS_GROUP_LIST.append(entity_id)

        player = service.data["entity_id"]
        if type(player) is str:
            j(player)
        if type(player) is list:
            for e in player:
                j(e)
        _update_state(hass)

    def unjoin(service):
        player = None
        if "entity_id" in service.data:
            if type(service.data["entity_id"]) is str:
                player = service.data["entity_id"]
            if type(service.data["entity_id"]) is list:
                player = None

        if player is None:
            ais_global.G_SPEAKERS_GROUP_LIST = ["media_player.wbudowany_glosnik"]
        else:
            if player in ais_global.G_SPEAKERS_GROUP_LIST:
                ais_global.G_SPEAKERS_GROUP_LIST.remove(player)
        _update_state(hass)

    def update_attributes(service):
        entity_id = service.data["entity_id"]
        ip = service.data[CONF_IP_ADDRESS]

        # update state
        state = hass.states.get("media_player." + entity_id).state
        attributes = hass.states.get("media_player." + entity_id).attributes
        new_attr = {}
        list_idx = -1
        for itm in attributes:
            list_idx = list_idx + 1
            new_attr[list_idx] = attributes[itm]
        new_attr[CONF_IP_ADDRESS] = ip
        hass.states.async_set("media_player." + entity_id, state, new_attr)

    def player_status_ask(service):
        # get player info and sent to all in network
        attributes = hass.states.get("media_player.wbudowany_glosnik").attributes
        j_media_info = {
            "media_title": attributes.get("media_title", ""),
            "media_source": attributes.get("source", ""),
            "media_stream_image": attributes.get("media_stream_image", ""),
            "media_album_name": attributes.get("media_album_name", ""),
            "setPlayerShuffle": attributes.get("shuffle", ""),
            "setMediaPosition": attributes.get("media_position", ""),
        }
        _LOGGER.info("j_media_info: " + str(j_media_info))
        for entity in hass.states.async_all():
            if entity.entity_id.startswith("media_player."):
                if (
                    "device_ip" in entity.attributes
                    and entity.attributes["device_ip"] != "localhost"
                ):
                    _LOGGER.info(
                        "publish_command_to_frame: "
                        + str(entity.attributes["device_ip"])
                    )
                    hass.services.call(
                        "ais_ai_service",
                        "publish_command_to_frame",
                        {
                            "key": "setAudioInfoNoPlay",
                            "val": j_media_info,
                            "ip": entity.attributes["device_ip"],
                        },
                    )

    def redirect_media(service):
        # ais_cloud.send_audio_to_speaker
        if "entity_id" not in service.data:
            return

        entity_id = service.data["entity_id"]
        state = hass.states.get(ais_global.G_LOCAL_EXO_PLAYER_ENTITY_ID)
        attr = state.attributes
        media_content_id = attr.get("media_content_id")
        if media_content_id is None:
            return

        # 1. pause internal player
        hass.services.call(
            "media_player",
            "media_pause",
            {"entity_id": ais_global.G_LOCAL_EXO_PLAYER_ENTITY_ID},
        )

        # 2. play on selected player
        # TODO
        # selected_player_state = hass.states.get(entity_id)
        # selected_player_attr = selected_player_state.attributes
        # if "device_ip" in selected_player_attr:
        #     # full info to exo player
        #     j_media_info = {
        #         "media_title": attr.get("media_title", ""),
        #         "media_source": attr.get("source", ""),
        #         "media_stream_image": attr.get("media_stream_image", ""),
        #         "media_album_name": attr.get("media_album_name", ""),
        #         "media_content_id": attr.get("media_content_id", ""),
        #         "setPlayerShuffle": attr.get("shuffle", ""),
        #         "setMediaPosition": attr.get("media_position", ""),
        #     }
        #     hass.services.call(
        #         "media_player",
        #         "play_media",
        #         {
        #             "entity_id": entity_id,
        #             "media_content_type": "music",
        #             "media_content_id": json.dumps(j_media_info),
        #         },
        #     )
        # else:
        hass.services.call(
            "media_player",
            "play_media",
            {
                "entity_id": entity_id,
                "media_content_type": "music",
                "media_content_id": media_content_id,
            },
        )

    def play_text_or_url(service):
        # text
        text = service.data["text"].strip()
        # TODO parse url better
        if text.startswith("http"):
            hass.services.call(
                "media_player",
                "play_media",
                {
                    "entity_id": "",
                    "media_content_type": "music",
                    "media_content_id": text,
                },
            )
        else:
            # nothing special here - just say (grouping is done in say_it service)
            hass.services.call("ais_ai_service", "say_it", {"text": text})

    if discovery_info is not None:
        name = discovery_info.get(CONF_NAME)
        _ip = discovery_info.get(CONF_IP_ADDRESS)
        _unique_id = discovery_info.get("unique_id")
    else:
        name = config.get(CONF_NAME)
        _ip = config.get(CONF_IP_ADDRESS)
        _unique_id = config.get("unique_id")
        hass.services.async_register("ais_exo_player", "join", join)
        hass.services.async_register("ais_exo_player", "unjoin", unjoin)
        hass.services.async_register(
            "ais_exo_player", "update_attributes", update_attributes
        )
        hass.services.async_register(
            "ais_exo_player", "player_status_ask", player_status_ask
        )
        hass.services.async_register("ais_exo_player", "redirect_media", redirect_media)
        hass.services.async_register(
            "ais_exo_player", "play_text_or_url", play_text_or_url
        )

    device = ExoPlayerDevice(_ip, _unique_id, name)
    _LOGGER.info("device: " + str(device))
    async_add_devices([device], True)


class ExoPlayerDevice(MediaPlayerEntity):
    """Representation of a ExoPlayer ."""

    # pylint: disable=no-member
    def __init__(self, device_ip, unique_id, name):
        """Initialize the ExoPlayer device."""
        self._device_ip = device_ip
        self._unique_id = unique_id
        self._name = name
        self._status = None
        self._playing = False
        self._qos = 2
        self._stream_image = None
        self._media_title = None
        self._media_source = None
        self._album_name = None
        self._sound_mode = "NORMAL"
        self._playlists = [
            ais_global.G_AN_RADIO,
            ais_global.G_AN_PODCAST,
            ais_global.G_AN_MUSIC,
            ais_global.G_AN_AUDIOBOOK,
            ais_global.G_AN_NEWS,
            ais_global.G_AN_LOCAL,
            ais_global.G_AN_SPOTIFY,
            ais_global.G_AN_FAVORITE,
        ]
        self._currentplaylist = None
        self._media_status_received_time = None
        self._media_position = 0
        self._duration = 0
        self._media_content_id = None
        self._volume_level = 0.5
        self._shuffle = False
        self._assistant_audio = False

        if device_ip == "localhost":
            self._is_master = True
        else:
            self._is_master = False

    def turn_on(self):
        pass

    def turn_off(self):
        pass

    def set_volume_level(self, volume):
        self._volume_level = volume
        vol = int(volume * 100)
        _publish_command_to_frame(self.hass, self._device_ip, "setVolume", vol)

    def select_sound_mode(self, sound_mode):
        self._sound_mode = sound_mode
        self.hass.services.call(
            "ais_amplifier_service", "change_sound_mode", {"mode": sound_mode}
        )

    def clear_playlist(self):
        pass

    @asyncio.coroutine
    def async_added_to_hass(self):
        pass

    def _fetch_status(self):
        """Fetch status from ExoPlayer."""
        # INFO - we are not fetching the status from player
        # exp player and spotify is pushing the status to asystent domowy

    @property
    def source(self):
        """Name of the current input source."""
        return self._currentplaylist

    @property
    def source_list(self):
        """Return the list of available input sources."""
        return self._playlists

    def select_source(self, source):
        """Choose a different available playlist and play it."""
        # TODO
        pass

    @property
    def volume_level(self):
        """Return the volume level of the Player"""
        return self._volume_level

    @property
    def media_duration(self):
        """Return the duration of current playing media in seconds."""
        return float(self._duration) // 1000

    @property
    def shuffle(self):
        """Boolean if shuffle is enabled."""
        return self._shuffle

    def set_shuffle(self, shuffle):
        """Enable/disable shuffle mode."""
        self._shuffle = shuffle
        _publish_command_to_frame(
            self.hass, self._device_ip, "setPlayerShuffle", self._shuffle
        )

    def media_seek(self, position):
        """Seek the media to a specific location."""
        if position == 0:
            _publish_command_to_frame(self.hass, self._device_ip, "seekTo", -5000)
        elif position == 1:
            _publish_command_to_frame(self.hass, self._device_ip, "seekTo", 5000)
        else:
            _publish_command_to_frame(
                self.hass, self._device_ip, "skipTo", position * 1000
            )
            self._media_status_received_time = dt_util.utcnow()
            self._media_position = position * 1000

    def volume_up(self):
        """Service to send the exo the command for volume up."""
        self._volume_level = min(self._volume_level + 0.0667, 1)
        _publish_command_to_frame(self.hass, self._device_ip, "upVolume", True)

    def volume_down(self):
        """Service to send the exo the command for volume down."""
        self._volume_level = max(self._volume_level - 0.0667, 0)
        _publish_command_to_frame(self.hass, self._device_ip, "downVolume", True)

    def mute_volume(self, mute):
        """Service to send the exo the command for mute."""
        pass
        # TODO

    @property
    def sound_mode(self):
        """Return the current matched sound mode."""
        return self._sound_mode

    @property
    def sound_mode_list(self):
        """Return a list of available sound modes."""
        return [
            "NORMAL",
            "BOOST",
            "TREBLE",
            "POP",
            "ROCK",
            "CLASSIC",
            "JAZZ",
            "DANCE",
            "R&P",
        ]

    @property
    def available(self):
        """True if ExoPlayer is available and connected."""
        return True

    def update(self):
        """Get the latest data and update the state."""
        self._fetch_status()

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def media_image_url(self):
        """Return the image url of current playing media."""
        if self._stream_image is None:
            return "http://localhost:8180/static/icons/tile-win-310x150.png"
        return self._stream_image

    @property
    def state(self):
        """Return the media state."""

        if self._playing is False:
            return STATE_PAUSED
        else:
            # STATE_IDLE == 1
            # STATE_BUFFERING == 2
            # Player.STATE_READY == 3
            # STATE_ENDED == 4
            if self._status == 1:
                return STATE_IDLE
            if self._status == 2:
                return STATE_PAUSED
            if self._status == 3:
                return STATE_PLAYING
            if self._status == 4:
                return STATE_PAUSED

        return STATE_OFF

    @property
    def media_title(self):
        """Return the title of current playing media."""
        return self._media_title

    @property
    def media_album_name(self):
        """Album of current playing media"""
        return self._album_name

    @property
    def app_name(self):
        """Name of the current running app."""
        app = None
        if self._media_source is not None:
            app = self._media_source
            if self._album_name is not None:
                app = str(app) + " " + str(self._album_name)
        return app

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return SUPPORT_EXO

    @property
    def media_content_id(self):
        """The media content id"""
        return self._media_content_id

    @property
    def media_stream_image(self):
        """The media content id"""
        return self._stream_image

    @property
    def media_position(self):
        """Position of current playing media in seconds."""
        position = float(self._media_position) // 1000
        # if self._status == 3 and self._media_status_received_time is not None:
        #     position += (dt_util.utcnow() - self._media_status_received_time).total_seconds()
        return int(position)

    @property
    def device_state_attributes(self):
        """Return the specific state attributes of the player."""
        attr = {
            "device_ip": self._device_ip,
            "unique_id": self._unique_id,
            "media_stream_image": self._stream_image,
        }
        """List members in group."""
        attr[ATTR_AIS_GROUP] = ais_global.G_SPEAKERS_GROUP_LIST

        if self._device_ip == "localhost":
            attr["friendly_name"] = "Pod<NAME>"
        else:
            attr["friendly_name"] = self._name
        attr[ATTR_MASTER] = self._is_master

        return attr

    @property
    def unique_id(self) -> Optional[str]:
        """Return a unique ID."""
        return self._unique_id

    @property
    def media_position_updated_at(self):
        """When was the position of the current playing media valid.
        Returns value from homeassistant.util.dt.utcnow().
        """
        return self._media_status_received_time

    def media_play(self):
        """Service to send the ExoPlayer the command for play/pause."""
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", False)
        self._playing = True
        self._status = 3

    def media_pause(self):
        """Service to send the ExoPlayer the command for play/pause."""
        # to have more accurate media_position
        self._fetch_status()
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", True)
        self._playing = False

    def media_stop(self):
        """Service to send the ExoPlayer the command for stop."""
        _publish_command_to_frame(self.hass, self._device_ip, "pauseAudio", True)
        self._playing = False

    def media_next_track(self):
        """Service to send the ExoPlayer the command for next track."""
        if self._media_source == ais_global.G_AN_LOCAL:
            self.hass.services.call("ais_drives_service", "play_next")
        else:
            self.hass.services.call(
                "ais_cloud", "play_next", {"media_source": self._media_source}
            )

    def media_previous_track(self):
        """Service to send the ExoPlayer the command for previous track."""
        if self._media_source == ais_global.G_AN_LOCAL:
            self.hass.services.call("ais_drives_service", "play_prev")
        else:
            self.hass.services.call(
                "ais_cloud", "play_prev", {"media_source": self._media_source}
            )

    def play_media(self, media_type, media_content_id, **kwargs):
        """Send the media player the command for playing a media."""
        if media_type == "ais_content_info":
            j_info = json.loads(media_content_id)
            # set image and name and id on list to be able to set correct bookmark and favorites
            ais_global.G_CURR_MEDIA_CONTENT = j_info
            # play
            self._media_content_id = j_info["media_content_id"]
            self._media_status_received_time = dt_util.utcnow()
            if "media_position_ms" in j_info:
                self._media_position = j_info["media_position_ms"]
            else:
                self._media_position = 0
            if "IMAGE_URL" not in j_info:
                self._stream_image = "/static/icons/tile-win-310x150.png"
            else:
                self._stream_image = j_info["IMAGE_URL"]
            self._media_title = j_info["NAME"]
            if j_info["MEDIA_SOURCE"] == ais_global.G_AN_GOOGLE_ASSISTANT:
                # do no change self._media_source
                self._assistant_audio = True
            else:
                self._assistant_audio = False
                self._media_source = j_info["MEDIA_SOURCE"]
            self._currentplaylist = j_info["MEDIA_SOURCE"]
            if "ALBUM_NAME" in j_info:
                self._album_name = j_info["ALBUM_NAME"]
            else:
                self._album_name = 0
            if "DURATION" in j_info:
                self._duration = j_info["DURATION"]

            j_media_info = {
                "media_title": self._media_title,
                "media_source": self._media_source,
                "media_stream_image": self._stream_image,
                "media_album_name": self._album_name,
                "media_content_id": self._media_content_id,
                "setPlayerShuffle": self._shuffle,
                "setMediaPosition": self._media_position,
            }

            _publish_command_to_frame(
                self.hass, self._device_ip, "playAudioFullInfo", j_media_info
            )

            self._playing = True
            self._status = 3

            # go to media player context on localhost
            if self._device_ip == "localhost":
                # refresh state
                old_state = self.hass.states.get("media_player.wbudowany_glosnik")
                self.hass.states.set(
                    "media_player.wbudowany_glosnik",
                    STATE_PLAYING,
                    old_state.attributes,
                    force_update=True,
                )
                self.hass.services.call(
                    "ais_ai_service",
                    "process_command_from_frame",
                    {"topic": "ais/go_to_player", "payload": ""},
                )

        elif media_type == "ais_info":
            # TODO remove this - it is only used in one case - for local media_extractor
            # set only the audio info
            j_info = json.loads(media_content_id)
            ais_global.G_CURR_MEDIA_CONTENT = j_info
            if "IMAGE_URL" not in j_info:
                self._stream_image = "/static/icons/tile-win-310x150.png"
            else:
                self._stream_image = j_info["IMAGE_URL"]
            self._media_title = j_info["NAME"]
            self._media_source = j_info["MEDIA_SOURCE"]
            self._currentplaylist = j_info["MEDIA_SOURCE"]
            if "ALBUM_NAME" in j_info:
                self._album_name = j_info["ALBUM_NAME"]
            else:
                self._album_name = None
            if "DURATION" in j_info:
                self._duration = j_info["DURATION"]

            try:
                j_media_info = {
                    "media_title": self._media_title,
                    "media_source": self._media_source,
                    "media_stream_image": self._stream_image,
                    "media_album_name": self._album_name,
                }
                _publish_command_to_frame(
                    self.hass, self._device_ip, "setAudioInfo", j_media_info
                )
            except Exception as e:
                _LOGGER.debug("problem to publish setAudioInfo: " + str(e))

            self._playing = True
            self._status = 3
            # go to media player context on localhost
            if self._device_ip == "localhost":
                # refresh state
                old_state = self.hass.states.get("media_player.wbudowany_glosnik")
                self.hass.states.set(
                    "media_player.wbudowany_glosnik",
                    STATE_PLAYING,
                    old_state.attributes,
                    force_update=True,
                )
                self.hass.services.call(
                    "ais_ai_service",
                    "process_command_from_frame",
                    {"topic": "ais/go_to_player", "payload": ""},
                )

        elif media_type == "exo_info":
            self._media_status_received_time = dt_util.utcnow()
            try:
                message = json.loads(media_content_id.decode("utf8").replace("'", '"'))
            except Exception as e:
                message = json.loads(media_content_id)
            try:
                self._volume_level = message.get("currentVolume", 0) / 100
            except Exception as e:
                _LOGGER.warning("no currentVolume info: " + str(e))
            self._status = message.get("currentStatus", 0)
            self._playing = message.get("playing", False)
            self._media_position = message.get("currentPosition", 0)
            if "duration" in message:
                self._duration = message.get("duration", 0)
            temp_stream_image = message.get("media_stream_image", None)
            if temp_stream_image is not None:
                if temp_stream_image.startswith("spotify:image:"):
                    temp_stream_image = temp_stream_image.replace("spotify:image:", "")
                    self._stream_image = "https://i.scdn.co/image/" + temp_stream_image
                else:
                    self._stream_image = temp_stream_image
            self._media_title = message.get("currentMedia", "AI-Speaker")
            self._media_source = message.get("media_source", self._media_source)
            self._album_name = message.get("media_album_name", "AI-Speaker")

            # spotify info
            if self._media_source == ais_global.G_AN_SPOTIFY:
                # shuffle
                self._shuffle = message.get("isShuffling", False)
                # mark track on Spotify list
                state = self.hass.states.get("sensor.spotifylist")
                attr = state.attributes
                for i in range(len(attr)):
                    track = attr.get(i)
                    if track["uri"] == message.get("media_content_id", ""):
                        self.hass.states.async_set("sensor.spotifylist", i, attr)
                        break

            if "giveMeNextOne" in message:
                play_next = message.get("giveMeNextOne", False)
                # play next audio only if the current was not from assistant
                if play_next is True and self._assistant_audio is False:
                    # TODO remove bookmark
                    self.hass.async_add_job(
                        self.hass.services.async_call(
                            "media_player",
                            "media_next_track",
                            {"entity_id": "media_player.wbudowany_glosnik"},
                        )
                    )
        else:
            # do not call async from threads - this will it can lead to a deadlock!!!
            # if media_content_id.startswith("ais_"):
            #     from homeassistant.components.ais_exo_player import media_browser
            #
            #     media_content_id = media_browser.get_media_content_id_form_ais(
            #         self.hass, media_content_id
            #     )
            # this is currently used when the media are taken from gallery
            if media_content_id.startswith("ais_"):
                import requests

                if media_content_id.startswith("ais_tunein"):
                    url_to_call = media_content_id.split("/", 3)[3]
                    try:
                        response_text = requests.get(url_to_call, timeout=2).text
                        response_text = response_text.split("\n")[0]
                        if response_text.endswith(".pls"):
                            response_text = requests.get(response_text, timeout=2).text
                            media_content_id = response_text.split("\n")[1].replace(
                                "File1=", ""
                            )
                        if response_text.startswith("mms:"):
                            response_text = requests.get(
                                response_text.replace("mms:", "http:"), timeout=2
                            ).text
                            media_content_id = response_text.split("\n")[1].replace(
                                "Ref1=", ""
                            )
                    except Exception as e:
                        pass
                elif media_content_id.startswith("ais_spotify"):
                    media_content_id = media_content_id.replace("ais_spotify/", "")
            self._media_content_id = media_content_id
            self._media_position = 0
            self._media_status_received_time = dt_util.utcnow()
            _publish_command_to_frame(
                self.hass, self._device_ip, "playAudio", media_content_id
            )

    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""
        result = await browse_media(self.hass, media_content_type, media_content_id)
        return result
Support to interact with a ExoPlayer on Android via HTTO and MQTT. # set command to group # exo player # other player (not exo) # set setAudioInfo to all ais speakers # to keep groups in sync with player Set up the ExoPlayer platform. # register services # update state # get player info and sent to all in network # ais_cloud.send_audio_to_speaker # 1. pause internal player # 2. play on selected player # TODO # selected_player_state = hass.states.get(entity_id) # selected_player_attr = selected_player_state.attributes # if "device_ip" in selected_player_attr: #     # full info to exo player #     j_media_info = { #         "media_title": attr.get("media_title", ""), #         "media_source": attr.get("source", ""), #         "media_stream_image": attr.get("media_stream_image", ""), #         "media_album_name": attr.get("media_album_name", ""), #         "media_content_id": attr.get("media_content_id", ""), #         "setPlayerShuffle": attr.get("shuffle", ""), #         "setMediaPosition": attr.get("media_position", ""), #     } #     hass.services.call( #         "media_player", #         "play_media", #         { #             "entity_id": entity_id, #             "media_content_type": "music", #             "media_content_id": json.dumps(j_media_info), #         }, #     ) # else: # text # TODO parse url better # nothing special here - just say (grouping is done in say_it service) Representation of a ExoPlayer . # pylint: disable=no-member Initialize the ExoPlayer device. Fetch status from ExoPlayer. # INFO - we are not fetching the status from player # exp player and spotify is pushing the status to asystent domowy Name of the current input source. Return the list of available input sources. Choose a different available playlist and play it. # TODO Return the volume level of the Player Return the duration of current playing media in seconds. Boolean if shuffle is enabled. Enable/disable shuffle mode. Seek the media to a specific location. Service to send the exo the command for volume up. Service to send the exo the command for volume down. Service to send the exo the command for mute. # TODO Return the current matched sound mode. Return a list of available sound modes. True if ExoPlayer is available and connected. Get the latest data and update the state. Return the name of the device. Return the image url of current playing media. Return the media state. # STATE_IDLE == 1 # STATE_BUFFERING == 2 # Player.STATE_READY == 3 # STATE_ENDED == 4 Return the title of current playing media. Album of current playing media Name of the current running app. Flag media player features that are supported. The media content id The media content id Position of current playing media in seconds. # if self._status == 3 and self._media_status_received_time is not None: #     position += (dt_util.utcnow() - self._media_status_received_time).total_seconds() Return the specific state attributes of the player. List members in group. Return a unique ID. When was the position of the current playing media valid.         Returns value from homeassistant.util.dt.utcnow(). Service to send the ExoPlayer the command for play/pause. Service to send the ExoPlayer the command for play/pause. # to have more accurate media_position Service to send the ExoPlayer the command for stop. Service to send the ExoPlayer the command for next track. Service to send the ExoPlayer the command for previous track. Send the media player the command for playing a media. # set image and name and id on list to be able to set correct bookmark and favorites # play # do no change self._media_source # go to media player context on localhost # refresh state # TODO remove this - it is only used in one case - for local media_extractor # set only the audio info # go to media player context on localhost # refresh state # spotify info # shuffle # mark track on Spotify list # play next audio only if the current was not from assistant # TODO remove bookmark # do not call async from threads - this will it can lead to a deadlock!!! # if media_content_id.startswith("ais_"): #     from homeassistant.components.ais_exo_player import media_browser # #     media_content_id = media_browser.get_media_content_id_form_ais( #         self.hass, media_content_id #     ) # this is currently used when the media are taken from gallery Implement the websocket media browsing helper.