diff --git a/platypush/backend/http/app/routes/plugins/camera/pi.py b/platypush/backend/http/app/routes/plugins/camera/pi.py
index 52c29bc9..719c1cb7 100644
--- a/platypush/backend/http/app/routes/plugins/camera/pi.py
+++ b/platypush/backend/http/app/routes/plugins/camera/pi.py
@@ -2,9 +2,12 @@ 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
+from platypush.config import Config
+from platypush.plugins.camera.pi import CameraPiPlugin
 
 camera_pi = Blueprint('camera.pi', __name__, template_folder=template_folder)
 filename = os.path.join(tempfile.gettempdir(), 'camera_pi.jpg')
@@ -14,22 +17,34 @@ __routes__ = [
     camera_pi,
 ]
 
+_camera: Optional[CameraPiPlugin] = None
 
-def get_frame_file(*args, **kwargs):
-    response = send_request(*args, action='camera.pi.take_picture', image_file=filename, **kwargs)
-    assert response.output and 'image_file' in response.output,\
-        (response.errors[0] if response.errors else 'Unable to capture frame from the picamera')
 
-    return response.output['image_file']
+def get_camera() -> CameraPiPlugin:
+    global _camera
+
+    # noinspection PyProtectedMember
+    if _camera and _camera._camera and not _camera._camera.closed:
+        return _camera
+
+    camera_conf = Config.get('camera.pi') or {}
+    _camera = CameraPiPlugin(**camera_conf)
+    return _camera
+
+
+def get_frame():
+    camera = get_camera()
+    output = camera.get_output_stream()
+
+    with output.ready:
+        output.ready.wait()
+        return output.frame
 
 
 def video_feed():
     try:
         while True:
-            frame_file = get_frame_file(warmup_time=0, close=False)
-            with open(frame_file, 'rb') as f:
-                frame = f.read()
-
+            frame = get_frame()
             yield (b'--frame\r\n'
                    b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
     finally:
@@ -38,8 +53,11 @@ def video_feed():
 
 @camera_pi.route('/camera/pi/frame', methods=['GET'])
 @authenticate()
-def get_frame():
-    frame_file = get_frame_file()
+def get_frame_img():
+    response = send_request('camera.pi.take_picture', image_file=filename)
+    frame_file = (response.output or {}).get('image_file')
+    assert frame_file is not None
+
     return send_from_directory(os.path.dirname(frame_file),
                                os.path.basename(frame_file))
 
@@ -47,8 +65,16 @@ def get_frame():
 @camera_pi.route('/camera/pi/stream', methods=['GET'])
 @authenticate()
 def get_stream_feed():
-    return Response(video_feed(),
-                    mimetype='multipart/x-mixed-replace; boundary=frame')
+    global _camera
+
+    try:
+        return Response(video_feed(),
+                        headers={'Cache-Control': 'no-cache, private', 'Pragma': 'no-cache', 'Age': 0},
+                        mimetype='multipart/x-mixed-replace; boundary=frame')
+    finally:
+        if _camera:
+            _camera.close_output_stream()
+            _camera = None
 
 
 # vim:sw=4:ts=4:et:
diff --git a/platypush/plugins/camera/__init__.py b/platypush/plugins/camera/__init__.py
index 5e666c3b..d00afa66 100644
--- a/platypush/plugins/camera/__init__.py
+++ b/platypush/plugins/camera/__init__.py
@@ -1,3 +1,4 @@
+import io
 import os
 import re
 import shutil
@@ -15,6 +16,24 @@ from platypush.message.event.camera import CameraRecordingStartedEvent, \
 from platypush.plugins import Plugin, action
 
 
+class StreamingOutput:
+    def __init__(self):
+        self.frame = None
+        self.buffer = io.BytesIO()
+        self.ready = threading.Condition()
+
+    def write(self, buf):
+        if buf.startswith(b'\xff\xd8'):
+            # New frame, copy the existing buffer's content and notify all clients that it's available
+            self.buffer.truncate()
+            with self.ready:
+                self.frame = self.buffer.getvalue()
+                self.ready.notify_all()
+            self.buffer.seek(0)
+
+        return self.buffer.write(buf)
+
+
 class CameraPlugin(Plugin):
     """
     Plugin to control generic cameras over OpenCV.
diff --git a/platypush/plugins/camera/pi.py b/platypush/plugins/camera/pi.py
index 591690aa..b2410fac 100644
--- a/platypush/plugins/camera/pi.py
+++ b/platypush/plugins/camera/pi.py
@@ -7,10 +7,10 @@ import socket
 import threading
 import time
 
-from typing import Optional
+from typing import Optional, Union
 
 from platypush.plugins import action
-from platypush.plugins.camera import CameraPlugin
+from platypush.plugins.camera import CameraPlugin, StreamingOutput
 
 
 class CameraPiPlugin(CameraPlugin):
@@ -67,6 +67,7 @@ class CameraPiPlugin(CameraPlugin):
         self._time_lapse_stop_condition = threading.Condition()
         self._recording_stop_condition = threading.Condition()
         self._streaming_stop_condition = threading.Condition()
+        self._output: StreamingOutput = None
 
     # noinspection PyUnresolvedReferences,PyPackageRequirements
     def _get_camera(self, **opts):
@@ -173,6 +174,26 @@ class CameraPiPlugin(CameraPlugin):
             if camera and close:
                 self.close()
 
+    def get_output_stream(self, resize: Union[tuple, list] = None, **opts) -> StreamingOutput:
+        camera = self._get_camera(**opts)
+
+        if self._output and not camera.closed:
+            return self._output
+
+        capture_opts = {}
+        if resize:
+            capture_opts['resize'] = tuple(resize)
+
+        self._output = StreamingOutput()
+        camera.start_recording(self._output, format='mjpeg', **capture_opts)
+        return self._output
+
+    def close_output_stream(self):
+        if self._camera and not self._camera.closed:
+            self._camera.stop_recording()
+
+        self._output = None
+
     @action
     def capture_sequence(self, n_images, directory, name_format='image_%04d.jpg', preview=False, warmup_time=2,
                          resize=None, **opts):