Camera: Clickable thumbnail opens to a larger stream

This is a clickable camera widget, that can display a different URL for the thumb nail to when it is clicked. This allows a low framerate stream to be used when the widget is small, yet when it is clicked it will open up to a higher framerate. You can supply the same URL to both.

widget

The widget also has some other features if you supply it a camera Equipment group that has the standard channels from the ipCamera binding linked to items with the default naming structure. These features are:

  • An ear icon appears if the camera hears audio.
  • An eye icon appears if the camera sees movement.
  • A gear icon appears if the camera equipment group has any items that can be displayed.

Another feature is if you select an item to switch, an icon will appear that allows you to turn a switch on and off.

To install this in openHAB 3.1 and older, go to Developer Tools > Widgets, create a new one, and copy-pasting the code below. In openHAB 3.2 use the new Marketplace feature to install the widget.

After installing you can insert the widget into layout pages.

Widget Code

uid: ClickableCameraImage
props:
  parameters:
    - description: "example: http://192.168.1.2:8080/ipcamera/uniqueID/autofps.mjpeg"
      label: Thumbnail URL
      name: thumbnailURL
      required: true
      type: TEXT
    - description: "example: http://192.168.1.2:8080/ipcamera/uniqueID/snapshots.mjpeg"
      label: Stream URL
      name: streamURL
      required: true
      type: TEXT
    - context: item
      label: Select the Camera to Control
      name: camera
      required: false
      type: TEXT
    - label: Show Equipment Controls
      name: showSettings
      required: true
      type: BOOLEAN
    - label: Show Audio Alarms
      name: showAudioAlarms
      required: true
      type: BOOLEAN
    - label: Show Motion Alarms
      name: showMotionAlarms
      required: true
      type: BOOLEAN
    - context: item
      label: Item to Switch
      name: switchItem
      required: false
      type: TEXT
component: f7-card
config:
  style:
    --f7-card-margin-horizontal: 0px
    height: 9rem
    width: 16rem
slots:
  default:
    - component: oh-image-card
      config:
        action: photos
        actionPhotoBrowserConfig:
          lazy: true
          theme: dark
          type: popup
        actionPhotos: =[props.streamURL]
        lazy: true
        style:
          border-radius: 6px
          height: 9rem
          margin: 0px
          width: 100%
        url: =props.thumbnailURL
    - component: f7-card-content
    - component: oh-link
      config:
        action: toggle
        actionCommand: true
        actionCommandAlt: false
        actionItem: =props.switchItem
        iconF7: power
        iconSize: 25
        style:
          color: "=(items[props.switchItem].state === 'ON') ? 'cyan' : 'white'"
          left: 0.2rem
          opacity: "=(items[props.switchItem].state === 'ON') ? '0.4' : '0.3'"
          position: absolute
          top: 0rem
        visible: =props.switchItem !== undefined
    - component: oh-link
      config:
        action: group
        actionGroupPopupItem: =props.camera
        iconF7: gear_alt
        iconSize: 25
        style:
          color: white
          opacity: 0.4
          position: absolute
          right: 0.2rem
          top: 0rem
        visible: =props.showSettings === true
    - component: oh-link
      config:
        iconF7: eye
        iconSize: 18
        visible: =props.showMotionAlarms === true
        style:
          color: white
          opacity: '=(props.showMotionAlarms !== true) ? "0" : (items[props.camera + "_Motion_Alarm"].state === "ON") ? "0.5" : "0" '
          position: absolute
          right: 0.2rem
          top: 7.8rem
    - component: oh-link
      config:
        iconF7: ear
        iconSize: 18
        visible: =props.showAudioAlarms === true
        style:
          color: white
          left: 0rem
          opacity: '=(props.showAudioAlarms !== true) ? "0" : (items[props.camera + "_Audio_Alarm"].state === "ON") ? "0.5" : "0" '
          position: absolute
          top: 7.7rem

Older Widget

The below widget is an older design I was playing with that works but will load and keep the streams open non stop. This can be a problem if you have FFmpeg creating the stream and chewing CPU power. The widget above will free up the CPU when you close the popup view, this one will not. I don’t recommend you use this older version with less features, however the code is still useful to learn from.

uid: ExpandableCameraCard
props:
  parameters:
    - description: "example: http://192.168.1.2:54321/autofps.mjpeg"
      label: Thumbnail URL
      name: thumbnailURL
      required: true
      type: TEXT
    - description: "example: http://192.168.1.2:54321/snapshots.mjpeg"
      label: Stream URL
      name: streamURL
      required: true
      type: TEXT
    - context: item
      label: Select Camera to open controls for
      name: camera
      required: false
      type: TEXT
    - context: item
      label: Item for Switch
      name: switchItem
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 27, 2020, 4:44:10 PM
component: f7-card
config:
  class:
    - no-margin
  expandable: true
  style:
    border-radius: 6px
    height: 9.6em
    width: 17em
slots:
  default:
    - component: f7-card-content
      config:
        class: card-opened-fade-in
        style:
          color: white
          background-image: ="url('"+props.streamURL+"')"
          background-size: 100% auto
          background-position: center
          background-repeat: no-repeat
      slots:
        default:
          - component: oh-link
            config:
              class:
                - card-prevent-open
              style:
                margin: 0em 0.25em 0 -0.7em
                color: var(--f7-card-text-color)
                opacity: 0.4
                z-index: 99
              visible: =props.switchItem !== undefined
              iconF7: power
              iconSize: 22
              round: true
              action: toggle
              actionItem: =props.switchItem
              actionCommand: ON
              actionCommandAlt: OFF
          - component: oh-link
            config:
              visible: =props.camera !== undefined
              class:
                - card-prevent-open
              style:
                margin: 0 0.25em
                color: var(--f7-card-text-color)
                opacity: 0.4
                z-index: 99
              iconF7: gear_alt
              iconSize: 22
              round: true
              action: group
              actionGroupPopupItem: =props.camera
    - component: f7-card-content
      config:
        class: card-opened-fade-out
        style:
          color: white
          background-image: ="url('"+props.thumbnailURL+"')"
          background-size: 17rem 10rem
          background-repeat: no-repeat
          border-radius: 6px
      slots:
        default:
          - component: oh-link
            config:
              class:
                - card-prevent-open
              style:
                margin: 0em 0.25em 0 -0.7em
                color: "=(items[props.switchItem].state === 'ON') ? 'cyan' : 'white'"
                opacity: "=(items[props.switchItem].state === 'ON') ? '1' : '0.3'"
              visible: =props.switchItem !== undefined
              iconF7: power
              iconSize: 22
              round: true
              action: toggle
              actionItem: =props.switchItem
              actionCommand: ON
              actionCommandAlt: OFF
          - component: oh-link
            config:
              visible: =props.camera !== undefined
              class:
                - card-prevent-open
              style:
                margin: 0 0.25em
                color: var(--f7-card-text-color)
                opacity: 0.4
              iconF7: gear_alt
              iconSize: 22
              round: true
              action: group
              actionGroupPopupItem: =props.camera

7 Likes

Mine opens up into big blurry mess

It also looks a little different to the standard widget

image

I have been playing with the binding for the last couple of days and its awesome.

The fuzzy view is caused by clicking on it when your in the edit mode, works when you exit edit.

This different code is also close to working but I cant work out how to give the cell a background url, so its a blank button that when pressed opens up the URL stream.

uid: CameraTest
props:
  parameters:
    - description: "example: http://192.168.1.2:54321/snapshots.mjpeg"
      label: URL
      name: URL
      required: true
      type: TEXT
timestamp: Dec 13, 2020, 9:20:33 PM
component: oh-cell
slots:
  default:
    - component: oh-image
      config:
        lazy: true
        lazyFadeIn: true
        url: =props.URL
        style:
          position: absolute
          top: 0px
          left: 0px
          maxWidth: 100%
          height: auto
          z-index: 5

Yeah I got that set as default widget for model. But a little different as it automatically sets up stream

If you use create equipment from thing and use default names like blah_MJPEGURL
You can reference that in your widget if you name it item

Eg create widget


uid: Default_Camera
tags: []
props:
  parameters:
    - context: item
      description: The Camera Equipment Item
      label: Item
      name: item
      required: false
      type: TEXT
  parameterGroups: []
component: f7-block
config:
  style:
    --f7-button-text-color: var(--f7-text-color)
    --f7-button-bg-color: var(--f7-card-bg-color)
    --f7-theme-color-rgb: var(--f7-color-blue-rgb)
  class:
    - no-padding
slots:
  default:
    - component: f7-row
      config:
        class:
          - margin
      slots:
        default:
          - component: f7-col
            slots:
              default:
                - component: oh-image
                  config:
                    class:
                      - margin
                      - no-padding
                    lazy: true
                    lazyFadeIn: true
                    url: =items[props.item + "_MJPEGURL"].state

then goto model select equipment the add metadata / default standalone widget / Default_Camera

this way you will be able to put the PTZ controls in the widget

1 Like

That has been what I have been aiming towards for many months, however for a standard camera view it is better to use a URL, because many people will want to use Blue Iris, Motion, or any other number of software to process the camera with and not the binding. When it comes to PTZ or a history of recent recordings, then that is different and the binding has been written to leverage things that hopefully will come together over time.

Here’s another piece of code that is probably the closet I have come to what I want. It requires a hard coded widget for each camera on top of this. Still playing to see what the best method will be.

uid: ModalCamera
timestamp: Dec 13, 2020, 11:05:28 PM
component: f7-card
config:
  class:
    - oh-cell
slots:
  default:
    - component: oh-button
      config:
        action: popup
        actionModal: widget:SingleCamera
        style:
          color: transparent
          padding: 75px
          background-image: url(http://192.168.1.2:54321/autofps.mjpeg)
          background-size: cover
          background-repeat: no-repeat
          background-position: center
1 Like

Cool, but you have the oh-image-card for that, it’s true that you don’t have a cell widget for images.
On a layout click Add Block, Add Row, Add Column, and then add an “Image Card” widget. Configure the item for it, if it’s an Image item it will display the image, if it’s a String it will assume it to be the URL
(you can also configure a fixed URL).

Below you have the “Action” settings, you can set it to “Open photo browser”:
The other settings are [object Object] in the screenshot below because I configured them with YAML (actionPhotos is an array and actionPhotoBrowserConfig an object):

component: oh-image-card
config:
  item: WebcamSalon_MJPEGURL
  action: photos
  actionPhotos:
    - item: WebcamSalon_MJPEGURL
  actionPhotoBrowserConfig:
    theme: dark

image

If you specify multiple items in actionPhotos you’ll have a carousel and can swipe from one to the other (or use the arrows), and add captions if you like:

component: oh-image-card
config:
  item: WebcamSalon_MJPEGURL
  action: photos
  actionPhotos:
    - item: WebcamSalon_MJPEGURL
      caption: Camera 1
    - url: https://source.unsplash.com/random
      caption: Random image
  actionPhotoBrowserConfig:
    theme: dark
1 Like

@ysc
Thanks for the pointers, I had a few things go click in my head after seeing that. However I have 1 thing which I would like your advice on if it is a bug, code on the todo list, or a limitation on the way I am doing it…

The below widget fails if I try to use =props.streamURL but if I hardcode an URL in there in the format http://192.168.1.2:54321/snapshots.mjpeg it then works great. The thumbnailURL works as a props, so it is only the location of trying to use it inside the actionPhotos (array?).

uid: ClickableCamera
props:
  parameters:
    - description: "example: http://192.168.1.2:54321/autofps.mjpeg"
      label: Thumbnail URL
      name: thumbnailURL
      required: true
      type: TEXT
    - description: "example: http://192.168.1.2:54321/snapshots.mjpeg"
      label: Stream URL
      name: streamURL
      required: true
      type: TEXT
timestamp: Dec 14, 2020, 7:04:31 PM
component: oh-image-card
config:
  class:
    - oh-cell
    - no-margin
  style:
    - border-radius: 6px
    - width: 100%
    - height: auto
  url: =props.thumbnailURL
  action: photos
  actionPhotos: 
    - url: =props.streamURL
  actionPhotoBrowserConfig:
    theme: dark
    type: popup

Yes you can’t use expressions inside array members, but you can use an expression to build the array itself. So instead of:

actionPhotos:
  - url: =props.streamURL

do:

actionPhotos: =[props.streamURL]
2 Likes

Thanks, that works perfect.

I actually tried many combos that were close and all failed before I posted as I didn’t know the right syntax…

actionPhotos: [=props.streamURL]
actionPhotos: [’=props.streamURL’]

Edit: see post 1 of this thread, it has the most up to date code that I use.

Multiple Cameras can be done like this and with - html: You should be able to play back mp4 recordings, or possibly HLS video streams.

uid: cameraAll
timestamp: Dec 14, 2020, 10:59:55 PM
component: oh-image-card
config:
  class:
    - oh-cell
    - no-margin
  style:
    - border-radius: 6px
    - width: 100%
    - height: auto
  url: https://source.unsplash.com/category/technology
  action: photos
  actionPhotos:
    - html: <video loop autoplay src="https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_1MB.mp4"></video>
      caption: BunnyCamera
    - url: https://source.unsplash.com/category/nature
      caption: Camera2
    - url: https://source.unsplash.com/category/street-photography
      caption: Camera3
    - url: https://source.unsplash.com/category/interiors
      caption: Camera4
  actionPhotoBrowserConfig:
    theme: dark
    type: popup

Single Camera that can have the URLs configured. Post one of this thread has a better version with more features.

uid: ClickableCamera
props:
  parameters:
    - description: "example: http://192.168.1.2:54321/autofps.mjpeg"
      label: Thumbnail URL
      name: thumbnailURL
      required: true
      type: TEXT
    - description: "example: http://192.168.1.2:54321/snapshots.mjpeg"
      label: Stream URL
      name: streamURL
      required: true
      type: TEXT
timestamp: Dec 14, 2020, 9:59:29 PM
component: oh-image-card
config:
  class:
    - oh-cell
    - no-margin
  style:
    - border-radius: 6px
    - width: 100%
    - height: auto
  url: =props.thumbnailURL
  action: photos
  actionPhotos: =[props.streamURL]
  actionPhotoBrowserConfig:
    theme: dark
    type: popup
4 Likes

This might even be a good candidate for a oh-swiper-card with several oh-image-card as slides.
It’s better to have all images of the same dimensions in this case, or it will look weird.

component: oh-swiper-card
config:
  pagination: true
  navigation: true
  params:
    slidesPerView: 1.5
slots:
  default:
    - component: oh-image-card
      config:
        url: https://source.unsplash.com/800x600/?frontdoor
        action: photos
        actionPhotos:
          - https://source.unsplash.com/800x600/?frontdoor
    - component: oh-image-card
      config:
        url: https://source.unsplash.com/800x600/?garage
    - component: oh-image-card
      config:
        url: https://source.unsplash.com/800x600/?garden

You can also have some fun with the SwiperJS parameters like the coverflow effect -(https://swiperjs.com/api/#coverflow-effect):

component: oh-swiper-card
config:
  pagination: true
  navigation: true
  params:
    effect: coverflow
    coverFlowEffect:
      rotate: 30
      slideShadows: false

coverflow

or the cube:

component: oh-swiper-card
config:
  pagination: true
  navigation: true
  params:
    effect: cube
    cubeEffect:
      shadow: false

cube

3 Likes

Nice Effects. Is there any way to remove the frame of the image (the space between wrapper and container I guess) ?

I have not tried but my guess is it should be possible if you use CSS to change the background color. To do this see the examples above at how CSS is used after style:
If the background color matches then you don’t see it.

Right and add noBorder & noShadow too (properties of all cards).

config:
  noBorder: true
  noShadow: true
  style:
    --f7-card-bg-color: transparent

Trying to get mjpeg video streams from cameras working in Main UI…

In OH2 sitemap I used:

Video url="http://user:pwd@192.168.0.15:80/video2.mjpg" encoding="mjpeg"

I can’t get it working in Main UI.
Error message: No compatible source was found for this media.

I tried the Video card as follows…

URL:
http://user:pwd@192.168.0.15:80/video2.mjpg

Type:
mjpg mjpeg video/mjpeg video/mjpg (none of these options worked)

Is there a way to get mjpeg stream working in Main UI?

I had luck using the “Image” card with a motion jpeg.

Which video settings does your motion jpeg stream have?

  • resolution
  • frames per second

Yes if you use the ipcamera binding and the widget in this post it should work. I can only guess that there may be an issue with embedded credentials not being supported. It was removed from the http spec due to security concerns and more and more software is removing it so hence the binding is needed.

Now it works for me too with the Image card - I had to go with the IPcamera binding though.