Play first item in m3u and pls playlists when casting (#70047)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
0959ee4353
commit
ce302f4540
20 changed files with 484 additions and 5 deletions
|
@ -1,13 +1,25 @@
|
|||
"""Helpers to deal with Cast devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import configparser
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import aiohttp
|
||||
import attr
|
||||
from pychromecast import dial
|
||||
from pychromecast.const import CAST_TYPE_GROUP
|
||||
from pychromecast.models import CastInfo
|
||||
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_PLS_SECTION_PLAYLIST = "playlist"
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class ChromecastInfo:
|
||||
|
@ -155,3 +167,143 @@ class CastStatusListener:
|
|||
else:
|
||||
self._mz_mgr.deregister_listener(self._uuid, self)
|
||||
self._valid = False
|
||||
|
||||
|
||||
class PlaylistError(Exception):
|
||||
"""Exception wrapper for pls and m3u helpers."""
|
||||
|
||||
|
||||
class PlaylistSupported(PlaylistError):
|
||||
"""The playlist is supported by cast devices and should not be parsed."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaylistItem:
|
||||
"""Playlist item."""
|
||||
|
||||
length: str | None
|
||||
title: str | None
|
||||
url: str
|
||||
|
||||
|
||||
def _is_url(url):
|
||||
"""Validate the URL can be parsed and at least has scheme + netloc."""
|
||||
result = urlparse(url)
|
||||
return all([result.scheme, result.netloc])
|
||||
|
||||
|
||||
async def _fetch_playlist(hass, url):
|
||||
"""Fetch a playlist from the given url."""
|
||||
try:
|
||||
session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False)
|
||||
async with session.get(url, timeout=5) as resp:
|
||||
charset = resp.charset or "utf-8"
|
||||
try:
|
||||
playlist_data = (await resp.content.read(64 * 1024)).decode(charset)
|
||||
except ValueError as err:
|
||||
raise PlaylistError(f"Could not decode playlist {url}") from err
|
||||
except asyncio.TimeoutError as err:
|
||||
raise PlaylistError(f"Timeout while fetching playlist {url}") from err
|
||||
except aiohttp.client_exceptions.ClientError as err:
|
||||
raise PlaylistError(f"Error while fetching playlist {url}") from err
|
||||
|
||||
return playlist_data
|
||||
|
||||
|
||||
async def parse_m3u(hass, url):
|
||||
"""Very simple m3u parser.
|
||||
|
||||
Based on https://github.com/dvndrsn/M3uParser/blob/master/m3uparser.py
|
||||
"""
|
||||
m3u_data = await _fetch_playlist(hass, url)
|
||||
m3u_lines = m3u_data.splitlines()
|
||||
|
||||
playlist = []
|
||||
|
||||
length = None
|
||||
title = None
|
||||
|
||||
for line in m3u_lines:
|
||||
line = line.strip()
|
||||
if line.startswith("#EXTINF:"):
|
||||
# Get length and title from #EXTINF line
|
||||
info = line.split("#EXTINF:")[1].split(",", 1)
|
||||
if len(info) != 2:
|
||||
_LOGGER.warning("Ignoring invalid extinf %s in playlist %s", line, url)
|
||||
continue
|
||||
length = info[0].split(" ", 1)
|
||||
title = info[1].strip()
|
||||
elif line.startswith("#EXT-X-VERSION:"):
|
||||
# HLS stream, supported by cast devices
|
||||
raise PlaylistSupported("HLS")
|
||||
elif line.startswith("#"):
|
||||
# Ignore other extensions
|
||||
continue
|
||||
elif len(line) != 0:
|
||||
# Get song path from all other, non-blank lines
|
||||
if not _is_url(line):
|
||||
raise PlaylistError(f"Invalid item {line} in playlist {url}")
|
||||
playlist.append(PlaylistItem(length=length, title=title, url=line))
|
||||
# reset the song variables so it doesn't use the same EXTINF more than once
|
||||
length = None
|
||||
title = None
|
||||
|
||||
return playlist
|
||||
|
||||
|
||||
async def parse_pls(hass, url):
|
||||
"""Very simple pls parser.
|
||||
|
||||
Based on https://github.com/mariob/plsparser/blob/master/src/plsparser.py
|
||||
"""
|
||||
pls_data = await _fetch_playlist(hass, url)
|
||||
|
||||
pls_parser = configparser.ConfigParser()
|
||||
try:
|
||||
pls_parser.read_string(pls_data, url)
|
||||
except configparser.Error as err:
|
||||
raise PlaylistError(f"Can't parse playlist {url}") from err
|
||||
|
||||
if (
|
||||
_PLS_SECTION_PLAYLIST not in pls_parser
|
||||
or pls_parser[_PLS_SECTION_PLAYLIST].getint("Version") != 2
|
||||
):
|
||||
raise PlaylistError(f"Invalid playlist {url}")
|
||||
|
||||
try:
|
||||
num_entries = pls_parser.getint(_PLS_SECTION_PLAYLIST, "NumberOfEntries")
|
||||
except (configparser.NoOptionError, ValueError) as err:
|
||||
raise PlaylistError(f"Invalid NumberOfEntries in playlist {url}") from err
|
||||
|
||||
playlist_section = pls_parser[_PLS_SECTION_PLAYLIST]
|
||||
|
||||
playlist = []
|
||||
for entry in range(1, num_entries + 1):
|
||||
file_option = f"File{entry}"
|
||||
if file_option not in playlist_section:
|
||||
_LOGGER.warning("Missing %s in pls from %s", file_option, url)
|
||||
continue
|
||||
item_url = playlist_section[file_option]
|
||||
if not _is_url(item_url):
|
||||
raise PlaylistError(f"Invalid item {item_url} in playlist {url}")
|
||||
playlist.append(
|
||||
PlaylistItem(
|
||||
length=playlist_section.get(f"Length{entry}"),
|
||||
title=playlist_section.get(f"Title{entry}"),
|
||||
url=item_url,
|
||||
)
|
||||
)
|
||||
return playlist
|
||||
|
||||
|
||||
async def parse_playlist(hass, url):
|
||||
"""Parse an m3u or pls playlist."""
|
||||
if url.endswith(".m3u") or url.endswith(".m3u8"):
|
||||
playlist = await parse_m3u(hass, url)
|
||||
else:
|
||||
playlist = await parse_pls(hass, url)
|
||||
|
||||
if not playlist:
|
||||
raise PlaylistError(f"Empty playlist {url}")
|
||||
|
||||
return playlist
|
||||
|
|
|
@ -64,7 +64,14 @@ from .const import (
|
|||
SIGNAL_HASS_CAST_SHOW_VIEW,
|
||||
)
|
||||
from .discovery import setup_internal_discovery
|
||||
from .helpers import CastStatusListener, ChromecastInfo, ChromeCastZeroconf
|
||||
from .helpers import (
|
||||
CastStatusListener,
|
||||
ChromecastInfo,
|
||||
ChromeCastZeroconf,
|
||||
PlaylistError,
|
||||
PlaylistSupported,
|
||||
parse_playlist,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -582,14 +589,13 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
|||
media_id = sourced_media.url
|
||||
|
||||
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")
|
||||
if metadata := extra.get("metadata"):
|
||||
app_data["metadata"] = metadata
|
||||
except json.JSONDecodeError:
|
||||
_LOGGER.error("Invalid JSON in media_content_id")
|
||||
raise
|
||||
|
@ -640,9 +646,51 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
|||
"hlsVideoSegmentFormat": "fmp4",
|
||||
},
|
||||
}
|
||||
elif (
|
||||
media_id.endswith(".m3u")
|
||||
or media_id.endswith(".m3u8")
|
||||
or media_id.endswith(".pls")
|
||||
):
|
||||
try:
|
||||
playlist = await parse_playlist(self.hass, media_id)
|
||||
_LOGGER.debug(
|
||||
"[%s %s] Playing item %s from playlist %s",
|
||||
self.entity_id,
|
||||
self._cast_info.friendly_name,
|
||||
playlist[0].url,
|
||||
media_id,
|
||||
)
|
||||
media_id = playlist[0].url
|
||||
if title := playlist[0].title:
|
||||
extra = {
|
||||
**extra,
|
||||
"metadata": {"title": title},
|
||||
}
|
||||
except PlaylistSupported as err:
|
||||
_LOGGER.debug(
|
||||
"[%s %s] Playlist %s is supported: %s",
|
||||
self.entity_id,
|
||||
self._cast_info.friendly_name,
|
||||
media_id,
|
||||
err,
|
||||
)
|
||||
except PlaylistError as err:
|
||||
_LOGGER.warning(
|
||||
"[%s %s] Failed to parse playlist %s: %s",
|
||||
self.entity_id,
|
||||
self._cast_info.friendly_name,
|
||||
media_id,
|
||||
err,
|
||||
)
|
||||
|
||||
# Default to play with the default media receiver
|
||||
app_data = {"media_id": media_id, "media_type": media_type, **extra}
|
||||
_LOGGER.debug(
|
||||
"[%s %s] Playing %s with default_media_receiver",
|
||||
self.entity_id,
|
||||
self._cast_info.friendly_name,
|
||||
app_data,
|
||||
)
|
||||
await self.hass.async_add_executor_job(
|
||||
quick_play, self._chromecast, "default_media_receiver", app_data
|
||||
)
|
||||
|
|
9
tests/components/cast/fixtures/164-hi-aac.pls
Normal file
9
tests/components/cast/fixtures/164-hi-aac.pls
Normal file
|
@ -0,0 +1,9 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=1
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
0
tests/components/cast/fixtures/164-hi-aac_invalid.pls
Normal file
0
tests/components/cast/fixtures/164-hi-aac_invalid.pls
Normal file
|
@ -0,0 +1,9 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=many
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
|
@ -0,0 +1,9 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=1
|
||||
|
||||
File1=http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
|
@ -0,0 +1,9 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=1
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=3
|
|
@ -0,0 +1,8 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=1
|
||||
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
7
tests/components/cast/fixtures/164-hi-aac_no_entries.pls
Normal file
7
tests/components/cast/fixtures/164-hi-aac_no_entries.pls
Normal file
|
@ -0,0 +1,7 @@
|
|||
[playlist]
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
|
@ -0,0 +1,7 @@
|
|||
NumberOfEntries=1
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
||||
|
||||
Version=2
|
7
tests/components/cast/fixtures/164-hi-aac_no_version.pls
Normal file
7
tests/components/cast/fixtures/164-hi-aac_no_version.pls
Normal file
|
@ -0,0 +1,7 @@
|
|||
[playlist]
|
||||
|
||||
NumberOfEntries=1
|
||||
|
||||
File1=https://http-live.sr.se/p3-aac-192
|
||||
Title1=Sveriges Radio
|
||||
Length1=-1
|
4
tests/components/cast/fixtures/209-hi-mp3.m3u
Normal file
4
tests/components/cast/fixtures/209-hi-mp3.m3u
Normal file
|
@ -0,0 +1,4 @@
|
|||
#EXTM3U
|
||||
|
||||
#EXTINF:-1,Sveriges Radio
|
||||
https://http-live.sr.se/p4norrbotten-mp3-192
|
4
tests/components/cast/fixtures/209-hi-mp3_bad_extinf.m3u
Normal file
4
tests/components/cast/fixtures/209-hi-mp3_bad_extinf.m3u
Normal file
|
@ -0,0 +1,4 @@
|
|||
#EXTM3U
|
||||
|
||||
#EXTINF:invalid
|
||||
https://http-live.sr.se/p4norrbotten-mp3-192
|
4
tests/components/cast/fixtures/209-hi-mp3_bad_url.m3u
Normal file
4
tests/components/cast/fixtures/209-hi-mp3_bad_url.m3u
Normal file
|
@ -0,0 +1,4 @@
|
|||
#EXTM3U
|
||||
|
||||
#EXTINF:-1,Sveriges Radio
|
||||
p4norrbotten-mp3-192
|
3
tests/components/cast/fixtures/209-hi-mp3_no_extinf.m3u
Normal file
3
tests/components/cast/fixtures/209-hi-mp3_no_extinf.m3u
Normal file
|
@ -0,0 +1,3 @@
|
|||
#EXTM3U
|
||||
|
||||
https://http-live.sr.se/p4norrbotten-mp3-192
|
4
tests/components/cast/fixtures/bbc_radio_fourfm.m3u8
Normal file
4
tests/components/cast/fixtures/bbc_radio_fourfm.m3u8
Normal file
|
@ -0,0 +1,4 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:3
|
||||
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=101760,CODECS="mp4a.40.5"
|
||||
http://as-hls-ww-live.akamaized.net/pool_904/live/ww/bbc_radio_fourfm/bbc_radio_fourfm.isml/bbc_radio_fourfm-audio%3d96000.norewind.m3u8
|
1
tests/components/cast/fixtures/empty.m3u
Normal file
1
tests/components/cast/fixtures/empty.m3u
Normal file
|
@ -0,0 +1 @@
|
|||
|
114
tests/components/cast/test_helpers.py
Normal file
114
tests/components/cast/test_helpers.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
"""Tests for the Cast integration helpers."""
|
||||
import asyncio
|
||||
|
||||
from aiohttp import client_exceptions
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.cast.helpers import (
|
||||
PlaylistError,
|
||||
PlaylistItem,
|
||||
PlaylistSupported,
|
||||
parse_playlist,
|
||||
)
|
||||
|
||||
from tests.common import load_fixture
|
||||
|
||||
|
||||
async def test_hls_playlist_supported(hass, aioclient_mock):
|
||||
"""Test playlist parsing of HLS playlist."""
|
||||
url = "http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_low/ak/bbc_radio_fourfm.m3u8"
|
||||
aioclient_mock.get(url, text=load_fixture("bbc_radio_fourfm.m3u8", "cast"))
|
||||
with pytest.raises(PlaylistSupported):
|
||||
await parse_playlist(hass, url)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,fixture,expected_playlist",
|
||||
(
|
||||
(
|
||||
"https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u",
|
||||
"209-hi-mp3.m3u",
|
||||
[
|
||||
PlaylistItem(
|
||||
length=["-1"],
|
||||
title="Sveriges Radio",
|
||||
url="https://http-live.sr.se/p4norrbotten-mp3-192",
|
||||
)
|
||||
],
|
||||
),
|
||||
(
|
||||
"https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u",
|
||||
"209-hi-mp3_bad_extinf.m3u",
|
||||
[
|
||||
PlaylistItem(
|
||||
length=None,
|
||||
title=None,
|
||||
url="https://http-live.sr.se/p4norrbotten-mp3-192",
|
||||
)
|
||||
],
|
||||
),
|
||||
(
|
||||
"https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u",
|
||||
"209-hi-mp3_no_extinf.m3u",
|
||||
[
|
||||
PlaylistItem(
|
||||
length=None,
|
||||
title=None,
|
||||
url="https://http-live.sr.se/p4norrbotten-mp3-192",
|
||||
)
|
||||
],
|
||||
),
|
||||
(
|
||||
"http://sverigesradio.se/topsy/direkt/164-hi-aac.pls",
|
||||
"164-hi-aac.pls",
|
||||
[
|
||||
PlaylistItem(
|
||||
length="-1",
|
||||
title="Sveriges Radio",
|
||||
url="https://http-live.sr.se/p3-aac-192",
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_parse_playlist(hass, aioclient_mock, url, fixture, expected_playlist):
|
||||
"""Test playlist parsing of HLS playlist."""
|
||||
aioclient_mock.get(url, text=load_fixture(fixture, "cast"))
|
||||
playlist = await parse_playlist(hass, url)
|
||||
assert expected_playlist == playlist
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,fixture",
|
||||
(
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_entries.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_file.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid_version.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_invalid.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_missing_file.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_no_entries.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_no_playlist.pls"),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", "164-hi-aac_no_version.pls"),
|
||||
("https://sverigesradio.se/209-hi-mp3.m3u", "209-hi-mp3_bad_url.m3u"),
|
||||
("https://sverigesradio.se/209-hi-mp3.m3u", "empty.m3u"),
|
||||
),
|
||||
)
|
||||
async def test_parse_bad_playlist(hass, aioclient_mock, url, fixture):
|
||||
"""Test playlist parsing of HLS playlist."""
|
||||
aioclient_mock.get(url, text=load_fixture(fixture, "cast"))
|
||||
with pytest.raises(PlaylistError):
|
||||
await parse_playlist(hass, url)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,exc",
|
||||
(
|
||||
("http://sverigesradio.se/164-hi-aac.pls", asyncio.TimeoutError),
|
||||
("http://sverigesradio.se/164-hi-aac.pls", client_exceptions.ClientError),
|
||||
),
|
||||
)
|
||||
async def test_parse_http_error(hass, aioclient_mock, url, exc):
|
||||
"""Test playlist parsing of HLS playlist when aioclient raises."""
|
||||
aioclient_mock.get(url, text="", exc=exc)
|
||||
with pytest.raises(PlaylistError):
|
||||
await parse_playlist(hass, url)
|
|
@ -44,7 +44,12 @@ from homeassistant.helpers import device_registry as dr, entity_registry as er,
|
|||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry, assert_setup_component, mock_platform
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
assert_setup_component,
|
||||
load_fixture,
|
||||
mock_platform,
|
||||
)
|
||||
from tests.components.media_player import common
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
@ -1108,6 +1113,80 @@ async def test_entity_play_media_sign_URL(hass: HomeAssistant, quick_play_mock):
|
|||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url,fixture,playlist_item",
|
||||
(
|
||||
# Test title is extracted from m3u playlist
|
||||
(
|
||||
"https://sverigesradio.se/topsy/direkt/209-hi-mp3.m3u",
|
||||
"209-hi-mp3.m3u",
|
||||
{
|
||||
"media_id": "https://http-live.sr.se/p4norrbotten-mp3-192",
|
||||
"media_type": "audio",
|
||||
"metadata": {"title": "Sveriges Radio"},
|
||||
},
|
||||
),
|
||||
# Test title is extracted from pls playlist
|
||||
(
|
||||
"http://sverigesradio.se/topsy/direkt/164-hi-aac.pls",
|
||||
"164-hi-aac.pls",
|
||||
{
|
||||
"media_id": "https://http-live.sr.se/p3-aac-192",
|
||||
"media_type": "audio",
|
||||
"metadata": {"title": "Sveriges Radio"},
|
||||
},
|
||||
),
|
||||
# Test HLS playlist is forwarded to the device
|
||||
(
|
||||
"http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_low/ak/bbc_radio_fourfm.m3u8",
|
||||
"bbc_radio_fourfm.m3u8",
|
||||
{
|
||||
"media_id": "http://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_low/ak/bbc_radio_fourfm.m3u8",
|
||||
"media_type": "audio",
|
||||
},
|
||||
),
|
||||
# Test bad playlist is forwarded to the device
|
||||
(
|
||||
"https://sverigesradio.se/209-hi-mp3.m3u",
|
||||
"209-hi-mp3_bad_url.m3u",
|
||||
{
|
||||
"media_id": "https://sverigesradio.se/209-hi-mp3.m3u",
|
||||
"media_type": "audio",
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_entity_play_media_playlist(
|
||||
hass: HomeAssistant, aioclient_mock, quick_play_mock, url, fixture, playlist_item
|
||||
):
|
||||
"""Test playing media."""
|
||||
entity_id = "media_player.speaker"
|
||||
aioclient_mock.get(url, text=load_fixture(fixture, "cast"))
|
||||
|
||||
await async_process_ha_core_config(
|
||||
hass,
|
||||
{"internal_url": "http://example.com:8123"},
|
||||
)
|
||||
|
||||
info = get_fake_chromecast_info()
|
||||
|
||||
chromecast, _ = await async_setup_media_player_cast(hass, info)
|
||||
_, conn_status_cb, _ = get_status_callbacks(chromecast)
|
||||
|
||||
connection_status = MagicMock()
|
||||
connection_status.status = "CONNECTED"
|
||||
conn_status_cb(connection_status)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Play_media
|
||||
await common.async_play_media(hass, "audio", url, entity_id)
|
||||
quick_play_mock.assert_called_once_with(
|
||||
chromecast,
|
||||
"default_media_receiver",
|
||||
playlist_item,
|
||||
)
|
||||
|
||||
|
||||
async def test_entity_media_content_type(hass: HomeAssistant):
|
||||
"""Test various content types."""
|
||||
entity_id = "media_player.speaker"
|
||||
|
|
|
@ -175,6 +175,7 @@ class AiohttpClientMockResponse:
|
|||
if response is None:
|
||||
response = b""
|
||||
|
||||
self.charset = "utf-8"
|
||||
self.method = method
|
||||
self._url = url
|
||||
self.status = status
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue