diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 82a7e1ee4f5..2374eca2e6a 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -95,7 +95,7 @@ def _get_media_item( @bind_hass async def async_browse_media( hass: HomeAssistant, - media_content_id: str, + media_content_id: str | None, *, content_filter: Callable[[BrowseMedia], bool] | None = None, ) -> BrowseMediaSource: diff --git a/homeassistant/components/vlc_telnet/manifest.json b/homeassistant/components/vlc_telnet/manifest.json index 9c019abbf46..aa3721fe645 100644 --- a/homeassistant/components/vlc_telnet/manifest.json +++ b/homeassistant/components/vlc_telnet/manifest.json @@ -1,6 +1,6 @@ { "domain": "vlc_telnet", - "name": "VLC media player Telnet", + "name": "VLC media player via Telnet", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vlc_telnet", "requirements": ["aiovlc==0.1.0"], diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py index ae46370dc40..11930aada99 100644 --- a/homeassistant/components/vlc_telnet/media_player.py +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -2,17 +2,21 @@ from __future__ import annotations from collections.abc import Awaitable, Callable, Coroutine -from datetime import datetime +from datetime import datetime, timedelta from functools import wraps from typing import Any, TypeVar +from urllib.parse import quote from aiovlc.client import Client from aiovlc.exceptions import AuthError, CommandError, ConnectError from typing_extensions import Concatenate, ParamSpec -from homeassistant.components.media_player import MediaPlayerEntity +from homeassistant.components import media_source +from homeassistant.components.http.auth import async_sign_path +from homeassistant.components.media_player import BrowseMedia, MediaPlayerEntity from homeassistant.components.media_player.const import ( MEDIA_TYPE_MUSIC, + SUPPORT_BROWSE_MEDIA, SUPPORT_CLEAR_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, @@ -31,6 +35,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.network import get_url import homeassistant.util.dt as dt_util from .const import DATA_AVAILABLE, DATA_VLC, DEFAULT_NAME, DOMAIN, LOGGER @@ -49,6 +54,7 @@ SUPPORT_VLC = ( | SUPPORT_STOP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET + | SUPPORT_BROWSE_MEDIA ) _T = TypeVar("_T", bound="VlcDevice") @@ -170,10 +176,16 @@ class VlcDevice(MediaPlayerEntity): self._media_artist = data.get(0, {}).get("artist") self._media_title = data.get(0, {}).get("title") - if not self._media_title: - # Fall back to filename. - if data_info := data.get("data"): - self._media_title = data_info["filename"] + if self._media_title: + return + + # Fall back to filename. + if data_info := data.get("data"): + self._media_title = data_info["filename"] + + # Strip out auth signatures if streaming local media + if self._media_title and (pos := self._media_title.find("?authSig=")) != -1: + self._media_title = self._media_title[:pos] @property def name(self) -> str: @@ -290,6 +302,24 @@ class VlcDevice(MediaPlayerEntity): 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", @@ -321,3 +351,13 @@ class VlcDevice(MediaPlayerEntity): """Enable/disable shuffle mode.""" shuffle_command = "on" if shuffle else "off" await self._vlc.random(shuffle_command) + + async def async_browse_media( + self, media_content_type: str | None = None, media_content_id: str | None = None + ) -> BrowseMedia: + """Implement the websocket media browsing helper.""" + return await media_source.async_browse_media( + self.hass, + media_content_id, + content_filter=lambda item: item.media_content_type.startswith("audio/"), + )