Skip to main content

Camera

Show still images or live streams (HLS, MJPEG, or WebRTC) from your cameras.

Quick Example​

import { Camera, useCamera } from 'hass-react'

// Compound components handle the image and stream player for you
function CameraView() {
const camera = useCamera('camera.front_door')

return (
<div>
{camera.streamState.isActive ? (
<Camera.StreamPlayer stream={camera.streamState} />
) : (
<Camera.Image url={camera.imageUrl} />
)}

<button onClick={() => camera.startStream({ type: 'mjpeg' })}>
Start Stream
</button>
{camera.streamState.isActive && (
<button onClick={camera.stopStream}>Stop Stream</button>
)}
</div>
)
}

Compound Components​

The Camera module ships two small components for the common cases:

Camera.Image​

Displays a static camera image and handles load errors for you.

import { Camera } from 'hass-react'

<Camera.Image
url={camera.imageUrl}
alt="Front door camera"
className="camera-image"
onError={() => console.log('Image failed to load')}
/>

Props:

  • url (string | null) - Image URL
  • alt (string) - Alt text (default: "Camera image")
  • style (CSSProperties) - Custom styles
  • className (string) - CSS class name
  • onError (() => void) - Error callback

Camera.StreamPlayer​

Display a live camera stream (HLS, MJPEG, or WebRTC).

import { Camera, useCamera } from 'hass-react'

function LiveStream() {
const camera = useCamera('camera.backyard')

return (
<Camera.StreamPlayer
stream={camera.streamState}
autoPlay={true}
muted={true}
controls={true}
className="camera-stream"
/>
)
}

Props:

  • stream (StreamState) - Stream state object from useCamera
  • autoPlay (boolean) - Auto-play video (default: true)
  • muted (boolean) - Mute audio (default: true)
  • controls (boolean) - Show video controls (default: true)
  • style (CSSProperties) - Custom styles
  • className (string) - CSS class name

Hook API​

Basic Usage​

import { useCamera } from 'hass-react'

function MyComponent() {
const camera = useCamera('camera.front_door')

return <div>{camera.isOn ? 'ON' : 'OFF'}</div>
}

The useCamera hook returns an object with the following properties and methods:

State Properties​

  • isOn (boolean) - Whether the camera is currently on
  • isRecording (boolean) - Whether the camera is recording
  • isStreaming (boolean) - Whether the camera is streaming
  • isIdle (boolean) - Whether the camera is idle
  • motionDetectionEnabled (boolean) - Motion detection status

Image and Stream Properties​

  • imageUrl (string | null) - URL for static camera image with authentication token
  • streamState (StreamState) - Current stream state with properties:
    • isLoading (boolean) - Stream is being loaded
    • isActive (boolean) - Stream is active
    • error (Error | null) - Stream error if any
    • url (string | null) - Stream URL
    • type ('hls' | 'mjpeg' | 'webrtc' | null) - Stream type
  • accessToken (string) - Authentication token for camera API

Camera Information​

  • brand (string) - Camera brand
  • model (string) - Camera model

Feature Support​

  • supportsOnOff (boolean) - Camera supports turning on/off
  • supportsStream (boolean) - Camera supports streaming

Control Methods​

  • turnOn() - Turn camera on (if supported)
  • turnOff() - Turn camera off (if supported)
  • enableMotionDetection() - Enable motion detection
  • disableMotionDetection() - Disable motion detection
  • snapshot(filename: string) - Save a snapshot to a Home Assistant-accessible filename
  • record(filename: string, duration?: number) - Record to a Home Assistant-accessible filename
  • refreshImage() - Force refresh the camera image

Streaming Methods​

  • getStreamUrl(options?: StreamOptions) - Get stream URL without starting playback
    • options.type: 'hls' | 'mjpeg' | 'webrtc' (default: 'hls')
  • startStream(options?: StreamOptions) - Start streaming
    • options.type: 'hls' | 'mjpeg' | 'webrtc' (default: 'hls')
  • stopStream() - Stop streaming
  • retryStream() - Retry last failed stream
  • playStream(mediaPlayer: string) - Play the stream on a media player entity

startStream reports failures through streamState.error and resolves without throwing. Service methods return promises, so handle service-call failures explicitly:

camera.snapshot('/config/www/snapshots/front-door.jpg').catch(console.error)
camera.record('/config/www/recordings/front-door.mp4', 30).catch(console.error)
camera.playStream('media_player.living_room').catch(console.error)

Entity Properties​

  • entityId (string) - The entity ID
  • state (string) - Raw state value from Home Assistant
  • attributes (object) - All entity attributes
  • lastChanged (Date) - When the entity last changed
  • lastUpdated (Date) - When the entity was last updated

Component API​

Basic Usage​

import { Camera } from 'hass-react'

<Camera entityId="camera.front_door">
{(cameraProps) => (
// Your UI here
)}
</Camera>

The Camera component provides all the same properties and methods as the useCamera hook to your render function.

List All Cameras​

The useCameras hook returns every camera entity Home Assistant knows about:

import { useCameras } from 'hass-react'

function CameraList() {
const cameras = useCameras()

return (
<div>
<h2>Available Cameras ({cameras.length})</h2>
{cameras.map(camera => (
<div key={camera.entity_id}>
{camera.attributes.friendly_name || camera.entity_id}
</div>
))}
</div>
)
}

Each item in the array is a raw entity object with entity_id, state, and attributes.

Examples​

Simple Image Display​

import { Camera, useCamera } from 'hass-react'

function CameraImage() {
const camera = useCamera('camera.front_door')

return (
<div>
<h3>Front Door</h3>
<Camera.Image url={camera.imageUrl} />
<button onClick={camera.refreshImage}>Refresh</button>
</div>
)
}

Stream Toggle​

import { Camera, useCamera } from 'hass-react'

function CameraStream() {
const camera = useCamera('camera.backyard')

return (
<div>
<h3>Backyard Camera</h3>

{camera.streamState.isActive ? (
<>
<Camera.StreamPlayer stream={camera.streamState} />
<button onClick={camera.stopStream}>Stop Stream</button>
</>
) : (
<>
<Camera.Image url={camera.imageUrl} />
<button
onClick={() => camera.startStream({ type: 'mjpeg' })}
disabled={camera.streamState.isLoading}
>
{camera.streamState.isLoading ? 'Loading...' : 'Start Stream'}
</button>
</>
)}

{camera.streamState.error && (
<div style={{ color: 'red' }}>
Error: {camera.streamState.error.message}
<button onClick={camera.retryStream}>Retry</button>
</div>
)}
</div>
)
}

Stream Type Selection​

import { Camera, useCamera } from 'hass-react'

function CameraWithStreamOptions() {
const camera = useCamera('camera.garage')

const handleStreamStart = (type: 'hls' | 'mjpeg') => {
camera.startStream({ type })
}

return (
<div>
<h3>Garage Camera</h3>

{camera.streamState.isActive ? (
<>
<Camera.StreamPlayer stream={camera.streamState} />
<p>Streaming ({camera.streamState.type})</p>
<button onClick={camera.stopStream}>Stop</button>
</>
) : (
<>
<Camera.Image url={camera.imageUrl} />
<div>
<button onClick={() => handleStreamStart('mjpeg')}>
Start MJPEG Stream
</button>
<button onClick={() => handleStreamStart('hls')}>
Start HLS Stream
</button>
</div>
</>
)}
</div>
)
}

Motion Detection Control​

import { useCamera } from 'hass-react'

function MotionDetectionControl() {
const camera = useCamera('camera.driveway')

const toggleMotionDetection = () => {
if (camera.motionDetectionEnabled) {
camera.disableMotionDetection()
} else {
camera.enableMotionDetection()
}
}

return (
<div>
<h3>{camera.attributes.friendly_name}</h3>

<label>
<input
type="checkbox"
checked={camera.motionDetectionEnabled}
onChange={toggleMotionDetection}
/>
Motion Detection
</label>

{camera.motionDetectionEnabled && (
<span style={{ color: 'green' }}>● Recording on motion</span>
)}
</div>
)
}

Recording Control​

import { useCamera } from 'hass-react'

function CameraRecorder() {
const camera = useCamera('camera.security')

const startRecording = () => {
camera
.record('/config/www/recordings/security.mp4', 30)
.catch((error) => console.error('Recording failed:', error))
}

const takeSnapshot = () => {
camera
.snapshot('/config/www/snapshots/security.jpg')
.catch((error) => console.error('Snapshot failed:', error))
}

return (
<div>
<h3>{camera.attributes.friendly_name}</h3>

<div>
Status: {camera.isRecording ? 'Recording' : 'Idle'}
</div>

{!camera.isRecording && (
<button onClick={startRecording}>
Record 30 seconds
</button>
)}

<button onClick={takeSnapshot}>
Take Snapshot
</button>
</div>
)
}

Full Camera Control Panel​

import { Camera, useCamera } from 'hass-react'

function CameraPanel() {
const camera = useCamera('camera.main')

return (
<div>
<h2>{camera.attributes.friendly_name}</h2>

{/* Camera Info */}
<div>
{camera.brand && <p>Brand: {camera.brand}</p>}
{camera.model && <p>Model: {camera.model}</p>}
<p>Status: {camera.state}</p>
</div>

{/* Power Control */}
{camera.supportsOnOff && (
<div>
<button onClick={camera.isOn ? camera.turnOff : camera.turnOn}>
{camera.isOn ? 'Turn Off' : 'Turn On'}
</button>
</div>
)}

{/* Image/Stream Display */}
{camera.isOn && (
<div>
{camera.streamState.isActive ? (
<Camera.StreamPlayer stream={camera.streamState} />
) : (
<Camera.Image url={camera.imageUrl} />
)}
</div>
)}

{/* Stream Controls */}
{camera.isOn && camera.supportsStream && (
<div>
{camera.streamState.isActive ? (
<button onClick={camera.stopStream}>Stop Stream</button>
) : (
<>
<button onClick={() => camera.startStream({ type: 'mjpeg' })}>
MJPEG Stream
</button>
<button onClick={() => camera.startStream({ type: 'hls' })}>
HLS Stream
</button>
</>
)}
</div>
)}

{/* Stream Error */}
{camera.streamState.error && (
<div style={{ color: 'red' }}>
Stream Error: {camera.streamState.error.message}
<button onClick={camera.retryStream}>Retry</button>
</div>
)}

{/* Motion Detection */}
<div>
<label>
<input
type="checkbox"
checked={camera.motionDetectionEnabled}
onChange={() => camera.motionDetectionEnabled
? camera.disableMotionDetection()
: camera.enableMotionDetection()
}
/>
Motion Detection
</label>
</div>

{/* Action Buttons */}
<div>
<button onClick={camera.refreshImage}>Refresh Image</button>
<button onClick={() => camera.snapshot('/config/www/snapshots/main.jpg').catch(console.error)}>Take Snapshot</button>
<button onClick={() => camera.record('/config/www/recordings/main.mp4', 60).catch(console.error)}>
Record 60s
</button>
</div>

<p>Last updated: {camera.lastUpdated.toLocaleTimeString()}</p>
</div>
)
}

Advanced: Custom Stream Player​

If you'd rather manage the video element yourself, get the URL with getStreamUrl and wire it up manually:

import { useCamera } from 'hass-react'
import { useEffect, useRef } from 'react'

function CustomStreamPlayer() {
const camera = useCamera('camera.custom')
const videoRef = useRef<HTMLVideoElement>(null)

// Get stream URL and manage playback manually
useEffect(() => {
const setupStream = async () => {
try {
const streamUrl = await camera.getStreamUrl({ type: 'hls' })
if (videoRef.current && streamUrl) {
videoRef.current.src = streamUrl
await videoRef.current.play()
}
} catch (error) {
console.error('Stream setup failed:', error)
}
}

setupStream()

return () => {
if (videoRef.current) {
videoRef.current.pause()
videoRef.current.src = ''
}
}
}, [camera])

return (
<video
ref={videoRef}
controls
autoPlay
muted
style={{ width: '100%' }}
/>
)
}

Using Component API​

import { Camera } from 'hass-react'

<Camera entityId="camera.porch">
{({ isOn, imageUrl, streamState, startStream, stopStream, refreshImage, attributes }) => (
<div>
<h3>{attributes.friendly_name}</h3>

{isOn && (
<>
{streamState.isActive ? (
<>
<Camera.StreamPlayer stream={streamState} />
<button onClick={stopStream}>Stop</button>
</>
) : (
<>
<Camera.Image url={imageUrl} />
<button onClick={() => startStream({ type: 'mjpeg' })}>
Start Stream
</button>
<button onClick={refreshImage}>Refresh</button>
</>
)}
</>
)}
</div>
)}
</Camera>

Stream Types​

MJPEG​

  • Best compatibility - Works with most cameras
  • Lower latency - Faster than HLS
  • Higher bandwidth - Less efficient compression
  • Recommended for: Local network viewing, maximum compatibility

HLS (HTTP Live Streaming)​

  • Better compression - Lower bandwidth usage
  • Higher latency - More delay than MJPEG
  • Requires Stream integration - Must be configured in Home Assistant
  • Recommended for: Remote access, bandwidth-constrained networks

WebRTC​

  • Lowest latency - Real-time streaming
  • Complex setup - Requires specialized configuration
  • Not yet fully supported - Consider MJPEG or HLS for now

Troubleshooting​

Stream Not Working​

If HLS streaming fails, try MJPEG instead:

// Try MJPEG first for better compatibility
camera.startStream({ type: 'mjpeg' })

HLS requires the Stream integration to be configured in Home Assistant. If you see errors about stream integration, use MJPEG instead.

Image Not Loading​

Make sure the camera entity has a valid access_token and is connected:

if (!camera.imageUrl) {
return <div>Camera not available</div>
}

Force Image Refresh​

If images appear stale, use the refresh method:

<button onClick={camera.refreshImage}>Refresh</button>

The imageUrl already includes a cache-busting parameter, so each refresh fetches a new image.