-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
142 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,49 @@ | ||
# picam-mjpeg | ||
# MJPEG stream from your raspberry pi over the internet | ||
|
||
#### With a little bit of Python, NodeJS, [ngrok](https://ngrok.com) and powerful [Node wrapper for ngrok](https://github.com/bubenshchykov/ngrok) | ||
|
||
I saw a lot of attempts to stream a video from a raspberry pi to your web application running on NodeJS. Most of them come up with non-idiomatic ideas eg sending base64 image strings over WebSockets to a web client. | ||
|
||
This is a simple example of how you can create mjpg stream, expose it to the web and use it in your web app. | ||
|
||
All of the source code is in `src` dir, assuming you have nodejs installed and camera set up on you rasperry pi. | ||
|
||
|
||
## Example: | ||
![mjpg raspberry nodejs video](./example.gif) | ||
|
||
|
||
## Usage: | ||
1. Copy src dir content | ||
2. `npm i` | ||
3. `npm start` | ||
|
||
|
||
## Explanation: | ||
1. First we'll create an mjpeg stream. I couldn't find proper nodejs solution for this, so we are using python (src/stream.py). | ||
2. Now all we need to do is to connect our js ngrok to the port at which our python stream runs (src/main.js) | ||
```js | ||
const url = await ngrok.connect(9090); | ||
``` | ||
Thats it, we have ngrok generated url string with our mjpg stream, use it however you want. | ||
|
||
|
||
## PS: | ||
You may want to password protect your video: | ||
|
||
```js | ||
const url = await ngrok.connect({ | ||
addr: 9090, | ||
auth: 'user:pwd', | ||
}); | ||
``` | ||
|
||
```html | ||
<img src="https://user:pwd@c038f6f8.ngrok.io" /> | ||
``` | ||
|
||
|
||
## Refs | ||
- [ngrok](https://ngrok.com) | ||
- [ngrok npm](https://github.com/bubenshchykov/ngrok) | ||
- [picamera web streaming](https://picamera.readthedocs.io/en/latest/recipes2.html#web-streaming) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
const ngrok = require('ngrok'); | ||
const {spawn} = require('child_process'); | ||
|
||
const stream = spawn('python3', ['./stream.py']); | ||
|
||
stream.stdout.once('data', async() => { | ||
const url = await ngrok.connect(9090); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"name": "mjpeg-video-stream", | ||
"version": "1.0.0", | ||
"description": "mjpeg video stream", | ||
"main": "main.js", | ||
"scripts": { | ||
"start": "node main.js" | ||
}, | ||
"author": "givehug", | ||
"license": "MIT", | ||
"dependencies": { | ||
"ngrok": "3.2.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import io | ||
import picamera | ||
import logging | ||
import socketserver | ||
from threading import Condition | ||
from http import server | ||
|
||
PORT = 9090 | ||
FRAME_RATE = 24 | ||
RESOLUTION = '640x480' | ||
|
||
class StreamingOutput(object): | ||
def __init__(self): | ||
self.frame = None | ||
self.buffer = io.BytesIO() | ||
self.condition = Condition() | ||
|
||
def write(self, buf): | ||
if buf.startswith(b'\xff\xd8'): | ||
# New frame, copy the existing buffer's content and notify all | ||
# clients it's available | ||
self.buffer.truncate() | ||
with self.condition: | ||
self.frame = self.buffer.getvalue() | ||
self.condition.notify_all() | ||
self.buffer.seek(0) | ||
return self.buffer.write(buf) | ||
|
||
class StreamingHandler(server.BaseHTTPRequestHandler): | ||
def do_GET(self): | ||
if self.path == '/': | ||
self.send_response(200) | ||
self.send_header('Age', 0) | ||
self.send_header('Cache-Control', 'no-cache, private') | ||
self.send_header('Pragma', 'no-cache') | ||
self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME') | ||
self.end_headers() | ||
try: | ||
while True: | ||
with output.condition: | ||
output.condition.wait() | ||
frame = output.frame | ||
self.wfile.write(b'--FRAME\r\n') | ||
self.send_header('Content-Type', 'image/jpeg') | ||
self.send_header('Content-Length', len(frame)) | ||
self.end_headers() | ||
self.wfile.write(frame) | ||
self.wfile.write(b'\r\n') | ||
except Exception as e: | ||
logging.warning( | ||
'Removed streaming client %s: %s', | ||
self.client_address, str(e)) | ||
else: | ||
self.send_error(404) | ||
self.end_headers() | ||
|
||
class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): | ||
allow_reuse_address = True | ||
daemon_threads = True | ||
|
||
with picamera.PiCamera(resolution=RESOLUTION, framerate=FRAME_RATE) as camera: | ||
output = StreamingOutput() | ||
#Uncomment the next line to change your Pi's Camera rotation (in degrees) | ||
#camera.rotation = 90 | ||
camera.start_recording(output, format='mjpeg') | ||
try: | ||
address = ('', PORT) | ||
server = StreamingServer(address, StreamingHandler) | ||
server.serve_forever() | ||
finally: | ||
camera.stop_recording() |