# Videos

{% hint style="info" %}
Videos are available on Homey Pro (2023 - 2026) and Homey Pro mini since v12.7.0, and Homey Cloud.
{% endhint %}

Your app's devices can let Homey know that they support a video stream. Once a front-end (e.g. the Homey app for iOS & Android) requests to start watching, your app will receive a request to share the stream's details, such as URL and authentication. The app, nor Homey, does any transcoding, but acts as a broker between the camera and the front-end.

## Supported Video Types

Homey supports video streams with WebRTC, RTSP, RTMP, HLS, DASH. Other types *may* be supported. Because the Homey Mobile App embeds a VLC media player, you can always try to see if your video type works.

{% hint style="info" %}
Homey automatically serves all videos as WebRTC to the frontend as of Homey Pro (2023 - 2026), Homey Pro mini and Homey Self-Hosted Server version 12.12.0. This makes it possible to show the videos on the Homey Web App, as well as make the videos available outside of your local network. Developers can opt out of this by passing the `disableWebRTCProxy: true` option while creating the video (see [Apps SDK - ManagerVideos](https://apps-sdk-v3.developer.homey.app/ManagerVideos.html))
{% endhint %}

## Getting Started with Videos

{% tabs %}
{% tab title="JavaScript" %}
To register a camera stream, your app needs to ask [ManagerVideos](https://apps-sdk-v3.developer.homey.app/ManagerVideos.html) first to create a video. Then, attach the video to your Device by calling `Device.setCameraVideo`. Note that when a device has both an image and video with the same `id` then the image will be used as a background image for the video while it is loading.
{% endtab %}

{% tab title="TypeScript" %}
To register a camera stream, your app needs to ask [ManagerVideos](https://apps-sdk-v3.developer.homey.app/ManagerVideos.html) first to create a video. Then, attach the video to your Device by calling `Device.setCameraVideo`. Note that when a device has both an image and video with the same `id` then the image will be used as a background image for the video while it is loading.
{% endtab %}

{% tab title="Python" %}
To register a camera stream, your app needs to ask [ManagerVideos](https://python-apps-sdk-v3.developer.homey.app/manager/videos.html#homey.manager.videos.ManagerVideos) first to create a video. Then, attach the video to your Device by calling `Device.set_camera_video`. Note that when a device has both an image and video with the same `id` then the image will be used as a background image for the video while it is loading.
{% endtab %}
{% endtabs %}

### Example — WebRTC

This example shows a basic WebRTC camera stream.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-webrtc-camera/device.mjs" %}

```javascript
import Homey from 'homey';

export default class MyWebRTCDevice extends Homey.Device {

    /*
     * WebRTC works by creating an offer SDP in the frontend, exchanging it for 
     * an answer SDP through the cameras API, and using that answer SDP in the
     * frontend to set up the connection.
     */
    async onInit() {
        try {
            const video = await this.homey.videos.createVideoWebRTC();

            /*
             * This listener is called when the user opens the camera stream in the
             * mobile app. The argument is an SDP offer generated by the mobile appp.
             */
            video.registerOfferListener(async (offerSdp) => {
                // Normally, you would call an API to exchange an SDP offer for an SDP answer
                const result = await this.oAuth2Client.createStream(offerSdp);
                return {
                    answerSdp: result.answerSdp,
                };
            });

            /*
             * Attach the camera to the device.
             */
            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }   
    }
    
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-webrtc-camera/device.mts" %}

```mts
import Homey from "homey";

type WebRTCAnswer = {
  answerSdp: string;
  // Only needed if a keep-alive listener is used
  streamId?: string;
};

export default class Device extends Homey.Device {
  /*
   * WebRTC works by creating an offer SDP in the frontend, exchanging it for
   * an answer SDP through the cameras API, and using that answer SDP in the
   * frontend to set up the connection.
   */
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoWebRTC();

      /*
       * This listener is called when the user opens the camera stream in the
       * mobile app. The argument is an SDP offer generated by the mobile appp.
       */
      video.registerOfferListener(async (offerSdp: string): Promise<WebRTCAnswer> => {
        // Normally, you would call an API to exchange an SDP offer for an SDP answer
        const result = await this.oAuth2Client.createStream(offerSdp);
        return {
          answerSdp: result.answerSdp,
        };
      });

      /*
       * Attach the camera to the device.
       */
      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-webrtc-camera/device.py" %}

```python
from homey import device
from homey.video_web_rtc import WebRTCAnswer

from .oauth2_client import OAuth2Client


class Device(device.Device):
    oauth2_client: OAuth2Client

    # WebRTC works by creating an offer SDP in the frontend, exchanging it for
    # an answer SDP through the cameras API, and using that answer SDP in the
    # frontend to set up the connection.
    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_web_rtc()

            # This listener is called when the user opens the camera stream in the
            # mobile app. The argument is an SDP offer generated by the mobile app.
            async def offer_listener(offer: str) -> WebRTCAnswer:
                # Normally, you would call an API to exchange an SDP offer for an SDP answer
                result = await self.oauth2_client.create_stream(offer)
                return {
                    "answerSdp": result.get("answerSdp"),
                }

            video.register_offer_listener(offer_listener)

            # Attach the camera to the device.
            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

### Example — WebRTC without Data Channel

This example shows a WebRTC camera without a data channel, and a keepalive listener.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-webrtc-camera/device.mjs" %}

```javascript
import Homey from 'homey';

export default class MyWebRTCDevice extends Homey.Device {

    /*
     * Some cameras require a data channel in order to work, while other cameras
     * only work when the offer does not contain a data channel. This can be
     * customized through the options object in the createCamera method.
     *
     * There are also cameras that only keep their stream open for a few minutes.
     * These can often be extended through an API call. The keep alive listener
     * can be used to send such a request.
     */
    async onInit() {
        try {
            const video = await this.homey.videos.createVideoWebRTC({
                dataChannel: false, // default: true
            });

            /*
             * The offer listener now also returns a stream ID next to the anwer
             * SDP. This stream ID can be used to identify the stream in the
             * keep alive listener.
             */
            video.registerOfferListener(async (offerSdp) => {
                // Normally, you would call an API to exchange an SDP offer for an SDP answer
                const result = await this.oAuth2Client.createStream(offerSdp);
                return {
                    answerSdp: result.answerSdp,
                    streamId: result.streamId,
                };
            });

            /*
             * The keep alive callback has a streamId argument that can be used
             * to identify the stream. Most APIs require such an identifier in
             * the request to extend the stream.
             */
            video.registerKeepAliveListener(async (streamId) => {
                // Normally, you would call an API to keep the stream alive
                await this.oAuth2Client.extendStream(streamId);
            });

            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }   
    }
    
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-webrtc-camera/device.mts" %}

```mts
import Homey from "homey";

type WebRTCAnswer = {
  answerSdp: string;
  // Only needed if a keep-alive listener is used
  streamId?: string;
};

export default class Device extends Homey.Device {
  /*
   * Some cameras require a data channel in order to work, while other cameras
   * only work when the offer does not contain a data channel. This can be
   * customized through the options object in the createCamera method.
   *
   * There are also cameras that only keep their stream open for a few minutes.
   * These can often be extended through an API call. The keep alive listener
   * can be used to send such a request.
   */
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoWebRTC({
        dataChannel: false, // default: true
      });

      /*
       * The offer listener now also returns a stream ID next to the anwer
       * SDP. This stream ID can be used to identify the stream in the
       * keep alive listener.
       */
      video.registerOfferListener(async (offerSdp: string): Promise<WebRTCAnswer> => {
        // Normally, you would call an API to exchange an SDP offer for an SDP answer
        const result = await this.oAuth2Client.createStream(offerSdp);
        return {
          answerSdp: result.answerSdp,
          streamId: result.streamId,
        };
      });

      /*
       * The keep alive callback has a streamId argument that can be used
       * to identify the stream. Most APIs require such an identifier in
       * the request to extend the stream.
       */
      video.registerKeepAliveListener(async (streamId: string) => {
        // Normally, you would call an API to keep the stream alive
        await this.oAuth2Client.extendStream(streamId);
      });

      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-webrtc-camera/device.py" %}

```python
from homey import device
from homey.video_web_rtc import WebRTCAnswer

from .oauth2_client import OAuth2Client


class Device(device.Device):
    oauth2_client: OAuth2Client

    """
    Some cameras require a data channel in order to work, while other cameras
    only work when the offer does not contain a data channel. This can be
    customized through the options object in the createCamera method.
    
    There are also cameras that only keep their stream open for a few minutes.
    These can often be extended through an API call. The keep alive listener
    can be used to send such a request.
    """

    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_web_rtc()

            # The offer listener now also returns a stream ID
            # so it can be used to identify the stream in the keep alive listener.
            async def offer_listener(offer: str) -> WebRTCAnswer:
                # Normally, you would call an API to exchange an SDP offer for an SDP answer
                result = await self.oauth2_client.create_stream(offer)
                return {
                    "answerSdp": result.get("answerSdp"),
                    "streamId": result.get("streamId"),
                }

            video.register_offer_listener(offer_listener)

            # The keep alive callback has a stream_id argument that can be used
            # to identify the stream. Most APIs require such an identifier in
            # the request to extend the stream.
            async def keep_alive_listener(stream_id: str) -> None:
                await self.oauth2_client.extend_stream(stream_id)

            video.register_keep_alive_listener(keep_alive_listener)

            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

### Example — RTSP

This example shows an RTSP camera, which is as simple as providing an URL. In this example, we use HTTP Basic Authentication (`username:password@...`) in the URL.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-rtsp-camera/device.mjs" %}

```mjs
import Homey from 'homey';

export default class MyRTSPDevice extends Homey.Device {

    /*
     * To play an RTSP stream, you simply need to return the URL to the stream
     * from the video url listener. Some streams require authentication in
     * different formats. This example uses query parameters for authentication.
     */
    async onInit() {        
        try {
            const video = await this.homey.videos.createVideoRTSP({
                allowInvalidCertificates: true,
                demuxer: 'h265',
            });

            /*
             * The video url listener takes no arguments. It simply builds the
             * URL to the RTSP stream using the username and password.
             */
            video.registerVideoUrlListener(async () => {
                // Get the username and password that were set during pairing
                const {
                    username,
                    password,
                 } = this.getSettings();
         
                // Normally, you would get the device's IP from Discovery, or another method
                return {
                    url: `rtsp://${username}:${password}@192.168.1.100:554/stream`
                };
            });

            /*
             * Attach the camera to the device.
             */
            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }    
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-rtsp-camera/device.mts" %}

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  /*
   * To play an RTSP stream, you simply need to return the URL to the stream
   * from the video url listener. Some streams require authentication in
   * different formats. This example uses query parameters for authentication.
   */
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoRTSP({
        allowInvalidCertificates: true,
        demuxer: "h265",
      });

      /*
       * The video url listener takes no arguments. It simply builds the
       * URL to the RTSP stream using the username and password.
       */
      video.registerVideoUrlListener(async () => {
        // Get the username and password that were set during pairing
        const { username, password } = this.getSettings();

        // Normally, you would get the device's IP from Discovery, or another method
        return {
          url: `rtsp://${username}:${password}@192.168.1.100:554/stream`,
        };
      });

      /*
       * Attach the camera to the device.
       */
      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-rtsp-camera/device.py" %}

```python
from homey import device


class Device(device.Device):
    # To play an RTSP stream, you simply need to return the URL to the stream
    # from the video url listener. Some streams require authentication in
    # different formats. This example uses query parameters for authentication.
    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_rtsp(
                allow_invalid_certificates=True, demuxer="h265"
            )

            # The video url listener takes no arguments. It simply builds the
            # URL to the RTSP stream using the username and password.
            async def url_listener() -> str:
                # Get the username and password that were set during pairing
                settings = self.get_settings()
                username, password = settings.get("username"), settings.get("password")

                # Normally, you would get the device's IP from Discovery, or another method
                return f"rtsp://{username}:{password}@192.168.1.100:554/stream"

            video.register_video_url_listener(url_listener)

            # Attach the camera to the device.
            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

### Example — RTMP

This example shows an RTMP camera, which is as simple as providing an URL. It's very similar to RTSP.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-rtmp-camera/device.mjs" %}

```mjs
import Homey from 'homey';

export default class MyRTMPDevice extends Homey.Device {

    async onInit() {        
        try {
            const video = await this.homey.videos.createVideoRTMP();

            /*
             * The video url listener takes no arguments. It simply builds the
             * URL to the RTMP stream.
             */
            video.registerVideoUrlListener(async () => {         
                // Normally, you would get the device's IP from Discovery, or another method
                return {
                    url: `rtmp://@192.168.1.100:1935`
                };
            });

            /*
             * Attach the camera to the device.
             */
            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }    
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-rtmp-camera/device.mts" %}

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoRTMP();

      /*
       * The video url listener takes no arguments. It simply builds the
       * URL to the RTMP stream.
       */
      video.registerVideoUrlListener(async () => {
        // Normally, you would get the device's IP from Discovery, or another method
        return {
          url: `rtmp://@192.168.1.100:1935`,
        };
      });

      /*
       * Attach the camera to the device.
       */
      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-rtmp-camera/device.py" %}

```python
from homey import device


class Device(device.Device):
    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_rtmp()

            # The video url listener takes no arguments. It simply builds the
            # URL to the RTMP stream.
            async def url_listener() -> str:
                # Normally, you would get the device's IP from Discovery, or another method
                return f"rtmp://@192.168.1.100:1935"

            video.register_video_url_listener(url_listener)

            # Attach the camera to the device.
            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

### Example — HLS

This example shows an HLS camera, which is as simple as providing an URL. It's very similar to RTSP.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-hls-camera/device.mjs" %}

```mjs
import Homey from 'homey';

export default class MyHLSDevice extends Homey.Device {

    async onInit() {        
        try {
            const video = await this.homey.videos.createVideoHLS();

            /*
             * The video url listener takes no arguments. It simply builds the
             * URL to the HLS stream.
             */
            video.registerVideoUrlListener(async () => {         
                // Normally, you would get the device's IP from Discovery, or another method
                return {
                    url: `http://@192.168.1.100/stream.m3u8`
                };
            });

            /*
             * Attach the camera to the device.
             */
            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }    
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-hls-camera/device.mts" %}

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoHLS();

      /*
       * The video url listener takes no arguments. It simply builds the
       * URL to the HLS stream.
       */
      video.registerVideoUrlListener(async () => {
        // Normally, you would get the device's IP from Discovery, or another method
        return {
          url: `http://@192.168.1.100/stream.m3u8`,
        };
      });

      /*
       * Attach the camera to the device.
       */
      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-hls-camera/device.py" %}

```python
from homey import device


class Device(device.Device):
    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_hls()

            # The video url listener takes no arguments. It simply builds the
            # URL to the HLS stream.
            async def url_listener() -> str:
                # Normally, you would get the device's IP from Discovery, or another method
                return f"http://@192.168.1.100/stream.m3u8"

            video.register_video_url_listener(url_listener)

            # Attach the camera to the device.
            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

### Example — DASH

This example shows an DASH camera, which is as simple as providing an URL. It's very similar to RTSP.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/drivers/my-dash-camera/device.mjs" %}

```mjs
import Homey from 'homey';

export default class MyDASHDevice extends Homey.Device {

    async onInit() {        
        try {
            const video = await this.homey.videos.createVideoDASH();

            /*
             * The video url listener takes no arguments. It simply builds the
             * URL to the DASH stream.
             */
            video.registerVideoUrlListener(async () => {         
                // Normally, you would get the device's IP from Discovery, or another method
                return {
                    url: `http://@192.168.1.100/stream.mpd`
                };
            });

            /*
             * Attach the camera to the device.
             */
            await this.setCameraVideo('main', 'Main Camera', video);
        } catch (err) {
            this.error('Error creating camera:', err);
        }    
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/drivers/my-dash-camera/device.mts" %}

```mts
import Homey from "homey";

export default class Device extends Homey.Device {
  async onInit(): Promise<void> {
    try {
      const video = await this.homey.videos.createVideoDASH();

      /*
       * The video url listener takes no arguments. It simply builds the
       * URL to the DASH stream.
       */
      video.registerVideoUrlListener(async () => {
        // Normally, you would get the device's IP from Discovery, or another method
        return {
          url: `http://@192.168.1.100/stream.mpd`,
        };
      });

      /*
       * Attach the camera to the device.
       */
      await this.setCameraVideo("main", "Main Camera", video);
    } catch (err) {
      this.error("Error creating camera:", err);
    }
  }
}

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/drivers/my-dash-camera/device.py" %}

```python
from homey import device


class Device(device.Device):
    async def on_init(self) -> None:
        try:
            video = await self.homey.videos.create_video_dash()

            # The video url listener takes no arguments. It simply builds the
            # URL to the DASH stream.
            async def url_listener() -> str:
                # Normally, you would get the device's IP from Discovery, or another method
                return f"http://@192.168.1.100/stream.mpd"

            video.register_video_url_listener(url_listener)

            # Attach the camera to the device.
            await self.set_camera_video("main", "Main Camera", video)
        except Exception as err:
            self.error("Error creating camera:", err)


homey_export = Device

```

{% endcode %}
{% endtab %}
{% endtabs %}

## Apps SDK Reference

{% tabs %}
{% tab title="JavaScript" %}
Please refer to [ManagerVideos](https://apps-sdk-v3.developer.homey.app/ManagerVideos.html) in the Apps SDK Reference to learn more about videos in your app.
{% endtab %}

{% tab title="TypeScript" %}
Please refer to [ManagerVideos](https://apps-sdk-v3.developer.homey.app/ManagerVideos.html) in the Apps SDK Reference to learn more about videos in your app.
{% endtab %}

{% tab title="Python" %}
Please refer to [ManagerVideos](https://python-apps-sdk-v3.developer.homey.app/manager/videos.html#homey.manager.videos.ManagerVideos) in the Apps SDK Reference to learn more about videos in your app.
{% endtab %}
{% endtabs %}
