Prefer internal docker URL for VLC telnet when possible (#67090)
parent
65eefcacfc
commit
4ea6ca7f91
|
@ -10,14 +10,22 @@ import yarl
|
||||||
|
|
||||||
from homeassistant.components.http.auth import async_sign_path
|
from homeassistant.components.http.auth import async_sign_path
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.network import get_url, is_hass_url
|
from homeassistant.helpers.network import (
|
||||||
|
get_supervisor_network_url,
|
||||||
|
get_url,
|
||||||
|
is_hass_url,
|
||||||
|
)
|
||||||
|
|
||||||
from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY
|
from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_process_play_media_url(
|
def async_process_play_media_url(
|
||||||
hass: HomeAssistant, media_content_id: str, *, allow_relative_url: bool = False
|
hass: HomeAssistant,
|
||||||
|
media_content_id: str,
|
||||||
|
*,
|
||||||
|
allow_relative_url: bool = False,
|
||||||
|
for_supervisor_network: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Update a media URL with authentication if it points at Home Assistant."""
|
"""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):
|
if media_content_id[0] != "/" and not is_hass_url(hass, media_content_id):
|
||||||
|
@ -39,7 +47,14 @@ def async_process_play_media_url(
|
||||||
|
|
||||||
# convert relative URL to absolute URL
|
# convert relative URL to absolute URL
|
||||||
if media_content_id[0] == "/" and not allow_relative_url:
|
if media_content_id[0] == "/" and not allow_relative_url:
|
||||||
media_content_id = f"{get_url(hass)}{media_content_id}"
|
base_url = None
|
||||||
|
if for_supervisor_network:
|
||||||
|
base_url = get_supervisor_network_url(hass)
|
||||||
|
|
||||||
|
if not base_url:
|
||||||
|
base_url = get_url(hass)
|
||||||
|
|
||||||
|
media_content_id = f"{base_url}{media_content_id}"
|
||||||
|
|
||||||
return media_content_id
|
return media_content_id
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ from homeassistant.components.media_player.const import (
|
||||||
SUPPORT_VOLUME_MUTE,
|
SUPPORT_VOLUME_MUTE,
|
||||||
SUPPORT_VOLUME_SET,
|
SUPPORT_VOLUME_SET,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import SOURCE_HASSIO, ConfigEntry
|
||||||
from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
|
from homeassistant.const import CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
@ -125,6 +125,7 @@ class VlcDevice(MediaPlayerEntity):
|
||||||
manufacturer="VideoLAN",
|
manufacturer="VideoLAN",
|
||||||
name=name,
|
name=name,
|
||||||
)
|
)
|
||||||
|
self._using_addon = config_entry.source == SOURCE_HASSIO
|
||||||
|
|
||||||
@catch_vlc_errors
|
@catch_vlc_errors
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
|
@ -316,7 +317,9 @@ class VlcDevice(MediaPlayerEntity):
|
||||||
)
|
)
|
||||||
|
|
||||||
# If media ID is a relative URL, we serve it from HA.
|
# If media ID is a relative URL, we serve it from HA.
|
||||||
media_id = async_process_play_media_url(self.hass, media_id)
|
media_id = async_process_play_media_url(
|
||||||
|
self.hass, media_id, for_supervisor_network=self._using_addon
|
||||||
|
)
|
||||||
|
|
||||||
await self._vlc.add(media_id)
|
await self._vlc.add(media_id)
|
||||||
self._state = STATE_PLAYING
|
self._state = STATE_PLAYING
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Network helpers."""
|
"""Network helpers."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
@ -15,6 +16,7 @@ from homeassistant.util.network import is_ip_address, is_loopback, normalize_url
|
||||||
|
|
||||||
TYPE_URL_INTERNAL = "internal_url"
|
TYPE_URL_INTERNAL = "internal_url"
|
||||||
TYPE_URL_EXTERNAL = "external_url"
|
TYPE_URL_EXTERNAL = "external_url"
|
||||||
|
SUPERVISOR_NETWORK_HOST = "homeassistant"
|
||||||
|
|
||||||
|
|
||||||
class NoURLAvailableError(HomeAssistantError):
|
class NoURLAvailableError(HomeAssistantError):
|
||||||
|
@ -33,6 +35,31 @@ def is_internal_request(hass: HomeAssistant) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@bind_hass
|
||||||
|
def get_supervisor_network_url(
|
||||||
|
hass: HomeAssistant, *, allow_ssl: bool = False
|
||||||
|
) -> str | None:
|
||||||
|
"""Get URL for home assistant within supervisor network."""
|
||||||
|
if hass.config.api is None or not hass.components.hassio.is_hassio():
|
||||||
|
return None
|
||||||
|
|
||||||
|
scheme = "http"
|
||||||
|
if hass.config.api.use_ssl:
|
||||||
|
# Certificate won't be valid for hostname so this URL usually won't work
|
||||||
|
if not allow_ssl:
|
||||||
|
return None
|
||||||
|
|
||||||
|
scheme = "https"
|
||||||
|
|
||||||
|
return str(
|
||||||
|
yarl.URL.build(
|
||||||
|
scheme=scheme,
|
||||||
|
host=SUPERVISOR_NETWORK_HOST,
|
||||||
|
port=hass.config.api.port,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_hass_url(hass: HomeAssistant, url: str) -> bool:
|
def is_hass_url(hass: HomeAssistant, url: str) -> bool:
|
||||||
"""Return if the URL points at this Home Assistant instance."""
|
"""Return if the URL points at this Home Assistant instance."""
|
||||||
parsed = yarl.URL(normalize_url(url))
|
parsed = yarl.URL(normalize_url(url))
|
||||||
|
@ -53,11 +80,13 @@ def is_hass_url(hass: HomeAssistant, url: str) -> bool:
|
||||||
except NoURLAvailableError:
|
except NoURLAvailableError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
potential_base_factory: Callable[[], str | None]
|
||||||
for potential_base_factory in (
|
for potential_base_factory in (
|
||||||
lambda: hass.config.internal_url,
|
lambda: hass.config.internal_url,
|
||||||
lambda: hass.config.external_url,
|
lambda: hass.config.external_url,
|
||||||
cloud_url,
|
cloud_url,
|
||||||
host_ip,
|
host_ip,
|
||||||
|
lambda: get_supervisor_network_url(hass, allow_ssl=True),
|
||||||
):
|
):
|
||||||
potential_base = potential_base_factory()
|
potential_base = potential_base_factory()
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,11 @@ from homeassistant.components.media_player.browse_media import (
|
||||||
)
|
)
|
||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.config import async_process_ha_core_config
|
||||||
|
|
||||||
|
from tests.common import mock_component
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def mock_sign_path():
|
@pytest.fixture(name="mock_sign_path")
|
||||||
|
def fixture_mock_sign_path():
|
||||||
"""Mock sign path."""
|
"""Mock sign path."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.media_player.browse_media.async_sign_path",
|
"homeassistant.components.media_player.browse_media.async_sign_path",
|
||||||
|
@ -58,3 +60,35 @@ async def test_process_play_media_url(hass, mock_sign_path):
|
||||||
)
|
)
|
||||||
== "http://192.168.123.123:8123/path?hello=world"
|
== "http://192.168.123.123:8123/path?hello=world"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_process_play_media_url_for_addon(hass, mock_sign_path):
|
||||||
|
"""Test it uses the hostname for an addon if available."""
|
||||||
|
await async_process_ha_core_config(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"internal_url": "http://example.local:8123",
|
||||||
|
"external_url": "https://example.com",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Not hassio or hassio not loaded yet, don't use supervisor network url
|
||||||
|
hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123")
|
||||||
|
assert (
|
||||||
|
async_process_play_media_url(hass, "/path", for_supervisor_network=True)
|
||||||
|
!= "http://homeassistant:8123/path?authSig=bla"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Is hassio and not SSL, use an supervisor network url
|
||||||
|
mock_component(hass, "hassio")
|
||||||
|
assert (
|
||||||
|
async_process_play_media_url(hass, "/path", for_supervisor_network=True)
|
||||||
|
== "http://homeassistant:8123/path?authSig=bla"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hassio loaded but using SSL, don't use an supervisor network url
|
||||||
|
hass.config.api = Mock(use_ssl=True, port=8123, local_ip="192.168.123.123")
|
||||||
|
assert (
|
||||||
|
async_process_play_media_url(hass, "/path", for_supervisor_network=True)
|
||||||
|
!= "https://homeassistant:8123/path?authSig=bla"
|
||||||
|
)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from homeassistant.helpers.network import (
|
||||||
_get_external_url,
|
_get_external_url,
|
||||||
_get_internal_url,
|
_get_internal_url,
|
||||||
_get_request_host,
|
_get_request_host,
|
||||||
|
get_supervisor_network_url,
|
||||||
get_url,
|
get_url,
|
||||||
is_hass_url,
|
is_hass_url,
|
||||||
is_internal_request,
|
is_internal_request,
|
||||||
|
@ -715,3 +716,41 @@ async def test_is_hass_url(hass):
|
||||||
assert is_hass_url(hass, "https://example.nabu.casa") is True
|
assert is_hass_url(hass, "https://example.nabu.casa") is True
|
||||||
assert is_hass_url(hass, "http://example.nabu.casa:443") is False
|
assert is_hass_url(hass, "http://example.nabu.casa:443") is False
|
||||||
assert is_hass_url(hass, "http://example.nabu.casa") is False
|
assert is_hass_url(hass, "http://example.nabu.casa") is False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_is_hass_url_addon_url(hass):
|
||||||
|
"""Test is_hass_url with a supervisor network URL."""
|
||||||
|
assert is_hass_url(hass, "http://homeassistant:8123") is False
|
||||||
|
|
||||||
|
hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123")
|
||||||
|
await async_process_ha_core_config(
|
||||||
|
hass,
|
||||||
|
{"internal_url": "http://example.local:8123"},
|
||||||
|
)
|
||||||
|
assert is_hass_url(hass, "http://homeassistant:8123") is False
|
||||||
|
|
||||||
|
mock_component(hass, "hassio")
|
||||||
|
assert is_hass_url(hass, "http://homeassistant:8123")
|
||||||
|
assert not is_hass_url(hass, "https://homeassistant:8123")
|
||||||
|
|
||||||
|
hass.config.api = Mock(use_ssl=True, port=8123, local_ip="192.168.123.123")
|
||||||
|
assert not is_hass_url(hass, "http://homeassistant:8123")
|
||||||
|
assert is_hass_url(hass, "https://homeassistant:8123")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_supervisor_network_url(hass):
|
||||||
|
"""Test get_supervisor_network_url."""
|
||||||
|
assert get_supervisor_network_url(hass) is None
|
||||||
|
|
||||||
|
hass.config.api = Mock(use_ssl=False, port=8123, local_ip="192.168.123.123")
|
||||||
|
await async_process_ha_core_config(hass, {})
|
||||||
|
assert get_supervisor_network_url(hass) is None
|
||||||
|
|
||||||
|
mock_component(hass, "hassio")
|
||||||
|
assert get_supervisor_network_url(hass) == "http://homeassistant:8123"
|
||||||
|
|
||||||
|
hass.config.api = Mock(use_ssl=True, port=8123, local_ip="192.168.123.123")
|
||||||
|
assert get_supervisor_network_url(hass) is None
|
||||||
|
assert (
|
||||||
|
get_supervisor_network_url(hass, allow_ssl=True) == "https://homeassistant:8123"
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue