Camera: Clickable thumbnail opens to a larger stream

Thanks, this one: actionVariableValue: =( vars.rewind > 1 )?(vars.rewind - 1):1 is genius. And with much more clear brains today i’ve seen my mistakes. Almost did it, but a rather big issue still remains - the video is not loading when i popup the widget until two switches from live to archive. And used all kind of css sizes, but still can’t fit the entire video into page - a small part in bottom is hidden and shows up only in fullscreen mode.

uid: Video
tags:
  - video
props:
  parameters:
    - context: item
      label: Camera stream URL
      name: camerahls
      required: false
      type: TEXT
    - context: item
      label: Motion item
      name: motionItem
      required: false
      type: TEXT
    - context: item
      label: Item for the door lock
      name: lockItem
      required: false
      type: TEXT
    - context: item
      label: Item for door state
      name: doorItem
      required: false
      type: TEXT
    - context: item
      description: Ipcamera MP4 history length item
      label: MP4 history length
      name: history
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Aug 10, 2022, 2:56:17 AM
component: f7-card
config:
  class: videoPopover
  key: '=(vars.rewind === undefined) ? Math.random() : Math.random() + vars.rewind'
  style:
    --f7-card-margin-horizontal: 0px
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px var(--f7-bars-bg-color)
    color: var(--f7-text-color)
    font-size: medium
    font-weight: 500
    height: auto
    margin: 5
    noShadow: false
    padding: 0
    text-shadow: 1px 0px 2px var(--f7-bars-bg-color), -1px 0px 2px var(--f7-bars-bg-color), 0px 0px 2px var(--f7-bars-bg-color), 0px 0px 3px var(--f7-bars-bg-color)
    width: 100%
slots:
  default:
    - component: oh-video-card
      config:
        hideControls: false
        startManually: false
        style:
          height: auto
          width: auto
        url: =items[props.camerahls].state
        visible: =vars.archive != 2
    - component: oh-video-card
      config:
        hideControls: false
        startManually: false
        style:
          height: auto
          width: auto
        url: ="http://192.168.2.9:8080/static/doorphone/Domofon" + (vars.rewind || items[props.history].state -1) + ".mp4"
        visible: =vars.archive == 2
    - component: f7-row
      config:
        style:
          position: absolute
          top: 10px
          width: 99%
          z-index: 30
      slots:
        default:
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 2
              iconColor: white
              iconF7: folder
              iconSize: 30
              visible: =(vars.archive !== 2)
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 1
              iconColor: white
              iconF7: videocam
              iconSize: 30
              visible: =(vars.archive == 2)
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =(vars.rewind != undefined)?(( vars.rewind > 1 )?(vars.rewind - 1):1):(items[props.history].state -2)
              iconColor: white
              iconF7: backward_end
              iconSize: 30
              visible: =(vars.archive == 2)
          - component: oh-icon
            config:
              icon: "=(items[props.doorItem].state === 'ON') ? 'door-closed' : 'door'"
              style:
                color: "=(items[props.doorItem].state === 'ON') ? 'cyan' : 'red'"
                z-index: 99
              visible: =((props.doorItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: command
              actionCommand: ON
              actionItem: =props.lockItem
              class: card-prevent-open
              iconF7: "=(items[props.lockItem].state === 'ON') ? 'lock_open' : 'lock'"
              iconSize: 30
              style:
                color: "=(items[props.lockItem].state === 'ON') ? 'cyan' : 'var(--f7-card-header-text-color)'"
                margin-left: auto
                margin-right: auto
                z-index: 99
              visible: =((props.lockItem !== undefined) && (vars.archive !== 2))
          - component: oh-icon
            config:
              icon: "=(items[props.motionItem].state === 'ON') ? 'mymotion-on' : 'mymotion-off'"
              style:
                z-index: 99
              visible: =((props.motionItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =( vars.rewind < items[props.history].state - 1)?(vars.rewind + 1):(items[props.history].state - 1)
              iconColor: white
              iconF7: forward_end
              iconSize: 30
              visible: =(vars.archive == 2)
          - component: oh-button
            config:
              iconColor: white
              iconF7: clear
              iconSize: 30
              popoverClose: .videoPopover
              style:
                z-index: 99
              tooltip: Закрыть

I don’t see any obvious reason for this behavior, and the widget appears to function as expected when tested in the widget editor. This is just a guess, but I can only surmise that this has something to do with calling an f7-card as a popup. Since you’re only using it as a container anyway and not really taking advantage of any card specific features, you might try changing the base component here to a f7-popup. That might also make the final bit of css easier to fix that last problem of hidden bottom.

1 Like

If i use f7-popup instead of f7-card or f7-block, then video height becomes NaN.
The error with non-loading video seems to come from parent widget not passing modalConfig (it’s shown as [object Object] in page inspector). Inside parent widget modalConfig is described like this:

        actionModalConfig:
          camerahls: =props.camerahls
          history: =props.history
          doorItem: =props.doorItem
          lockItem: =props.lockItem
          motionItem: =props.motionItem

But how then video becomes fine after some switching here and there and why buttons and door/motion states do work normally?
Tested with

    - component: oh-video
      config:
        hideControls: false
        startManually: false
        url: =items[props.camerahls].state

to get away from all these ternary and undefined variables and got the same issue! No video on initial popover, but if i close popover and reopen it again - then i see hls stream working from the very first milliseconds, as if the stream itself started in the first popover.

I have had a lot of trouble today playing with the - component: oh-video-card and using it with the url: I found behaviour similar to yours until I changed to using it like this…

- component: oh-video-card
  config:
    item: =[props.camera + '_HLSURL']

props.camera is the equipment level group, then all your member items can be guessed based off the auto naming convention that is used when you create equipment from things.

When ever you refresh the page, it fails a lot of the time if your using the url and not the item.

I can’t use item because for mp4 history i have to use URL. Maybe some kind of auto-refresh (like changing some variable inside key: ) needed on the first widget load.

Do you have more than one widget on a page calling this popup widget? I wonder if there’s some conflict with the settings.

It’s worth testing with matt1’s suggestion just to see if that is the problem. 1) Because it might be worth a github issue (if there isn’t one already) and 2) It would be possible (and only a little awkward) to convert this to a system that uses an item state instead of a variable if that turns out to be a fix.

Already tested, indeed if item: props.item is used instead of url: items[props.item].state, then there is no problem.

So widgets for my doorbell now are looking like this:

uid: ClickablePTZCamera
tags:
  - video
props:
  parameters:
    - context: item
      label: Camera image URL
      name: camerajpeg
      required: true
      type: TEXT
    - context: item
      label: Camera stream URL
      name: camerahls
      required: false
      type: TEXT
    - context: item
      label: Motion item
      name: motionItem
      required: false
      type: TEXT
    - context: item
      label: Item for the door lock
      name: lockItem
      required: false
      type: TEXT
    - context: item
      label: Item for door state
      name: doorItem
      required: false
      type: TEXT
    - context: item
      description: Ipcamera MP4 history length item
      label: MP4 history length
      name: history
      required: true
      type: TEXT
timestamp: Aug 10, 2022, 8:41:38 PM
component: f7-card
config:
  style:
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px var(--f7-bars-bg-color)
    color: var(--f7-text-color)
    font-size: medium
    font-weight: 500
    max-height: 150px
    noShadow: false
slots:
  default:
    - component: oh-image
      config:
        action: popover
        actionModal: widget:Video
        actionModalConfig:
          camerahls: =props.camerahls
          history: =props.history
          doorItem: =props.doorItem
          lockItem: =props.lockItem
          motionItem: =props.motionItem
        item: =[props.camerajpeg]
        lazy: true
        style:
          border-radius: var(--f7-card-expandable-border-radius)
          height: 150px
          margin: 0px
          padding: 0px
          width: 100%
          z-index: -2
    - component: f7-row
      config:
        style:
          position: absolute
          top: 2px
          width: 99%
          z-index: 30
      slots:
        default:
          - component: oh-icon
            config:
              icon: "=(items[props.doorItem].state === 'ON') ? 'door-closed' : 'door'"
              style:
                color: "=(items[props.doorItem].state === 'ON') ? 'cyan' : 'red'"
                z-index: 99
              visible: =props.doorItem !== undefined
              width: 22
          - component: oh-button
            config:
              action: command
              actionCommand: ON
              actionItem: =props.lockItem
              class: card-prevent-open
              iconF7: "=(items[props.lockItem].state === 'ON') ? 'lock_open' : 'lock'"
              iconSize: 23
              style:
                color: "=(items[props.lockItem].state === 'ON') ? 'cyan' : 'var(--f7-card-header-text-color)'"
                margin-left: auto
                margin-right: auto
                z-index: 99
              visible: =props.lockItem !== undefined
          - component: oh-icon
            config:
              icon: "=(items[props.motionItem].state === 'ON') ? 'mymotion-on' : 'mymotion-off'"
              style:
                z-index: 99
              visible: =props.motionItem !== undefined
              width: 23
uid: Video
tags:
  - video
props:
  parameters:
    - context: item
      label: Camera stream URL
      name: camerahls
      required: true
      type: TEXT
    - context: item
      description: Ipcamera MP4 history length item
      label: MP4 history length
      name: history
      required: true
      type: TEXT
    - context: item
      label: Motion item
      name: motionItem
      required: false
      type: TEXT
    - context: item
      label: Item for the door lock
      name: lockItem
      required: false
      type: TEXT
    - context: item
      label: Item for door state
      name: doorItem
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Aug 10, 2022, 11:19:10 PM
component: f7-block
config:
  key: =Math.random() + vars.archive + vars.rewind
  style:
    margin: 0
    margin-left: auto
    noShadow: false
    padding: 0
    color: (--var-f7-text-color)
    text-shadow: 1px 0px 2px black, -1px 0px 2px black, 0px 0px 2px black, 0px 0px 3px black
    width: 97%
slots:
  default:
    - component: oh-video
      config:
        hideControls: false
        startManually: false
        item: =props.camerahls
        visible: =(vars.archive !== 2)
    - component: oh-video
      config:
        hideControls: false
        startManually: false
        url: ="http://192.168.2.9:8080/static/doorphone/Domofon" + (vars.rewind || items[props.history].state -1) + ".mp4"
        type: video/mp4
        visible: =(vars.archive == 2)
    - component: f7-row
      config:
        style:
          position: absolute
          top: 10px
          width: 99%
          z-index: 30
      slots:
        default:
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 2
              iconColor: white
              iconF7: folder
              iconSize: 30
              visible: =(vars.archive !== 2)
              tooltip: Видеоархив
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 1
              iconColor: white
              iconF7: videocam
              iconSize: 30
              visible: =(vars.archive == 2)
              tooltip: Камера
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =(vars.rewind != undefined)?(( vars.rewind > 1 )?(vars.rewind - 1):1):(items[props.history].state -2)
              iconColor: white
              iconF7: backward_end
              iconSize: 30
              visible: =(vars.archive == 2)
              tooltip: Назад
          - component: oh-icon
            config:
              icon: "=(items[props.doorItem].state === 'ON') ? 'door-closed' : 'door'"
              style:
                color: "=(items[props.doorItem].state === 'ON') ? 'cyan' : 'red'"
                z-index: 99
              visible: =((props.doorItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: command
              actionCommand: ON
              actionItem: =props.lockItem
              class: card-prevent-open
              iconF7: "=(items[props.lockItem].state === 'ON') ? 'lock_open' : 'lock'"
              iconSize: 30
              style:
                color: "=(items[props.lockItem].state === 'ON') ? 'white' : 'red'"
                margin-left: auto
                margin-right: auto
                z-index: 99
              visible: =((props.lockItem !== undefined) && (vars.archive !== 2))
          - component: oh-icon
            config:
              icon: "=(items[props.motionItem].state === 'ON') ? 'mymotion-on' : 'mymotion-off'"
              style:
                z-index: 99
              visible: =((props.motionItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =( vars.rewind < items[props.history].state - 1)?(vars.rewind + 1):(items[props.history].state - 1)
              iconColor: white
              iconF7: forward_end
              iconSize: 30
              visible: =(vars.archive == 2)
              tooltip: Вперёд
          - component: oh-button
            config:
              iconColor: white
              iconF7: clear
              iconSize: 30
              popoverClose: .popover.modal-in
              style:
                z-index: 99
              tooltip: Закрыть

And I’m pretty happy with them except the issue mentioned above. And note popoverClose: .popover.modal-in that’s the only way i’ve figured out to close the popover, adding class didn’t helped, maybe because the popover is called from another widget.

Try the following code, but make sure you have all items created first with the default naming convention, or just create the items with the “create equipment from thing” feature of oh3. You will need to tick the show advanced box to see the extra channels and add the mp4History and mp4HistoryLength channels to items/equipment, as well as all the non advanced channels.

Issues still to solve:

  1. How to get variables to default to a set value on first startup? You need to click on the next or previous for it to show the first recording when it is undefined. This can be solved by reading this post: Using vars in custom widgets: when are vars updated? - Add-ons / UIs - openHAB Community

  2. If there is an easier way to fetch a uniqueID of a thing AND the IP of openhab. This would make the setup easier instead of needing a baseURL to be filled in by someone who may not know these details.

uid: CameraHistory
tags: []
props:
  parameters:
    - context: item
      label: Select the Camera (Equipment)
      name: camera
      required: true
      type: TEXT
    - description: "example: http://192.168.1.2:8080/ipcamera/DoorbellCamera/"
      label: Base URL
      name: cameraBaseURL
      required: true
      type: TEXT
timestamp: Aug 11, 2022, 11:42:00 PM
component: f7-card
config:
  class: videoPopover
  key: "=(vars.rewind === undefined) ? Math.random() : Math.random() + vars.rewind"
  style:
    --f7-card-margin-horizontal: 0px
    border-radius: var(--f7-card-expandable-border-radius)
    box-shadow: 5px 5px 10px 1px var(--f7-bars-bg-color)
    color: var(--f7-text-color)
    font-size: medium
    font-weight: 500
    height: auto
    margin: 5
    noShadow: false
    padding: 0
    text-shadow: 1px 0px 2px var(--f7-bars-bg-color), -1px 0px 2px var(--f7-bars-bg-color), 0px 0px 2px var(--f7-bars-bg-color), 0px 0px 3px var(--f7-bars-bg-color)
    width: 100%
slots:
  default:
    - component: oh-video-card
      config:
        hideControls: false
        startManually: false
        style:
          height: auto
          width: auto
        item: =[props.camera + '_HLSURL']
        visible: =vars.archive != 2
    - component: oh-video-card
      config:
        hideControls: false
        startManually: false
        style:
          height: auto
          width: auto
        url: =props.cameraBaseURL + items[props.camera + '_MP4History'].state.split(",")[vars.rewind] +".mp4"
        visible: =vars.archive == 2
    - component: f7-row
      config:
        style:
          position: absolute
          top: 10px
          width: 99%
          z-index: 30
      slots:
        default:
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 2
              iconColor: white
              iconF7: folder
              iconSize: 30
              visible: =(vars.archive !== 2)
          - component: Label
            config:
              text: =items[props.camera+'_MP4HistoryLength'].state
              style:
                position: absolute
                left: 45px
                top: 2px
                color: white
                font-size: 18px
                z-index: 2
              visible: =vars.archive != 2 && items[props.camera+'_MP4HistoryLength'].state > 0
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 1
              iconColor: white
              iconF7: videocam
              iconSize: 30
              visible: =(vars.archive == 2)
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =(vars.rewind+1 < items[props.camera +'_MP4HistoryLength'].state)?(vars.rewind+1):(items[props.camera +'_MP4HistoryLength'].state-1)
              iconColor: white
              iconF7: backward_end
              iconSize: 30
              visible: =vars.archive == 2 && items[props.camera+'_MP4HistoryLength'].state > 0
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =(vars.rewind > 1)?(vars.rewind-1):(0)
              iconColor: white
              iconF7: forward_end
              iconSize: 30
              visible: =vars.archive == 2 && items[props.camera+'_MP4HistoryLength'].state > 0
          - component: oh-button
            config:
              iconColor: white
              iconF7: clear
              iconSize: 30
              tooltip: Clear cameras mp4 history
              action: toggle
              actionItem: =props.camera+'_MP4HistoryLength'
              actionCommand: "0"
              visible: =vars.archive == 2 && items[props.camera+'_MP4HistoryLength'].state > 0

The first issue is not related to undefined variables but to the way oh-video and oh-video-card handles item: and url: properties. And… I’ve found a fix! And the fix is a bit crazy.

uid: Video
tags:
  - video
props:
  parameters:
    - context: item
      label: Camera stream URL
      name: camerahls
      required: true
      type: TEXT
    - context: item
      description: Ipcamera MP4 history length item
      label: MP4 history length
      name: history
      required: true
      type: TEXT
    - context: item
      label: Motion item
      name: motionItem
      required: false
      type: TEXT
    - context: item
      label: Item for the door lock
      name: lockItem
      required: false
      type: TEXT
    - context: item
      label: Item for door state
      name: doorItem
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Aug 13, 2022, 2:12:21 PM
component: f7-block
config:
  key: =Math.random() + vars.archive + vars.rewind
  style:
    color: (--var-f7-text-color)
    margin: 0
    margin-left: auto
    noShadow: false
    padding: 0
    text-shadow: 1px 0px 2px black, -1px 0px 2px black, 0px 0px 2px black, 0px 0px 3px black
    width: 97%
slots:
  default:
    - component: oh-video
      config:
        hideControls: false
        item: =props.camerahls
        startManually: false
        visible: =(vars.archive !== 2)
    - component: oh-video-card
      config:
        hideControls: false
        startManually: false
        type: video/mp4
        url: ="http://192.168.2.9:8080/static/doorphone/Domofon" + (vars.rewind || items[props.history].state -1) + ".mp4"
        visible: =(vars.archive == 2)
    - component: f7-row
      config:
        style:
          position: absolute
          top: 10px
          width: 99%
          z-index: 30
      slots:
        default:
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 2
              iconColor: white
              iconF7: folder
              iconSize: 30
              tooltip: Видеоархив
              visible: '=(items[props.history].state !== NULL ? vars.archive !== 2 : false)'
          - component: oh-button
            config:
              action: variable
              actionVariable: archive
              actionVariableValue: 1
              iconColor: white
              iconF7: videocam
              iconSize: 30
              tooltip: Камера
              visible: =(vars.archive == 2)
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =(vars.rewind != undefined)?(( vars.rewind > 1 )?(vars.rewind - 1):1):(items[props.history].state -2)
              iconColor: white
              iconF7: backward_end
              iconSize: '=(vars.rewind !== undefined ? ((vars.rewind > 1 )? 30 : 0):30)'
              tooltip: Назад
              visible: =(vars.archive == 2)
          - component: oh-icon
            config:
              icon: "=(items[props.doorItem].state === 'ON') ? 'door-closed' : 'door'"
              style:
                color: "=(items[props.doorItem].state === 'ON') ? 'cyan' : 'red'"
                z-index: 99
              visible: =((props.doorItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: command
              actionCommand: ON
              actionItem: =props.lockItem
              class: card-prevent-open
              iconF7: "=(items[props.lockItem].state === 'ON') ? 'lock_open' : 'lock'"
              iconSize: 30
              style:
                color: "=(items[props.lockItem].state === 'ON') ? 'white' : 'red'"
                margin-left: auto
                margin-right: auto
                z-index: 99
              visible: =((props.lockItem !== undefined) && (vars.archive !== 2))
          - component: oh-icon
            config:
              icon: "=(items[props.motionItem].state === 'ON') ? 'mymotion-on' : 'mymotion-off'"
              style:
                z-index: 99
              visible: =((props.motionItem !== undefined) && (vars.archive !== 2))
              width: 30
          - component: oh-button
            config:
              action: variable
              actionVariable: rewind
              actionVariableValue: =( vars.rewind < items[props.history].state - 1)?(vars.rewind + 1):(items[props.history].state - 1)
              iconColor: white
              iconF7: forward_end
              iconSize: '=(vars.rewind !== undefined ? ((vars.rewind < items[props.history].state -1 )? 30 : 0):0)'
              tooltip: Вперёд
              visible: =(vars.archive == 2)
          - component: oh-button
            config:
              iconColor: white
              iconF7: clear
              iconSize: 30
              popoverClose: .popover.modal-in
              style:
                z-index: 99
              tooltip: Закрыть

So what’s changed? Archive button visibility! So the widget accesses items[props.history].state before user can click to switch to archive, so when user actually switch to archive, that state is available already for oh-video!

@matt1 : When I click at the widget, the screen turns blurry. The small preview of the widget is fine and sharp. This is not only in edit mode; it’s always - also in run mode and in the app. Any idea what’s wrong here?

In the logs I find this message:

2022-08-21 22:46:49.013 [WARN ] [era.internal.handler.IpCameraHandler] - Binding has not been supplied with a FFmpeg Input URL, so some features will not work.

I also noted some “strange” options at the thing I never entered there:

When I use e.g. the JPG path in an image widget, everything looks good.
My camera is an INSTAR 9008 FullHD. Thanks!

Well, the binding has not been supplied with a FFmpeg Input URL, so some features will not work. =)
All options are ok, but does your camera has HLS-stream and is this stream enabled in Ipcamera binding? Do you see the stream, if you pass the URL from HLS-stream channel to videoplayer like VLC? If you don’t, could you check your camera with any tool like ONVIF Device Manager, get the camera’s stream URL from there and paste it to FFmpeg Input property of binding?

It may be a bug if it is happening only when you use in Masonry?
See here…
Expandable card is “blurry” when expanded in oh-masonry - Add-ons / UIs - openHAB Community

They are the DEFAULT settings which are needed for the binding to work, but the binding allows you to modify them if you choose to do something advanced.

If you have set it up as an INSTAR I would like to know why you get the WARN message you posted as ONVIF should discover the FFmeg input url automatically for you. To know what is going on I would need the TRACE level log from when the camera first connects and does its hand shake. Best to start a new thread on the forum for that. It may be that your firewall is blocking the ONVIF port so the binding can not connect.

Found a workaround for the blurring problem:
image

…which only works in the browser on my PC, but not in the app :frowning:

did anyone get managed this to work on Android?
on my PC the widget works fine within the browser, and my cams are also working in the OH-app (Basic UI), but they showing just a grey block in the widget on Main UI.

I´ve read the topic, but didn´t find a solution.
Any idea?

How is it possible to configure the refresh/reload frequency?

Works the same in my case on any android starting from 5.1. @matt1, do you know if this a known issue?
In my case it sometimes work fine but mostly the thumbnail is greyed or blinking. When thumbnail is clicked it works fine.

Set lazy to false that will pull the image faster…

1 Like

Did you found a solution? My snapshot image is most of the time outdated and from the last time I checked.
The image which is use from the ip camera binding is fine.