Added camera.android.ipcam web panel

This commit is contained in:
Fabio Manganiello 2019-12-18 01:00:56 +01:00
parent 416c9ceb93
commit 93a3c72d4e
7 changed files with 192 additions and 28 deletions

View file

@ -2,7 +2,6 @@ import os
import tempfile import tempfile
from flask import Response, Blueprint, send_from_directory 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 import template_folder
from platypush.backend.http.app.utils import authenticate, send_request from platypush.backend.http.app.utils import authenticate, send_request
@ -18,7 +17,6 @@ __routes__ = [
def video_feed(): def video_feed():
camera: Optional[CameraPiPlugin] = None
camera_conf = Config.get('camera.pi') or {} camera_conf = Config.get('camera.pi') or {}
camera = CameraPiPlugin(**camera_conf) camera = CameraPiPlugin(**camera_conf)

View file

@ -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();
},
});

View file

@ -1,6 +1,7 @@
{% {%
with pluginIcons = { with pluginIcons = {
'camera': 'fas fa-camera', 'camera': 'fas fa-camera',
'camera.android.ipcam': 'fab fa-android',
'camera.pi': 'fab fa-raspberry-pi', 'camera.pi': 'fab fa-raspberry-pi',
'camera.ir.mlx90640': 'fas fa-sun', 'camera.ir.mlx90640': 'fas fa-sun',
'execute': 'fas fa-play', 'execute': 'fas fa-play',

View file

@ -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>&nbsp; Start streaming video
</button>
<button type="button" @click="stopStreaming" :disabled="capturing || loading" v-else>
<i class="fa fa-stop"></i>&nbsp; Stop streaming video
</button>
<button type="button" @click="capture" :disabled="streaming || capturing || loading">
<i class="fas fa-camera"></i>&nbsp; Take snapshot
</button>
</div>
<div class="controls">
<button type="button" @click="recording = true" v-if="!recording" :disabled="loading">
<i class="fa fa-play"></i>&nbsp; Start streaming audio
</button>
<button type="button" @click="recording = false" v-else :disabled="loading">
<i class="fa fa-stop"></i>&nbsp; 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>

View file

@ -10,6 +10,9 @@ class AndroidCameraStatusResponse(CameraResponse):
..code-block:: json ..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", "orientation": "landscape",
"idle": "off", "idle": "off",
"audio_only": "off", "audio_only": "off",
@ -71,6 +74,10 @@ class AndroidCameraStatusResponse(CameraResponse):
} }
def __init__(self, *args, def __init__(self, *args,
name: str = None,
stream_url: str = None,
image_url: str = None,
audio_url: str = None,
orientation: str = None, orientation: str = None,
idle: str = None, idle: str = None,
audio_only: str = None, audio_only: str = None,
@ -117,6 +124,10 @@ class AndroidCameraStatusResponse(CameraResponse):
**kwargs): **kwargs):
self.status = { self.status = {
"name": name,
"stream_url": stream_url,
"image_url": image_url,
"audio_url": audio_url,
"orientation": orientation, "orientation": orientation,
"idle": True if idle == "on" else False, "idle": True if idle == "on" else False,
"audio_only": True if audio_only == "on" else False, "audio_only": True if audio_only == "on" else False,

View file

@ -148,19 +148,29 @@ class CameraAndroidIpcamPlugin(Plugin):
:return: True if the camera is available, False otherwise :return: True if the camera is available, False otherwise
""" """
cameras = self._camera_name_to_idx.keys() if camera is None else [camera] cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
status = [] statuses = []
for cam in cameras: for c in cameras:
try: try:
status_data = self._exec('status.json', params={'show_avail': 1}, camera=cam).get('curvals', {}) if isinstance(camera, int):
status.append(AndroidCameraStatusResponse( 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() **{k: v for k, v in status_data.items()
if k in AndroidCameraStatusResponse.attrs}) 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 @action
def set_front_facing_camera(self, activate: bool = True, camera: Union[int, str] = None) -> bool: 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.""" """Set video orientation."""
return self.change_setting('scenemode', scenemode, camera=camera) 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: # vim:sw=4:ts=4:et: