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)
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)
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)