Added camera.android.ipcam web panel
This commit is contained in:
parent
416c9ceb93
commit
93a3c72d4e
7 changed files with 192 additions and 28 deletions
|
@ -2,7 +2,6 @@ import os
|
|||
import tempfile
|
||||
|
||||
from flask import Response, Blueprint, send_from_directory
|
||||
from typing import Optional
|
||||
|
||||
from platypush.backend.http.app import template_folder
|
||||
from platypush.backend.http.app.utils import authenticate, send_request
|
||||
|
@ -18,7 +17,6 @@ __routes__ = [
|
|||
|
||||
|
||||
def video_feed():
|
||||
camera: Optional[CameraPiPlugin] = None
|
||||
camera_conf = Config.get('camera.pi') or {}
|
||||
camera = CameraPiPlugin(**camera_conf)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
camera
|
|
@ -0,0 +1,108 @@
|
|||
Vue.component('camera-android-ipcam', {
|
||||
template: '#tmpl-camera-android-ipcam',
|
||||
props: ['config'],
|
||||
|
||||
data: function() {
|
||||
return {
|
||||
bus: new Vue({}),
|
||||
loading: false,
|
||||
streaming: false,
|
||||
capturing: false,
|
||||
recording: false,
|
||||
cameras: {},
|
||||
selectedCamera: undefined,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasMultipleCameras: function () {
|
||||
return Object.keys(this.cameras).length > 1;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
startStreaming: function() {
|
||||
if (this.streaming)
|
||||
return;
|
||||
|
||||
const cam = this.cameras[this.selectedCamera];
|
||||
this.streaming = true;
|
||||
this.capturing = false;
|
||||
this.$refs.frame.setAttribute('src', cam.stream_url);
|
||||
},
|
||||
|
||||
stopStreaming: function() {
|
||||
if (!this.streaming)
|
||||
return;
|
||||
|
||||
this.streaming = false;
|
||||
this.capturing = false;
|
||||
this.$refs.frame.removeAttribute('src');
|
||||
},
|
||||
|
||||
capture: function() {
|
||||
if (this.capturing)
|
||||
return;
|
||||
|
||||
const cam = this.cameras[this.selectedCamera];
|
||||
this.streaming = false;
|
||||
this.capturing = true;
|
||||
this.$refs.frame.setAttribute('src', cam.image_url + '?t=' + (new Date()).getTime());
|
||||
},
|
||||
|
||||
onFrameLoaded: function(event) {
|
||||
if (this.capturing)
|
||||
this.capturing = false;
|
||||
},
|
||||
|
||||
onCameraSelected: function(event) {
|
||||
this.selectedCamera = event.target.value;
|
||||
},
|
||||
|
||||
flipCamera: async function() {
|
||||
const cam = this.cameras[this.selectedCamera];
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const value = !cam.ffc;
|
||||
await request('camera.android.ipcam.set_front_facing_camera', {
|
||||
activate: value, camera: cam.name
|
||||
});
|
||||
|
||||
this.cameras[this.selectedCamera].ffc = value;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
updateCameraStatus: async function() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const cameras = await request('camera.android.ipcam.status');
|
||||
this.cameras = cameras.reduce((cameras, cam) => {
|
||||
for (const attr of ['stream_url', 'image_url', 'audio_url']) {
|
||||
if (cam[attr].startsWith('https://')) {
|
||||
cam[attr] = cam[attr].replace('https://', 'http://');
|
||||
}
|
||||
}
|
||||
|
||||
cameras[cam.name] = cam;
|
||||
return cameras;
|
||||
}, {});
|
||||
|
||||
if (cameras.length)
|
||||
this.selectedCamera = cameras[0].name;
|
||||
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.$refs.frame.addEventListener('load', this.onFrameLoaded);
|
||||
this.updateCameraStatus();
|
||||
},
|
||||
});
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{%
|
||||
with pluginIcons = {
|
||||
'camera': 'fas fa-camera',
|
||||
'camera.android.ipcam': 'fab fa-android',
|
||||
'camera.pi': 'fab fa-raspberry-pi',
|
||||
'camera.ir.mlx90640': 'fas fa-sun',
|
||||
'execute': 'fas fa-play',
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<script type="text/x-template" id="tmpl-camera-android-ipcam">
|
||||
<div class="camera">
|
||||
<div class="camera-selector">
|
||||
<select ref="cameraSelector" @change="onCameraSelected" v-if="hasMultipleCameras">
|
||||
<option v-for="(cam, name) in cameras"
|
||||
:key="name" :value="name">
|
||||
{% raw %}{{ name }}{% endraw %}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="camera-container">
|
||||
<div class="no-frame" v-if="!streaming && !capturing">The camera is not active</div>
|
||||
<img class="frame" ref="frame">
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button type="button" @click="startStreaming" :disabled="capturing || loading" v-if="!streaming">
|
||||
<i class="fa fa-play"></i> Start streaming video
|
||||
</button>
|
||||
|
||||
<button type="button" @click="stopStreaming" :disabled="capturing || loading" v-else>
|
||||
<i class="fa fa-stop"></i> Stop streaming video
|
||||
</button>
|
||||
|
||||
<button type="button" @click="capture" :disabled="streaming || capturing || loading">
|
||||
<i class="fas fa-camera"></i> Take snapshot
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button type="button" @click="recording = true" v-if="!recording" :disabled="loading">
|
||||
<i class="fa fa-play"></i> Start streaming audio
|
||||
</button>
|
||||
|
||||
<button type="button" @click="recording = false" v-else :disabled="loading">
|
||||
<i class="fa fa-stop"></i> Stop streaming audio
|
||||
</button>
|
||||
|
||||
<button type="button" @click="flipCamera" :disabled="loading">
|
||||
<i class="fas fa-retweet"></i> Flip camera
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sound-container">
|
||||
<audio autoplay preload="none" ref="player" v-if="recording">
|
||||
<source :src="cameras[selectedCamera].audio_url" type="audio/x-wav;codec=pcm">
|
||||
Your browser does not support audio elements
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
|
@ -10,6 +10,9 @@ class AndroidCameraStatusResponse(CameraResponse):
|
|||
..code-block:: json
|
||||
|
||||
{
|
||||
"stream_url": "https://192.168.1.30:8080/video",
|
||||
"image_url": "https://192.168.1.30:8080/photo.jpg",
|
||||
"audio_url": "https://192.168.1.30:8080/audio.wav",
|
||||
"orientation": "landscape",
|
||||
"idle": "off",
|
||||
"audio_only": "off",
|
||||
|
@ -71,6 +74,10 @@ class AndroidCameraStatusResponse(CameraResponse):
|
|||
}
|
||||
|
||||
def __init__(self, *args,
|
||||
name: str = None,
|
||||
stream_url: str = None,
|
||||
image_url: str = None,
|
||||
audio_url: str = None,
|
||||
orientation: str = None,
|
||||
idle: str = None,
|
||||
audio_only: str = None,
|
||||
|
@ -117,6 +124,10 @@ class AndroidCameraStatusResponse(CameraResponse):
|
|||
**kwargs):
|
||||
|
||||
self.status = {
|
||||
"name": name,
|
||||
"stream_url": stream_url,
|
||||
"image_url": image_url,
|
||||
"audio_url": audio_url,
|
||||
"orientation": orientation,
|
||||
"idle": True if idle == "on" else False,
|
||||
"audio_only": True if audio_only == "on" else False,
|
||||
|
|
|
@ -148,19 +148,29 @@ class CameraAndroidIpcamPlugin(Plugin):
|
|||
:return: True if the camera is available, False otherwise
|
||||
"""
|
||||
cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
|
||||
status = []
|
||||
statuses = []
|
||||
|
||||
for cam in cameras:
|
||||
for c in cameras:
|
||||
try:
|
||||
status_data = self._exec('status.json', params={'show_avail': 1}, camera=cam).get('curvals', {})
|
||||
status.append(AndroidCameraStatusResponse(
|
||||
if isinstance(camera, int):
|
||||
cam = self.cameras[c]
|
||||
else:
|
||||
cam = self.cameras[self._camera_name_to_idx[c]]
|
||||
|
||||
status_data = self._exec('status.json', params={'show_avail': 1}, camera=cam.name).get('curvals', {})
|
||||
status = AndroidCameraStatusResponse(
|
||||
name=cam.name,
|
||||
stream_url=cam.stream_url,
|
||||
image_url=cam.image_url,
|
||||
audio_url=cam.audio_url,
|
||||
**{k: v for k, v in status_data.items()
|
||||
if k in AndroidCameraStatusResponse.attrs})
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not get the status of {}: {}'.format(cam, str(e)))
|
||||
|
||||
return AndroidCameraStatusListResponse(status)
|
||||
statuses.append(status)
|
||||
except Exception as e:
|
||||
self.logger.warning('Could not get the status of {}: {}'.format(c, str(e)))
|
||||
|
||||
return AndroidCameraStatusListResponse(statuses)
|
||||
|
||||
@action
|
||||
def set_front_facing_camera(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
||||
|
@ -246,23 +256,5 @@ class CameraAndroidIpcamPlugin(Plugin):
|
|||
"""Set video orientation."""
|
||||
return self.change_setting('scenemode', scenemode, camera=camera)
|
||||
|
||||
@action
|
||||
def get_stream_url(self, camera: Union[int, str] = None) -> str:
|
||||
""" Get the streaming URL for the specified camera. """
|
||||
cam = self._get_camera(camera)
|
||||
return cam.stream_url
|
||||
|
||||
@action
|
||||
def get_image_url(self, camera: Union[int, str] = None) -> str:
|
||||
""" Get the URL to the camera static picture. """
|
||||
cam = self._get_camera(camera)
|
||||
return cam.image_url
|
||||
|
||||
@action
|
||||
def get_audio_url(self, camera: Union[int, str] = None) -> str:
|
||||
""" Get the URL to the camera audio source. """
|
||||
cam = self._get_camera(camera)
|
||||
return cam.audio_url
|
||||
|
||||
|
||||
# vim:sw=4:ts=4:et:
|
||||
|
|
Loading…
Reference in a new issue