def play_media(self, media_type, media_id, **kwargs):
        """Play media from a URL."""
        extra = kwargs.get(ATTR_MEDIA_EXTRA, {})
        metadata = extra.get("metadata")

        # We do not want this to be forwarded to a group
        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)
                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:
                quick_play(self._chromecast, app_name, app_data)
            except NotImplementedError:
                _LOGGER.error("App %s not supported", app_name)
        # Handle plex
        elif media_id and media_id.startswith(PLEX_URI_SCHEME):
            media_id = media_id[len(PLEX_URI_SCHEME):]
            media, _ = lookup_plex_media(self.hass, media_type, media_id)
            if media is None:
                return
            controller = PlexController()
            self._chromecast.register_handler(controller)
            controller.play_media(media)
        else:
            app_data = {
                "media_id": media_id,
                "media_type": media_type,
                **extra
            }
            quick_play(self._chromecast, "default_media_receiver", app_data)
Beispiel #2
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)
            media_id = spotify.spotify_uri_from_media_browser_url(media_id)

        is_radio = False

        if media_source.is_media_source_id(media_id):
            is_radio = media_id.startswith("media-source://radio_browser/")
            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.
            media_id = async_process_play_media_url(self.hass, media_id)

            if kwargs.get(ATTR_MEDIA_ENQUEUE):
                soco.add_uri_to_queue(media_id)
            else:
                soco.play_uri(media_id, force_radio=is_radio)
        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)
Beispiel #3
0
async def test_lookup_media_for_other_integrations(
    hass,
    entry,
    setup_plex_server,
    requests_mock,
    playqueue_1234,
    playqueue_created,
):
    """Test media lookup for media_player.play_media calls from cast/sonos."""
    CONTENT_ID = '{"library_name": "Music", "artist_name": "Artist"}'
    CONTENT_ID_KEY = "100"
    CONTENT_ID_BAD_MEDIA = '{"library_name": "Music", "artist_name": "Not an Artist"}'
    CONTENT_ID_PLAYQUEUE = '{"playqueue_id": 1234}'
    CONTENT_ID_BAD_PLAYQUEUE = '{"playqueue_id": 1235}'
    CONTENT_ID_SERVER = '{"plex_server": "Plex Server 1", "library_name": "Music", "artist_name": "Artist"}'
    CONTENT_ID_SHUFFLE = (
        '{"library_name": "Music", "artist_name": "Artist", "shuffle": 1}'
    )

    # Test with no Plex integration available
    with pytest.raises(HomeAssistantError) as excinfo:
        lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID)
    assert "Plex integration not configured" in str(excinfo.value)

    with patch(
        "homeassistant.components.plex.PlexServer.connect", side_effect=NotFound
    ):
        # Initialize Plex integration without setting up a server
        with pytest.raises(AssertionError):
            await setup_plex_server()

        # Test with no Plex servers available
        with pytest.raises(HomeAssistantError) as excinfo:
            lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID)
        assert "No Plex servers available" in str(excinfo.value)

    # Complete setup of a Plex server
    await hass.config_entries.async_unload(entry.entry_id)
    await setup_plex_server()

    # Test lookup success
    result = lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID)
    assert isinstance(result, plexapi.audio.Artist)

    # Test media key payload
    result = lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_KEY)
    assert isinstance(result, plexapi.audio.Track)

    # Test with specified server
    result = lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_SERVER)
    assert isinstance(result, plexapi.audio.Artist)

    # Test with media not found
    with patch("plexapi.library.LibrarySection.search", return_value=None):
        with pytest.raises(HomeAssistantError) as excinfo:
            lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_BAD_MEDIA)
        assert "Plex media not found" in str(excinfo.value)

    # Test with playqueue
    requests_mock.get("https://1.2.3.4:32400/playQueues/1234", text=playqueue_1234)
    result = lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_PLAYQUEUE)
    assert isinstance(result, plexapi.playqueue.PlayQueue)

    # Test with invalid playqueue
    requests_mock.get(
        "https://1.2.3.4:32400/playQueues/1235", status_code=HTTPStatus.NOT_FOUND
    )
    with pytest.raises(HomeAssistantError) as excinfo:
        lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_BAD_PLAYQUEUE)
    assert "PlayQueue '1235' could not be found" in str(excinfo.value)

    # Test playqueue is created with shuffle
    requests_mock.post("/playqueues", text=playqueue_created)
    result = lookup_plex_media(hass, MEDIA_TYPE_MUSIC, CONTENT_ID_SHUFFLE)
    assert isinstance(result, plexapi.playqueue.PlayQueue)