Using oh-video in a popup. OH 5.1

I have a task to show a surveillance camera stream inside a popup: there’s a button with a photo; when I click it, a popup opens where a WebRTC stream from the camera is displayed. The problem is that if I enable autoplay on oh-video, the stream keeps playing even when the popup is closed (I can even hear the audio). If I disable autoplay and use the “hack” that GPT suggested, it starts when the popup opens, but randomly. How do people solve this seemingly trivial task? Here is the poorly working solution from GPT:

uid: home-sidebar
tags: []
props: {}
timestamp: Dec 24, 2025, 2:11:30 AM
component: div
config: {}
slots:
  default:
    - component: widget:button
      config:
        background: /static/snapshots/EntryGateDoorbell_snapshot.jpg
        description: Вхід
        popup: "#EntryGateDoorbell"
    - component: widget:button
      config:
        background: /static/snapshots/ParkingCamera_snapshot.jpg
        description: Парковка
        popup: "#ParkingCamera"
    - component: f7-popup
      config:
        1opened: =(items["EntryGateDoorbell_Ring"].state == 'OPEN') ? true:false
        id: EntryGateDoorbell
        push: true
        swipeToClose: true
        top: true
      slots:
        default:
          - component: div
            config:
              id: EntryGateDoorbell2
            slots:
              default:
                - component: f7-navbar
                  config:
                    title: Хвіртка
                  slots:
                    default:
                      - component: f7-nav-right
                        slots:
                          default:
                            - component: f7-link
                              config:
                                iconMaterial: close
                                popupClose: true
                - component: oh-video
                  config:
                    action: '=device.android ? "url" : ""'
                    actionUrl: unifi-protect://deeplink_v1/home/dashboard/
                    hideControls: false
                    item: EntryGateDoorbell_RTS
                    playerType: webrtc
                    posterItem: EntryGateDoorbell_Image
                    sendAudio: true
                    startManually: true
                    style:
                      min-height: 200px
                      width: 100%
                - component: widget:button
                  config:
                    bgcolorOFF: orange
                    buttonitem: EntryGate_Control
                    compact: true
                    confirmation: Справді відкрити?
                    description: Хвіртку
                    icon: material:lock_open
                    itemname: Відкрити
    - component: f7-popup
      config:
        id: ParkingCamera
        opened: =(items["ParkingGate_Opened"].state == 'OPEN') ? true:false
        push: true
        swipeToClose: true
        top: true
      slots:
        default:
          - component: div
            config:
              id: ParkingCamera2
            slots:
              default:
                - component: f7-navbar
                  config:
                    title: Парковка
                  slots:
                    default:
                      - component: f7-nav-right
                        slots:
                          default:
                            - component: f7-link
                              config:
                                iconMaterial: close
                                popupClose: true
                - component: oh-video
                  config:
                    action: '=device.android ? "url" : ""'
                    actionUrl: unifi-protect://deeplink_v1/home/dashboard/
                    hideControls: false
                    item: ParkingCamera_RTS
                    playerType: webrtc
                    posterItem: ParkingCamera_Image
                    startManually: true
                    style:
                      min-height: 200px
                      width: 100%
                - component: widget:button
                  config:
                    bgcolorOFF: red
                    buttonitem: ParkingGate_Control
                    compact: true
                    confirmation: Справді відкрити?
                    description: Ворота
                    icon: material:lock_open
                    itemname: Відкрити
                    visible: =items["ParkingGate_Opened"].state == "CLOSED"
                - component: widget:button
                  config:
                    bgcolorOFF: green
                    buttonitem: ParkingGate_Control
                    compact: true
                    confirmation: Справді закрити?
                    description: Ворота
                    icon: material:lock_outline
                    itemname: Закрити
                    visible: =items["ParkingGate_Opened"].state != "CLOSED"
    - component: script
      config:
        text: |
          (function () {
          function init(id) {
            let popup = document.getElementById(id);
            if (!popup || popup.__webrtcHooked) return;
            
            popup.__webrtcHooked = true;

            popup.addEventListener('popup:open', (e) => {
              const v = e.target.querySelector('video');
              if (v) {
                console.log('+');

                v.play().catch((e) => {
                  console.log(e)
                });

                const s = v.srcObject;
                if (s && typeof s.getTracks === 'function') {
                  s.getTracks().forEach(t => t.enabled = true);
                }
              }
            });

            popup.addEventListener('popup:closed', (e) => {
              const v = e.target.querySelector('video');
              if (!v) return;
              v.pause();

              const s = v.srcObject;
              if (s && typeof s.getTracks === 'function') {
                s.getTracks().forEach(t => t.enabled = false);
              }
            });
          };
          init('EntryGateDoorbell');
          init('ParkingCamera');
          })();

The underlying issue is with how the f7 library works. When you create an f7-popup, that element is always rendered on the page, even if you don’t see it. If a video is playing on that popup, that video element still exists and is still playing even after you hide the popup again.

The easiest solution here is to go back to using autoplay (and get rid of the GPT stuff). The difference is just that you only want the oh-video to exist when the popup is open (if the video doesn’t even exist when the popup is closed, it doesn’t matter if the popup still exists, there will be nothing playing). Since you already have a widget expression that determines if the popup is opened or not, you can just use that same expression in the visible property of the oh-video components as well.

1 Like

Unfortunately, the item I am currently using to open the popup was more of a test; in the final version it is not planned to be used. Using an item causes problems because

  1. the popup opens on all clients (major issue)
  2. it is not clear how to reliably intercept a manual popup close in order to reset the “open” switch.
- component: script
      config:
        text: |
          (function () {

          async function sendCommand(itemName, cmd) {
            const url = `/rest/items/${encodeURIComponent(itemName)}`;
            const res = await fetch(url, {
              method: 'POST',
              headers: { 'Content-Type': 'text/plain' },
              credentials: 'same-origin',
              body: String(cmd)
            });
            if (!res.ok) throw new Error(`REST ${res.status} ${res.statusText}`);
          }
          
          
          let popup = document.getElementById(id);
          if (!popup || popup.__webrtcHooked) return;
          
          popup.__webrtcHooked = true;

          popup.addEventListener('popup:closed', (e) => {
            sendCommand('UI_POPUP_PARKINGCAMERA', 'OFF');
          };
        })();

It looks like I’ve found a solution. I moved everything inside the popup into a separate widget with video autoplay, and I use this kind of button to open the popup. The video starts and stops correctly.

- component: oh-button
  config:
    text: test
    action: popup
    actionModal: widget:home-parking