]> www.vanbest.org Git - motioneye-debian/commitdiff
added camera frame (full screen) support
authorCalin Crisan <ccrisan@gmail.com>
Sat, 28 Jun 2014 15:42:37 +0000 (18:42 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sat, 28 Jun 2014 15:42:37 +0000 (18:42 +0300)
doc/todo.txt
src/handlers.py
src/server.py
static/css/frame.css [new file with mode: 0644]
static/js/frame.js [new file with mode: 0644]
static/js/main.js
templates/frame.html [new file with mode: 0644]

index 0c770cf41dc07858b3ec8127dd149b93529c5b47..2de429fe217e83cf2e09d3cfd155a717d3c9fc33 100644 (file)
@@ -1,4 +1,3 @@
 -> in continuare o camera remote moarta blocheaza intreg sistemul la load
 -> use minimum_frame_time
 -> implement custom camera frame sizes
--> add a separate full-screen camera link
index d391ee5beb5b3df193fe608f5c6b9b1a4c3535f3..8e35d790ccfa0c75bbe3f55d78246a68ad7c80c5 100644 (file)
@@ -563,6 +563,9 @@ class PictureHandler(BaseHandler):
         elif op == 'list':
             self.list(camera_id)
             
+        elif op == 'frame':
+            self.frame(camera_id)
+            
         elif op == 'download':
             self.download(camera_id, filename)
         
@@ -639,6 +642,14 @@ class PictureHandler(BaseHandler):
             mediafiles.list_media(camera_config, media_type='picture',
                     callback=on_media_list, prefix=self.get_argument('prefix', None))
 
+    @BaseHandler.auth()
+    def frame(self, camera_id):
+        camera_config = config.get_camera(camera_id)
+        
+        self.render('frame.html',
+                camera_id=camera_id,
+                camera_config=camera_config)
+
     @BaseHandler.auth()
     def download(self, camera_id, filename):
         logging.debug('downloading picture %(filename)s of camera %(id)s' % {
index 3d6064ec23753bc3ba75be666478688b267424c0..181b45e77e52578af8c404e82360b784a19ee599 100644 (file)
@@ -44,7 +44,7 @@ application = Application(
         (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
         (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
         (r'^/config/(?P<op>add|list|list_devices)/?$', handlers.ConfigHandler),
-        (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list)/?$', handlers.PictureHandler),
+        (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
         (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+)/?$', handlers.PictureHandler),
         (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
         (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+)/?$', handlers.MovieHandler),
diff --git a/static/css/frame.css b/static/css/frame.css
new file mode 100644 (file)
index 0000000..92ac8f9
--- /dev/null
@@ -0,0 +1,109 @@
+
+
+    /* basic */
+
+* {
+    padding: 0px;
+    border: 0px solid black;
+    margin: 0px;
+    outline: 0px;
+    border-spacing: 0px;
+    border-collapse: separate;
+}
+
+html {
+    height: 100%;
+}
+
+body {
+    height: 100%;
+    color: #dddddd;
+    background-color: #212121;
+}
+
+
+    /* layout */
+
+img.main-loading-progress {
+    display: block;
+    margin: auto;
+    margin-top: 50px;
+}
+
+div.add-camera-message {
+    text-align: center;
+    margin-top: 30px;
+}
+
+
+    /* camera frame */
+
+div.camera-frame {
+    position: relative;
+    width: 100%;
+    height: 100%;
+    transition: all 0.2s, opacity 0s;
+    opacity: 0;
+    vertical-align: top;
+}
+
+div.camera-container {
+    height: 100%;
+}
+
+img.camera {
+    width: 100%;
+    height: auto;
+    display: block;
+    transition: opacity 0.2s linear;
+    opacity: 1;
+}
+
+img.camera.error,
+img.camera.loading {
+    opacity: 0;
+    height: 100%;
+}
+
+div.camera-placeholder {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    bottom: 0px;
+    left: 0px;
+    text-align: center;
+    transition: opacity 0.2s linear;
+}
+
+img.no-camera {
+    margin-top: 20%;
+    width: 30%;
+    opacity: 0.8;
+}
+
+div.camera-progress {
+    background: rgba(0, 0, 0, 0.001); /* otherwise IE would not extend this as expected */
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    bottom: 0px;
+    left: 0px;
+    opacity: 0;
+    transition: all 0.2s linear;
+    text-align: center;
+}
+
+div.camera-progress.visible {
+    opacity: 0.4;
+}
+
+img.camera-progress {
+    border: 10px solid white;
+    border-radius: 10px;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    bottom: 0px;
+    right: 0px;
+    margin: auto;
+}
diff --git a/static/js/frame.js b/static/js/frame.js
new file mode 100644 (file)
index 0000000..af4997b
--- /dev/null
@@ -0,0 +1,154 @@
+
+var refreshDisabled = false;
+var inProgress = false;
+var refreshInterval = 50; /* milliseconds */
+
+    
+    /* progress */
+
+function beginProgress() {
+    if (inProgress) {
+        return; /* already in progress */
+    }
+
+    inProgress = true;
+    
+    /* show the camera progress indicator */
+    $('div.camera-progress').addClass('visible');
+}
+
+function endProgress() {
+    if (!inProgress) {
+        return; /* not in progress */
+    }
+    
+    inProgress = false;
+    
+    /* hide the camera progress indicator */
+    $('div.camera-progress').removeClass('visible');
+}
+
+
+    /* camera frame */
+
+function setupCameraFrame() {
+    var cameraFrameDiv = $('div.camera-frame')
+    var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
+    var cameraProgress = cameraFrameDiv.find('div.camera-progress');
+    var cameraImg = cameraFrameDiv.find('img.camera');
+    var progressImg = cameraFrameDiv.find('img.camera-progress');
+    
+    cameraFrameDiv[0].refreshDivider = 0;
+    cameraFrameDiv[0].streamingFramerate = parseInt(cameraFrameDiv.attr('streaming_framerate')) || 1;
+    cameraFrameDiv[0].streamingServerResize = cameraFrameDiv.attr('streaming_server_resize') == 'True';
+    progressImg.attr('src', staticUrl + 'img/camera-progress.gif');
+    
+    cameraProgress.addClass('visible');
+    cameraPlaceholder.css('opacity', '0');
+    
+    /* fade in */
+    cameraFrameDiv.animate({'opacity': 1}, 100);
+    
+    /* error and load handlers */
+    cameraImg.error(function () {
+        this.error = true;
+        this.loading = 0;
+        
+        cameraImg.addClass('error').removeClass('loading');
+        cameraPlaceholder.css('opacity', 1);
+        cameraProgress.removeClass('visible');
+    });
+    cameraImg.load(function () {
+        if (refreshDisabled) {
+            return; /* refresh temporarily disabled for updating */
+        }
+        
+        this.error = false;
+        this.loading = 0;
+        
+        cameraImg.removeClass('error').removeClass('loading');
+        cameraImg.css('height', '');
+        cameraPlaceholder.css('opacity', 0);
+        cameraProgress.removeClass('visible');
+    });
+    
+    cameraImg.addClass('loading');
+}
+
+function refreshCameraFrame() {
+    var $cameraFrame = $('div.camera-frame');
+    var cameraFrame = $cameraFrame[0];
+    var img = $cameraFrame.find('img.camera')[0];
+    var cameraId = cameraFrame.id.substring(6);
+    
+    /* limit the refresh rate to 20 fps */
+    var count = Math.max(1, 1 / cameraFrame.streamingFramerate * 1000 / refreshInterval);
+    
+    if (img.error) {
+        /* in case of error, decrease the refresh rate to 1 fps */
+        count = 1000 / refreshInterval;
+    }
+    
+    if (cameraFrame.refreshDivider < count) {
+        cameraFrame.refreshDivider++;
+    }
+    else {
+        (function () {
+            if (refreshDisabled) {
+                /* camera refreshing disabled, retry later */
+                
+                return;
+            }
+            
+            if (img.loading) {
+                img.loading++; /* increases each time the camera would refresh but is still loading */
+                
+                if (img.loading > 2 * 1000 / refreshInterval) { /* limits the retry at one every two seconds */
+                    img.loading = 0;
+                }
+                else {
+                    return; /* wait for the previous frame to finish loading */
+                }
+            }
+            
+            var timestamp = Math.round(new Date().getTime());
+            
+            var uri = '/picture/' + cameraId + '/current/?seq=' + timestamp;
+            if (cameraFrame.serverSideResize) {
+                uri += '&width=' + img.width;
+            }
+            
+            img.src = uri;
+            img.loading = 1;
+            
+            cameraFrame.refreshDivider = 0;
+        })();
+    }
+
+    setTimeout(refreshCameraFrame, refreshInterval);
+}
+
+function checkCameraErrors() {
+    /* properly triggers the onerror event on the cameras whose imgs were not successfully loaded,
+     * but the onerror event hasn't been triggered, for some reason (seems to happen in Chrome) */
+    var cameraFrame = $('div.camera-frame').find('img.camera');
+    
+    cameraFrame.each(function () {
+        if (this.complete === true && this.naturalWidth === 0 && !this.error && this.src) {
+            $(this).error();
+        }
+    });
+    
+    setTimeout(checkCameraErrors, 500);
+}
+
+
+    /* startup function */
+
+$(document).ready(function () {
+    beginProgress();
+    setupCameraFrame();
+    refreshCameraFrame();
+    checkCameraErrors();
+});
+
index b0fe343e6884f50f7838e7d642d5da72944495f1..820a9e37e6aa9c39e9e1a6600eaa2297b3ed3bdc 100644 (file)
@@ -1958,7 +1958,7 @@ function doFullScreenCamera(cameraId) {
         return; /* a camera is already in full screen */
     }
     
-    fullScreenCameraId = -1; /* aviods successive fast toggles of fullscreen */
+    fullScreenCameraId = -1; /* avoids successive fast toggles of fullscreen */
     
     var cameraFrameDiv = $('#camera' + cameraId);
     var cameraName = cameraFrameDiv.find('span.camera-name').text();
diff --git a/templates/frame.html b/templates/frame.html
new file mode 100644 (file)
index 0000000..6b3635a
--- /dev/null
@@ -0,0 +1,23 @@
+{% extends "base.html" %}
+
+{% block style %}
+    {{super()}}
+    <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/frame.css" />
+{% endblock %}
+
+{% block script %}
+    {{super()}}
+    <script type="text/javascript" src="{{STATIC_URL}}js/frame.js"></script>
+{% endblock %}
+
+{% block body %}
+    <div class="camera-frame" id="camera{{camera_id}}"
+            streaming_framerate="{{camera_config['stream_maxrate']}}" streaming_server_resize="{{camera_config['@webcam_server_resize']}}">
+        
+        <div class="camera-container">
+            <div class="camera-placeholder"><img class="no-camera" src="{{STATIC_URL}}img/no-camera.svg"></div>
+            <img class="camera">
+            <div class="camera-progress"><img class="camera-progress"></div>
+        </div>
+    </div>
+{% endblock %}