Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for axis network speaker #130163

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion homeassistant/components/axis/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@
DEFAULT_TRIGGER_TIME = 0
DEFAULT_VIDEO_SOURCE = "No video source"

PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.LIGHT, Platform.SWITCH]
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CAMERA,
Platform.LIGHT,
Platform.MEDIA_PLAYER,
Platform.SWITCH,
]
2 changes: 1 addition & 1 deletion homeassistant/components/axis/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "axis",
"name": "Axis",
"after_dependencies": ["mqtt"],
"after_dependencies": ["mqtt", "ffmpeg"],
"codeowners": ["@Kane610"],
"config_flow": true,
"dhcp": [
Expand Down
146 changes: 146 additions & 0 deletions homeassistant/components/axis/media_player.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"""Support for Axis network speakers."""

import asyncio
import logging
from typing import Any

import httpx

from homeassistant.components import ffmpeg, media_source
from homeassistant.components.media_player import (
BrowseMedia,
MediaPlayerDeviceClass,
MediaPlayerEntity,
MediaPlayerEntityDescription,
MediaPlayerEntityFeature,
MediaPlayerState,
MediaType,
async_process_play_media_url,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.httpx_client import get_async_client

from . import AxisConfigEntry
from .entity import AxisEntity
from .hub import AxisHub

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: AxisConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add media player for an Axis Network Speaker."""
hub = config_entry.runtime_data
async_add_entities([AxisSpeaker(hub)])


class AxisSpeaker(AxisEntity, MediaPlayerEntity):
"""Axis Network Speaker."""

_attr_has_entity_name = True
_attr_name = None
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
_attr_media_content_type = MediaType.MUSIC
_attr_supported_features = (
MediaPlayerEntityFeature.PLAY_MEDIA
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.STOP
| MediaPlayerEntityFeature.BROWSE_MEDIA
)
entity_description: MediaPlayerEntityDescription

def __init__(self, hub: AxisHub) -> None:
"""Initialize the entity."""
super().__init__(hub)
self._attr_unique_id = f"{hub.unique_id}_mediaplayer"
self._attr_state = MediaPlayerState.IDLE

async def async_browse_media(
self,
media_content_type: MediaType | 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/"),
)

async def async_play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None:
"""Play a piece of media."""
if media_source.is_media_source_id(media_id):
media_type = MediaType.MUSIC
play_item = await media_source.async_resolve_media(
self.hass, media_id, self.entity_id
)
media_id = async_process_play_media_url(self.hass, play_item.url)

if media_type != MediaType.MUSIC:
raise HomeAssistantError("Only music media type is supported")

_LOGGER.debug("playing media %s for axis speaker", media_id)

ffmpeg_manager = ffmpeg.get_ffmpeg_manager(self.hass)
output = await ffmpeg_cmd(ffmpeg_manager.binary, media_id)

uri = "/axis-cgi/audio/transmit.cgi"
conf = self.hub.config
url = f"{conf.protocol}://{conf.host}:{conf.port}{uri}"
auth = httpx.DigestAuth(self.hub.config.username, self.hub.config.password)
headers = {"Content-Type": "audio/axis-mulaw-128"}

client = get_async_client(self.hass, verify_ssl=False)
await client.post(url, headers=headers, content=output, auth=auth)
jonoberheide marked this conversation as resolved.
Show resolved Hide resolved

async def async_media_stop(self) -> None:
"""Send stop command."""

async def async_set_volume_level(self, volume: float) -> None:
"""Set volume level, range 0..1."""


async def ffmpeg_cmd(ffmpeg_path: str, media_id: str) -> bytes:
"""Convert media to Axis-compatible format."""
args = [
"-hide_banner",
"-loglevel",
"error",
"-i",
media_id,
"-vn",
"-probesize",
"32",
"-analyzeduration",
"32",
"-c:a",
"pcm_mulaw",
"-ab",
"128k",
"-ac",
"1",
"-ar",
"16000",
"-f",
"wav",
"-",
]

_LOGGER.debug("ffmpeg_cmd: %s %s", ffmpeg_path, " ".join(args))

process = await asyncio.create_subprocess_exec(
ffmpeg_path,
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
return stdout
Loading