Esempio n. 1
0
    async def async_play_media(self, media_type: str, media_id: str,
                               **kwargs: Any) -> None:
        """Play media from a URL or file."""
        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(
                self.hass, media_id)
            media_type = MEDIA_TYPE_MUSIC
            media_id = sourced_media.url

        # Sign and prefix with URL if playing a relative URL
        if media_id[0] == "/":
            media_id = async_sign_path(
                self.hass,
                quote(media_id),
                timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
            )

            # prepend external URL
            hass_url = get_url(self.hass)
            media_id = f"{hass_url}{media_id}"

        if media_type != MEDIA_TYPE_MUSIC:
            LOGGER.error(
                "Invalid media type %s. Only %s is supported",
                media_type,
                MEDIA_TYPE_MUSIC,
            )
            return

        await self._vlc.add(media_id)
        self._state = STATE_PLAYING
Esempio n. 2
0
def async_process_play_media_url(hass: HomeAssistant,
                                 media_content_id: str,
                                 *,
                                 allow_relative_url: bool = False) -> str:
    """Update a media URL with authentication if it points at Home Assistant."""
    if media_content_id[0] != "/" and not is_hass_url(hass, media_content_id):
        return media_content_id

    parsed = yarl.URL(media_content_id)

    if parsed.query:
        logging.getLogger(__name__).debug(
            "Not signing path for content with query param")
    else:
        signed_path = async_sign_path(
            hass,
            quote(parsed.path),
            timedelta(seconds=CONTENT_AUTH_EXPIRY_TIME),
        )
        media_content_id = str(parsed.join(yarl.URL(signed_path)))

    # convert relative URL to absolute URL
    if media_content_id[0] == "/" and not allow_relative_url:
        media_content_id = f"{get_url(hass)}{media_content_id}"

    return media_content_id
Esempio n. 3
0
    async def async_play_media(self, media_type: str, media_id: str,
                               **kwargs) -> None:
        """Play media from a URL or file, launch an application, or tune to a channel."""
        extra: dict[str, Any] = kwargs.get(ATTR_MEDIA_EXTRA) or {}

        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(
                self.hass, media_id)
            media_type = MEDIA_TYPE_URL
            media_id = sourced_media.url

        # Sign and prefix with URL if playing a relative URL
        if media_id[0] == "/":
            media_id = async_sign_path(
                self.hass,
                quote(media_id),
                dt.timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
            )

            # prepend external URL
            hass_url = get_url(self.hass)
            media_id = f"{hass_url}{media_id}"

        if media_type not in PLAY_MEDIA_SUPPORTED_TYPES:
            _LOGGER.error(
                "Invalid media type %s. Only %s, %s, %s, and camera HLS streams are supported",
                media_type,
                MEDIA_TYPE_APP,
                MEDIA_TYPE_CHANNEL,
                MEDIA_TYPE_URL,
            )
            return

        if media_type == MEDIA_TYPE_APP:
            params = {
                param: extra[attr]
                for attr, param in ATTRS_TO_LAUNCH_PARAMS.items()
                if attr in extra
            }

            await self.coordinator.roku.launch(media_id, params)
        elif media_type == MEDIA_TYPE_CHANNEL:
            await self.coordinator.roku.tune(media_id)
        elif media_type == MEDIA_TYPE_URL:
            params = {
                param: extra[attr]
                for (attr, param) in ATTRS_TO_PLAY_VIDEO_PARAMS.items()
                if attr in extra
            }

            await self.coordinator.roku.play_on_roku(media_id, params)
        elif media_type == FORMAT_CONTENT_TYPE[HLS_PROVIDER]:
            params = {
                "MediaType": "hls",
            }

            await self.coordinator.roku.play_on_roku(media_id, params)

        await self.coordinator.async_request_refresh()
    async def async_play_media(self, media_type, media_id, **kwargs):
        """Play a piece of media."""
        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(self.hass, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url

        # If media ID is a relative URL, we serve it from HA.
        # Create a signed path.
        if media_id[0] == "/":
            # Sign URL with Home Assistant Cast User
            config_entries = self.hass.config_entries.async_entries(CAST_DOMAIN)
            user_id = config_entries[0].data["user_id"]
            user = await self.hass.auth.async_get_user(user_id)
            if user.refresh_tokens:
                refresh_token: RefreshToken = list(user.refresh_tokens.values())[0]

                media_id = async_sign_path(
                    self.hass,
                    refresh_token.id,
                    media_id,
                    timedelta(minutes=5),
                )

            # prepend external URL
            hass_url = get_url(self.hass, prefer_external=True)
            media_id = f"{hass_url}{media_id}"

        await self.hass.async_add_executor_job(
            ft.partial(self.play_media, media_type, media_id, **kwargs)
        )
Esempio n. 5
0
def websocket_sign_path(
        hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
    """Handle a sign path request."""
    connection.send_message(websocket_api.result_message(msg['id'], {
        'path': async_sign_path(hass, connection.refresh_token_id, msg['path'],
                                timedelta(seconds=msg['expires']))
        }))
Esempio n. 6
0
    async def async_play_media(self, media_type, media_id, **kwargs):
        """Play a piece of media."""
        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(
                self.hass, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url

        # If media ID is a relative URL, we serve it from HA.
        # Create a signed path.
        if media_id[0] == "/":
            # Sign URL with Safegate Pro Cast User
            config_entry_id = self.registry_entry.config_entry_id
            config_entry = self.hass.config_entries.async_get_entry(
                config_entry_id)
            user_id = config_entry.data["user_id"]
            user = await self.hass.auth.async_get_user(user_id)
            if user.refresh_tokens:
                refresh_token: RefreshToken = list(
                    user.refresh_tokens.values())[0]

                media_id = async_sign_path(
                    self.hass,
                    refresh_token.id,
                    quote(media_id),
                    timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
                )

            # prepend external URL
            hass_url = get_url(self.hass, prefer_external=True)
            media_id = f"{hass_url}{media_id}"

        await self.hass.async_add_executor_job(
            ft.partial(self.play_media, media_type, media_id, **kwargs))
Esempio n. 7
0
    async def async_play_media(self, media_type, media_id, **kwargs):
        """Play media from media_source."""
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(self.hass, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url

            # If media ID is a relative URL, we serve it from HA.
            if media_id[0] == "/":
                user = await self.hass.auth.async_get_owner()
                if user.refresh_tokens:
                    refresh_token: RefreshToken = list(user.refresh_tokens.values())[0]

                    # Use kwargs so it works both before and after the change in Home Assistant 2022.2
                    media_id = async_sign_path(
                        hass=self.hass,
                        refresh_token_id=refresh_token.id,
                        path=media_id,
                        expiration=timedelta(minutes=5)
                    )

                # Prepend external URL.
                hass_url = get_url(self.hass, allow_internal=True)
                media_id = f"{hass_url}{media_id}"

            _LOGGER.info("Meural device %s: Playing media. Media type is %s, previewing image from %s", self.name, media_type, media_id)
            await self.local_meural.send_postcard(media_id, media_type)

        # Play gallery (playlist or album) by ID.
        elif media_type in ['playlist']:
            _LOGGER.info("Meural device %s: Playing media. Media type is %s, playing gallery %s", self.name, media_type, media_id)
            await self.local_meural.send_change_gallery(media_id)

        # "Preview image from URL.
        elif media_type in [ 'image/jpg', 'image/png', 'image/jpeg' ]:
            _LOGGER.info("Meural device %s: Playing media. Media type is %s, previewing image from %s", self.name, media_type, media_id)
            await self.local_meural.send_postcard(media_id, media_type)

        # Play item (artwork) by ID. Play locally if item is in currently displayed gallery. If not, play using Meural server."""
        elif media_type in ['item']:
            if media_id.isdigit():
                currentgallery_id = self._gallery_status["current_gallery"]
                currentitems = await self.local_meural.send_get_items_by_gallery(currentgallery_id)
                in_playlist = next((g["title"] for g in currentitems if g["id"] == media_id), None)
                if in_playlist is None:
                    _LOGGER.info("Meural device %s: Playing media. Item %s is not in current gallery, trying to display via Meural server", self.name, media_id)
                    try:
                        await self.meural.device_load_item(self.meural_device_id, media_id)
                    except:
                        _LOGGER.error("Meural device %s: Playing media. Error while trying to display %s item %s via Meural server", self.name, media_type, media_id, exc_info=True)
                else:
                    _LOGGER.info("Meural device %s: Playing media. Item %s is in current gallery %s, trying to display via local device", self.name, media_id, self._gallery_status["current_gallery_name"])
                    await self.local_meural.send_change_item(media_id)
            else:
                _LOGGER.error("Meural device %s: Playing media. ID %s is not an item", self.name, media_id)

        # This is an unsupported media type.
        else:
            _LOGGER.error("Meural device %s: Playing media. Does not support displaying this %s media with ID %s", self.name, media_type, media_id)
Esempio n. 8
0
def async_process_play_media_url(
    hass: HomeAssistant,
    media_content_id: str,
    *,
    allow_relative_url: bool = False,
    for_supervisor_network: bool = False,
) -> str:
    """Update a media URL with authentication if it points at Home Assistant."""
    parsed = yarl.URL(media_content_id)

    if parsed.scheme and parsed.scheme not in ("http", "https"):
        return media_content_id

    if parsed.is_absolute():
        if not is_hass_url(hass, media_content_id):
            return media_content_id
    else:
        if media_content_id[0] != "/":
            raise ValueError("URL is relative, but does not start with a /")

    if parsed.query:
        logging.getLogger(__name__).debug(
            "Not signing path for content with query param"
        )
    elif parsed.path.startswith(PATHS_WITHOUT_AUTH):
        # We don't sign this path if it doesn't need auth. Although signing itself can't hurt,
        # some devices are unable to handle long URLs and the auth signature might push it over.
        pass
    else:
        signed_path = async_sign_path(
            hass,
            quote(parsed.path),
            timedelta(seconds=CONTENT_AUTH_EXPIRY_TIME),
        )
        media_content_id = str(parsed.join(yarl.URL(signed_path)))

    # convert relative URL to absolute URL
    if not parsed.is_absolute() and not allow_relative_url:
        base_url = None
        if for_supervisor_network:
            base_url = get_supervisor_network_url(hass)

        if not base_url:
            try:
                base_url = get_url(hass)
            except NoURLAvailableError as err:
                msg = "Unable to determine Home Assistant URL to send to device"
                if (
                    hass.config.api
                    and hass.config.api.use_ssl
                    and (not hass.config.external_url or not hass.config.internal_url)
                ):
                    msg += ". Configure internal and external URL in general settings."
                raise HomeAssistantError(msg) from err

        media_content_id = f"{base_url}{media_content_id}"

    return media_content_id
Esempio n. 9
0
async def test_auth_access_signed_path_with_refresh_token(
        hass, app, aiohttp_client, hass_access_token):
    """Test access with signed url."""
    app.router.add_post("/", mock_handler)
    app.router.add_get("/another_path", mock_handler)
    await async_setup_auth(hass, app)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(hass,
                                  "/",
                                  timedelta(seconds=5),
                                  refresh_token_id=refresh_token.id)

    req = await client.get(signed_path)
    assert req.status == HTTPStatus.OK
    data = await req.json()
    assert data["user_id"] == refresh_token.user.id

    # Use signature on other path
    req = await client.get("/another_path?{}".format(
        signed_path.split("?")[1]))
    assert req.status == HTTPStatus.UNAUTHORIZED

    # We only allow GET
    req = await client.post(signed_path)
    assert req.status == HTTPStatus.UNAUTHORIZED

    # Never valid as expired in the past.
    expired_signed_path = async_sign_path(hass,
                                          "/",
                                          timedelta(seconds=-5),
                                          refresh_token_id=refresh_token.id)

    req = await client.get(expired_signed_path)
    assert req.status == HTTPStatus.UNAUTHORIZED

    # refresh token gone should also invalidate signature
    await hass.auth.async_remove_refresh_token(refresh_token)
    req = await client.get(signed_path)
    assert req.status == HTTPStatus.UNAUTHORIZED
Esempio n. 10
0
async def test_auth_access_signed_path(
        hass, app, aiohttp_client, hass_access_token):
    """Test access with signed url."""
    app.router.add_post('/', mock_handler)
    app.router.add_get('/another_path', mock_handler)
    setup_auth(app, [], True, api_password=None)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(
        hass, refresh_token.id, '/', timedelta(seconds=5)
    )

    req = await client.get(signed_path)
    assert req.status == 200
    data = await req.json()
    assert data['refresh_token_id'] == refresh_token.id
    assert data['user_id'] == refresh_token.user.id

    # Use signature on other path
    req = await client.get(
        '/another_path?{}'.format(signed_path.split('?')[1]))
    assert req.status == 401

    # We only allow GET
    req = await client.post(signed_path)
    assert req.status == 401

    # Never valid as expired in the past.
    expired_signed_path = async_sign_path(
        hass, refresh_token.id, '/', timedelta(seconds=-5)
    )

    req = await client.get(expired_signed_path)
    assert req.status == 401

    # refresh token gone should also invalidate signature
    await hass.auth.async_remove_refresh_token(refresh_token)
    req = await client.get(signed_path)
    assert req.status == 401
Esempio n. 11
0
async def test_auth_access_signed_path(
        hass, app, aiohttp_client, hass_access_token):
    """Test access with signed url."""
    app.router.add_post('/', mock_handler)
    app.router.add_get('/another_path', mock_handler)
    setup_auth(app, [], True, api_password=None)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(
        hass, refresh_token.id, '/', timedelta(seconds=5)
    )

    req = await client.get(signed_path)
    assert req.status == 200
    data = await req.json()
    assert data['refresh_token_id'] == refresh_token.id
    assert data['user_id'] == refresh_token.user.id

    # Use signature on other path
    req = await client.get(
        '/another_path?{}'.format(signed_path.split('?')[1]))
    assert req.status == 401

    # We only allow GET
    req = await client.post(signed_path)
    assert req.status == 401

    # Never valid as expired in the past.
    expired_signed_path = async_sign_path(
        hass, refresh_token.id, '/', timedelta(seconds=-5)
    )

    req = await client.get(expired_signed_path)
    assert req.status == 401

    # refresh token gone should also invalidate signature
    await hass.auth.async_remove_refresh_token(refresh_token)
    req = await client.get(signed_path)
    assert req.status == 401
Esempio n. 12
0
async def test_auth_access_signed_path_with_content_user(hass, app, aiohttp_client):
    """Test access signed url uses content user."""
    await async_setup_auth(hass, app)
    signed_path = async_sign_path(hass, "/", timedelta(seconds=5))
    signature = yarl.URL(signed_path).query["authSig"]
    claims = jwt.decode(
        signature,
        hass.data[DATA_SIGN_SECRET],
        algorithms=["HS256"],
        options={"verify_signature": False},
    )
    assert claims["iss"] == hass.data[STORAGE_KEY]
Esempio n. 13
0
def websocket_sign_path(hass: HomeAssistant,
                        connection: websocket_api.ActiveConnection, msg):
    """Handle a sign path request."""
    connection.send_message(
        websocket_api.result_message(
            msg["id"],
            {
                "path":
                async_sign_path(
                    hass,
                    msg["path"],
                    timedelta(seconds=msg["expires"]),
                )
            },
        ))
Esempio n. 14
0
async def websocket_resolve_media(hass, connection, msg):
    """Resolve media."""
    try:
        media = await async_resolve_media(hass, msg["media_content_id"])
        url = media.url
    except Unresolvable as err:
        connection.send_error(msg["id"], "resolve_media_failed", str(err))
    else:
        if url[0] == "/":
            url = async_sign_path(
                hass,
                connection.refresh_token_id,
                url,
                timedelta(seconds=msg["expires"]),
            )

        connection.send_result(msg["id"], {"url": url, "mime_type": media.mime_type})
Esempio n. 15
0
async def websocket_resolve_media(
    hass: HomeAssistant, connection: ActiveConnection, msg: dict
) -> None:
    """Resolve media."""
    try:
        media = await async_resolve_media(hass, msg["media_content_id"])
        url = media.url
    except Unresolvable as err:
        connection.send_error(msg["id"], "resolve_media_failed", str(err))
    else:
        if url[0] == "/":
            url = async_sign_path(
                hass,
                quote(url),
                timedelta(seconds=msg["expires"]),
            )

        connection.send_result(msg["id"], {"url": url, "mime_type": media.mime_type})
Esempio n. 16
0
async def websocket_resolve_media(hass: HomeAssistant,
                                  connection: ActiveConnection,
                                  msg: dict) -> None:
    """Resolve media."""
    try:
        media = await async_resolve_media(hass, msg["media_content_id"])
    except Unresolvable as err:
        connection.send_error(msg["id"], "resolve_media_failed", str(err))
        return

    data = dataclasses.asdict(media)

    if data["url"][0] == "/":
        data["url"] = async_sign_path(
            hass,
            quote(data["url"]),
            timedelta(seconds=msg["expires"]),
        )

    connection.send_result(msg["id"], data)
Esempio n. 17
0
async def test_auth_access_signed_path_with_query_param(
        hass, app, aiohttp_client, hass_access_token):
    """Test access with signed url and query params."""
    app.router.add_post("/", mock_handler)
    app.router.add_get("/another_path", mock_handler)
    await async_setup_auth(hass, app)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(hass,
                                  "/?test=test",
                                  timedelta(seconds=5),
                                  refresh_token_id=refresh_token.id)

    req = await client.get(signed_path)
    assert req.status == HTTPStatus.OK
    data = await req.json()
    assert data["user_id"] == refresh_token.user.id
Esempio n. 18
0
async def test_auth_access_signed_path_with_query_param_tamper(
        hass, app, aiohttp_client, hass_access_token, base_url: str,
        test_url: str):
    """Test access with signed url and query params that have been tampered with."""
    app.router.add_post("/", mock_handler)
    app.router.add_get("/another_path", mock_handler)
    await async_setup_auth(hass, app)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(hass,
                                  base_url,
                                  timedelta(seconds=5),
                                  refresh_token_id=refresh_token.id)
    url = yarl.URL(signed_path)
    token = url.query.get(SIGN_QUERY_PARAM)

    req = await client.get(f"{test_url}&{SIGN_QUERY_PARAM}={token}")
    assert req.status == HTTPStatus.UNAUTHORIZED
Esempio n. 19
0
async def test_auth_access_signed_path_with_query_param_order(
        hass, app, aiohttp_client, hass_access_token):
    """Test access with signed url and query params different order."""
    app.router.add_post("/", mock_handler)
    app.router.add_get("/another_path", mock_handler)
    await async_setup_auth(hass, app)
    client = await aiohttp_client(app)

    refresh_token = await hass.auth.async_validate_access_token(
        hass_access_token)

    signed_path = async_sign_path(
        hass,
        "/?test=test&foo=bar",
        timedelta(seconds=5),
        refresh_token_id=refresh_token.id,
    )
    url = yarl.URL(signed_path)
    signed_path = f"{url.path}?{SIGN_QUERY_PARAM}={url.query.get(SIGN_QUERY_PARAM)}&foo=bar&test=test"

    req = await client.get(signed_path)
    assert req.status == HTTPStatus.OK
    data = await req.json()
    assert data["user_id"] == refresh_token.user.id
Esempio n. 20
0
    async def async_play_media(self, media_type, media_id, **kwargs):
        """Play a piece of media."""
        # Handle media_source
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(
                self.hass, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url

        # If media ID is a relative URL, we serve it from HA.
        # Create a signed path.
        if media_id[0] == "/":
            media_id = async_sign_path(
                self.hass,
                quote(media_id),
                timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
            )

            # prepend external URL
            hass_url = get_url(self.hass, prefer_external=True)
            media_id = f"{hass_url}{media_id}"

        extra = kwargs.get(ATTR_MEDIA_EXTRA, {})
        metadata = extra.get("metadata")

        # Handle media supported by a known cast app
        if media_type == CAST_DOMAIN:
            try:
                app_data = json.loads(media_id)
                if metadata is not None:
                    app_data["metadata"] = extra.get("metadata")
            except json.JSONDecodeError:
                _LOGGER.error("Invalid JSON in media_content_id")
                raise

            # Special handling for passed `app_id` parameter. This will only launch
            # an arbitrary cast app, generally for UX.
            if "app_id" in app_data:
                app_id = app_data.pop("app_id")
                _LOGGER.info("Starting Cast app by ID %s", app_id)
                await self.hass.async_add_executor_job(
                    self._chromecast.start_app, app_id)
                if app_data:
                    _LOGGER.warning(
                        "Extra keys %s were ignored. Please use app_name to cast media",
                        app_data.keys(),
                    )
                return

            app_name = app_data.pop("app_name")
            try:
                await self.hass.async_add_executor_job(quick_play,
                                                       self._chromecast,
                                                       app_name, app_data)
            except NotImplementedError:
                _LOGGER.error("App %s not supported", app_name)
            return

        # Try the cast platforms
        for platform in self.hass.data[CAST_DOMAIN].values():
            result = await platform.async_play_media(self.hass, self.entity_id,
                                                     self._chromecast,
                                                     media_type, media_id)
            if result:
                return

        # Default to play with the default media receiver
        app_data = {"media_id": media_id, "media_type": media_type, **extra}
        await self.hass.async_add_executor_job(quick_play, self._chromecast,
                                               "default_media_receiver",
                                               app_data)
Esempio n. 21
0
    def play_media(self, media_type: str, media_id: str,
                   **kwargs: Any) -> None:
        """
        Send the play_media command to the media player.

        If media_id is a Plex payload, attempt Plex->Sonos playback.

        If media_id is an Apple Music, Deezer, Sonos, or Tidal share link,
        attempt playback using the respective service.

        If media_type is "playlist", media_id should be a Sonos
        Playlist name.  Otherwise, media_id should be a URI.

        If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
        """
        if spotify.is_spotify_media_type(media_type):
            media_type = spotify.resolve_spotify_media_type(media_type)

        if media_source.is_media_source_id(media_id):
            media_type = MEDIA_TYPE_MUSIC
            media_id = (run_coroutine_threadsafe(
                media_source.async_resolve_media(self.hass, media_id),
                self.hass.loop,
            ).result().url)

        if media_type == "favorite_item_id":
            favorite = self.speaker.favorites.lookup_by_item_id(media_id)
            if favorite is None:
                raise ValueError(f"Missing favorite for media_id: {media_id}")
            self._play_favorite(favorite)
            return

        soco = self.coordinator.soco
        if media_id and media_id.startswith(PLEX_URI_SCHEME):
            plex_plugin = self.speaker.plex_plugin
            media_id = media_id[len(PLEX_URI_SCHEME):]
            payload = json.loads(media_id)
            if isinstance(payload, dict):
                shuffle = payload.pop("shuffle", False)
            else:
                shuffle = False
            media = lookup_plex_media(self.hass, media_type,
                                      json.dumps(payload))
            if not kwargs.get(ATTR_MEDIA_ENQUEUE):
                soco.clear_queue()
            if shuffle:
                self.set_shuffle(True)
            plex_plugin.play_now(media)
            return

        share_link = self.coordinator.share_link
        if share_link.is_share_link(media_id):
            if kwargs.get(ATTR_MEDIA_ENQUEUE):
                share_link.add_share_link_to_queue(media_id)
            else:
                soco.clear_queue()
                share_link.add_share_link_to_queue(media_id)
                soco.play_from_queue(0)
        elif media_type in (MEDIA_TYPE_MUSIC, MEDIA_TYPE_TRACK):
            # If media ID is a relative URL, we serve it from HA.
            # Create a signed path.
            if media_id[0] == "/":
                media_id = async_sign_path(
                    self.hass,
                    quote(media_id),
                    datetime.timedelta(
                        seconds=media_source.DEFAULT_EXPIRY_TIME),
                )

                # prepend external URL
                hass_url = get_url(self.hass, prefer_external=True)
                media_id = f"{hass_url}{media_id}"

            if kwargs.get(ATTR_MEDIA_ENQUEUE):
                soco.add_uri_to_queue(media_id)
            else:
                soco.play_uri(media_id)
        elif media_type == MEDIA_TYPE_PLAYLIST:
            if media_id.startswith("S:"):
                item = media_browser.get_media(
                    self.media.library, media_id,
                    media_type)  # type: ignore[no-untyped-call]
                soco.play_uri(item.get_uri())
                return
            try:
                playlists = soco.get_sonos_playlists()
                playlist = next(p for p in playlists if p.title == media_id)
            except StopIteration:
                _LOGGER.error('Could not find a Sonos playlist named "%s"',
                              media_id)
            else:
                soco.clear_queue()
                soco.add_to_queue(playlist)
                soco.play_from_queue(0)
        elif media_type in PLAYABLE_MEDIA_TYPES:
            item = media_browser.get_media(
                self.media.library, media_id,
                media_type)  # type: ignore[no-untyped-call]

            if not item:
                _LOGGER.error('Could not find "%s" in the library', media_id)
                return

            soco.play_uri(item.get_uri())
        else:
            _LOGGER.error('Sonos does not support a media type of "%s"',
                          media_type)
Esempio n. 22
0
 def get_signed_path(hass, connection, msg):
     connection.send_result(
         msg["id"],
         {"path": async_sign_path(hass, "/", timedelta(seconds=5))})
Esempio n. 23
0
 async def mock_handler(request):
     """Return signed path."""
     return web.json_response(
         data={"path": async_sign_path(hass, "/", timedelta(seconds=-5))})