Add /video
to automatically give HLS to supported browser
This commit is contained in:
parent
5ee0bee59f
commit
5801152b03
@ -76,7 +76,9 @@ http_method_t http_methods[] = {
|
|||||||
{ "GET /stream?", http_stream },
|
{ "GET /stream?", http_stream },
|
||||||
{ "GET /?action=snapshot", http_snapshot },
|
{ "GET /?action=snapshot", http_snapshot },
|
||||||
{ "GET /?action=stream", http_stream },
|
{ "GET /?action=stream", http_stream },
|
||||||
{ "GET /video?", http_content, "text/html", html_video_html, 0, &html_video_html_len },
|
{ "GET /video?", http_detect_video },
|
||||||
|
{ "GET /video.html?", http_content, "text/html", html_video_html, 0, &html_video_html_len },
|
||||||
|
{ "GET /video.m3u8?", http_m3u8_video },
|
||||||
{ "GET /video.h264?", http_h264_video },
|
{ "GET /video.h264?", http_h264_video },
|
||||||
{ "GET /video.mkv?", http_mkv_video },
|
{ "GET /video.mkv?", http_mkv_video },
|
||||||
{ "GET /video.mp4?", http_mp4_video },
|
{ "GET /video.mp4?", http_mp4_video },
|
||||||
|
@ -108,15 +108,20 @@ ret:
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, buffer_write_fn fn, void *data)
|
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, unsigned timeout_ms, buffer_write_fn fn, void *data)
|
||||||
{
|
{
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
int frames = 0;
|
int frames = 0;
|
||||||
uint64_t deadline_ms = get_monotonic_time_us(NULL, NULL) + DEFAULT_BUFFER_LOCK_GET_TIMEOUT * 1000LL;
|
uint64_t deadline_ms = get_monotonic_time_us(NULL, NULL) + DEFAULT_BUFFER_LOCK_GET_TIMEOUT * 1000LL;
|
||||||
|
uint64_t frame_stop_ms = get_monotonic_time_us(NULL, NULL) + timeout_ms * 1000LL;
|
||||||
|
|
||||||
buffer_lock_use(buf_lock, 1);
|
buffer_lock_use(buf_lock, 1);
|
||||||
|
|
||||||
while (nframes == 0 || frames < nframes) {
|
while (nframes == 0 || frames < nframes) {
|
||||||
|
if (timeout_ms && frame_stop_ms < get_monotonic_time_us(NULL, NULL)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
buffer_t *buf = buffer_lock_get(buf_lock, 0, &counter);
|
buffer_t *buf = buffer_lock_get(buf_lock, 0, &counter);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -52,6 +52,6 @@ buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_ms, int *counter)
|
|||||||
bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock);
|
bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock);
|
||||||
void buffer_lock_use(buffer_lock_t *buf_lock, int ref);
|
void buffer_lock_use(buffer_lock_t *buf_lock, int ref);
|
||||||
bool buffer_lock_is_used(buffer_lock_t *buf_lock);
|
bool buffer_lock_is_used(buffer_lock_t *buf_lock);
|
||||||
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, buffer_write_fn fn, void *data);
|
int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, unsigned timeout_ms, buffer_write_fn fn, void *data);
|
||||||
bool buffer_lock_register_check_streaming(buffer_lock_t *buf_lock, buffer_lock_check_streaming check_streaming);
|
bool buffer_lock_register_check_streaming(buffer_lock_t *buf_lock, buffer_lock_check_streaming check_streaming);
|
||||||
bool buffer_lock_register_notify_buffer(buffer_lock_t *buf_lock, buffer_lock_notify_buffer notify_buffer);
|
bool buffer_lock_register_notify_buffer(buffer_lock_t *buf_lock, buffer_lock_notify_buffer notify_buffer);
|
||||||
|
@ -40,14 +40,18 @@
|
|||||||
<br>
|
<br>
|
||||||
<li>
|
<li>
|
||||||
<a href="video"><b>/video</b></a><br>
|
<a href="video"><b>/video</b></a><br>
|
||||||
Get a live WebRTC (H264) stream.<br>
|
Get a live (H264) video stream best suited to current browser in a maximum compatibility mode choosing automatically between one of the below formats.<br>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="video.mp4"><b>/video.mp4</b></a><br> get a live video stream in MP4 format (if FFMPEG enabled).</li>
|
<li><a href="video.html"><b>/video.html</b></a><br> get a live video using WebRTC (low-latency streaming with latency of around 100ms).</li>
|
||||||
<br>
|
<br>
|
||||||
<li><a href="video.mkv"><b>/video.mkv</b></a><br> get a live video stream in MKV format (if FFMPEG enabled).</li>
|
<li><a href="video.mp4"><b>/video.mp4</b></a><br> get a live video stream in MP4 format (Firefox, with latency of around 1s if FFMPEG enabled).</li>
|
||||||
<br>
|
<br>
|
||||||
<li><a href="video?res=low">/video?res=low</a> get a low resolution stream (if -camera-low_res_factor=X is configured).</li>
|
<li><a href="video.mkv"><b>/video.mkv</b></a><br> get a live video stream in MKV format (Chrome, with latency of around 2s if FFMPEG enabled).</li>
|
||||||
|
<br>
|
||||||
|
<li><a href="video.m3u8"><b>/video.m3u8</b></a><br> get a live video stream in HLS format (Safari, with latency of around 1s).</li>
|
||||||
|
<br>
|
||||||
|
<li><a href="video.html?res=low">/video.html?res=low</a> get a low resolution stream (if -camera-low_res_factor=X is configured).</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
|
@ -137,6 +137,7 @@ static void http_ffmpeg_video(http_worker_t *worker, FILE *stream, const char *c
|
|||||||
int n = buffer_lock_write_loop(
|
int n = buffer_lock_write_loop(
|
||||||
http_h264_buffer_for_res(worker),
|
http_h264_buffer_for_res(worker),
|
||||||
0,
|
0,
|
||||||
|
0,
|
||||||
(buffer_write_fn)http_ffmpeg_video_buf_part,
|
(buffer_write_fn)http_ffmpeg_video_buf_part,
|
||||||
&status);
|
&status);
|
||||||
ffmpeg_remuxer_close(&remuxer);
|
ffmpeg_remuxer_close(&remuxer);
|
||||||
|
@ -81,7 +81,7 @@ void http_h264_video(http_worker_t *worker, FILE *stream)
|
|||||||
{
|
{
|
||||||
http_video_status_t status = { stream };
|
http_video_status_t status = { stream };
|
||||||
|
|
||||||
int n = buffer_lock_write_loop(http_h264_buffer_for_res(worker), 0, (buffer_write_fn)http_video_buf_part, &status);
|
int n = buffer_lock_write_loop(http_h264_buffer_for_res(worker), 0, 0, (buffer_write_fn)http_video_buf_part, &status);
|
||||||
|
|
||||||
if (status.wrote_header) {
|
if (status.wrote_header) {
|
||||||
return;
|
return;
|
||||||
|
46
output/http_hls.c
Normal file
46
output/http_hls.c
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "output.h"
|
||||||
|
#include "util/opts/log.h"
|
||||||
|
#include "util/http/http.h"
|
||||||
|
#include "device/buffer.h"
|
||||||
|
#include "device/buffer_lock.h"
|
||||||
|
#include "device/buffer_list.h"
|
||||||
|
#include "device/device.h"
|
||||||
|
|
||||||
|
static const char *const CONTENT_TYPE = "application/x-mpegURL";
|
||||||
|
|
||||||
|
static const char *const STREAM_M3U8 =
|
||||||
|
"#EXTM3U\r\n" \
|
||||||
|
"#EXT-X-TARGETDURATION:1\r\n" \
|
||||||
|
"#EXT-X-VERSION:4\r\n" \
|
||||||
|
"#EXTINF:1.0,\r\n" \
|
||||||
|
"video.mp4?ts=%zu\r\n";
|
||||||
|
|
||||||
|
static const char *const LOCATION_REDIRECT =
|
||||||
|
"HTTP/1.0 307 Temporary Redirect\r\n"
|
||||||
|
"Access-Control-Allow-Origin: *\r\n"
|
||||||
|
"Connection: close\r\n"
|
||||||
|
"Location: %s\r\n"
|
||||||
|
"\r\n";
|
||||||
|
|
||||||
|
void http_m3u8_video(struct http_worker_s *worker, FILE *stream)
|
||||||
|
{
|
||||||
|
uint64_t ts = get_monotonic_time_us(NULL, NULL) / 1000 / 1000;
|
||||||
|
http_write_responsef(stream, "200 OK", CONTENT_TYPE, STREAM_M3U8, ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_detect_video(struct http_worker_s *worker, FILE *stream)
|
||||||
|
{
|
||||||
|
if (strstr(worker->user_agent, "Safari/") && !strstr(worker->user_agent, "Chrome/") && !strstr(worker->user_agent, "Chromium/")) {
|
||||||
|
// Safari only supports m3u8
|
||||||
|
fprintf(stream, LOCATION_REDIRECT, "video.m3u8");
|
||||||
|
} else if (strstr(worker->user_agent, "Firefox/")) {
|
||||||
|
// Firefox only supports mp4
|
||||||
|
fprintf(stream, LOCATION_REDIRECT, "video.mp4");
|
||||||
|
} else {
|
||||||
|
// Chrome offers best latency with mkv
|
||||||
|
fprintf(stream, LOCATION_REDIRECT, "video.mkv");
|
||||||
|
}
|
||||||
|
}
|
@ -40,7 +40,7 @@ int http_snapshot_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, FI
|
|||||||
|
|
||||||
void http_snapshot(http_worker_t *worker, FILE *stream)
|
void http_snapshot(http_worker_t *worker, FILE *stream)
|
||||||
{
|
{
|
||||||
int n = buffer_lock_write_loop(http_jpeg_buffer_for_res(worker), 1, (buffer_write_fn)http_snapshot_buf_part, stream);
|
int n = buffer_lock_write_loop(http_jpeg_buffer_for_res(worker), 1, 0, (buffer_write_fn)http_snapshot_buf_part, stream);
|
||||||
|
|
||||||
if (n <= 0) {
|
if (n <= 0) {
|
||||||
http_500(stream, NULL);
|
http_500(stream, NULL);
|
||||||
@ -68,7 +68,7 @@ int http_stream_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, FILE
|
|||||||
|
|
||||||
void http_stream(http_worker_t *worker, FILE *stream)
|
void http_stream(http_worker_t *worker, FILE *stream)
|
||||||
{
|
{
|
||||||
int n = buffer_lock_write_loop(http_jpeg_buffer_for_res(worker), 0, (buffer_write_fn)http_stream_buf_part, stream);
|
int n = buffer_lock_write_loop(http_jpeg_buffer_for_res(worker), 0, 0, (buffer_write_fn)http_stream_buf_part, stream);
|
||||||
|
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
http_500(stream, NULL);
|
http_500(stream, NULL);
|
||||||
|
@ -23,4 +23,8 @@ void http_mkv_video(struct http_worker_s *worker, FILE *stream);
|
|||||||
void http_mp4_video(struct http_worker_s *worker, FILE *stream);
|
void http_mp4_video(struct http_worker_s *worker, FILE *stream);
|
||||||
void http_mov_video(struct http_worker_s *worker, FILE *stream);
|
void http_mov_video(struct http_worker_s *worker, FILE *stream);
|
||||||
|
|
||||||
|
// HLS
|
||||||
|
void http_m3u8_video(struct http_worker_s *worker, FILE *stream);
|
||||||
|
void http_detect_video(struct http_worker_s *worker, FILE *stream);
|
||||||
|
|
||||||
#define HTTP_LOW_RES_PARAM "res=low"
|
#define HTTP_LOW_RES_PARAM "res=low"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#define HEADER_RANGE "Range:"
|
#define HEADER_RANGE "Range:"
|
||||||
#define HEADER_CONTENT_LENGTH "Content-Length:"
|
#define HEADER_CONTENT_LENGTH "Content-Length:"
|
||||||
|
#define HEADER_USER_AGENT "User-Agent:"
|
||||||
|
|
||||||
static int http_listen(int port, int maxcons)
|
static int http_listen(int port, int maxcons)
|
||||||
{
|
{
|
||||||
@ -95,6 +96,7 @@ static void http_process(http_worker_t *worker, FILE *stream)
|
|||||||
}
|
}
|
||||||
|
|
||||||
worker->range_header[0] = 0;
|
worker->range_header[0] = 0;
|
||||||
|
worker->user_agent[0] = 0;
|
||||||
worker->content_length = -1;
|
worker->content_length = -1;
|
||||||
|
|
||||||
// Consume headers
|
// Consume headers
|
||||||
@ -107,9 +109,10 @@ static void http_process(http_worker_t *worker, FILE *stream)
|
|||||||
|
|
||||||
if (strcasestr(line, HEADER_RANGE) == line) {
|
if (strcasestr(line, HEADER_RANGE) == line) {
|
||||||
strcpy(worker->range_header, line);
|
strcpy(worker->range_header, line);
|
||||||
}
|
} else if (strcasestr(line, HEADER_CONTENT_LENGTH) == line) {
|
||||||
if (strcasestr(line, HEADER_CONTENT_LENGTH) == line) {
|
|
||||||
worker->content_length = atoi(line + strlen(HEADER_CONTENT_LENGTH));
|
worker->content_length = atoi(line + strlen(HEADER_CONTENT_LENGTH));
|
||||||
|
} else if (strcasestr(line, HEADER_USER_AGENT) == line) {
|
||||||
|
strcpy(worker->user_agent, line + strlen(HEADER_USER_AGENT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +132,8 @@ static void http_process(http_worker_t *worker, FILE *stream)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LOG_INFO(worker, "Request '%s'", worker->client_method);
|
||||||
|
|
||||||
if (worker->current_method) {
|
if (worker->current_method) {
|
||||||
worker->current_method->func(worker, stream);
|
worker->current_method->func(worker, stream);
|
||||||
worker->current_method = NULL;
|
worker->current_method = NULL;
|
||||||
|
@ -36,6 +36,7 @@ typedef struct http_worker_s {
|
|||||||
char *client_host;
|
char *client_host;
|
||||||
char client_method[BUFSIZE];
|
char client_method[BUFSIZE];
|
||||||
char range_header[BUFSIZE];
|
char range_header[BUFSIZE];
|
||||||
|
char user_agent[BUFSIZE];
|
||||||
|
|
||||||
http_method_t *current_method;
|
http_method_t *current_method;
|
||||||
} http_worker_t;
|
} http_worker_t;
|
||||||
@ -48,6 +49,7 @@ typedef struct http_server_options_s {
|
|||||||
int http_server(http_server_options_t *options, http_method_t *methods);
|
int http_server(http_server_options_t *options, http_method_t *methods);
|
||||||
void http_content(http_worker_t *worker, FILE *stream);
|
void http_content(http_worker_t *worker, FILE *stream);
|
||||||
void http_write_response(FILE *stream, const char *status, const char *content_type, const char *body, unsigned content_length);
|
void http_write_response(FILE *stream, const char *status, const char *content_type, const char *body, unsigned content_length);
|
||||||
|
void http_write_responsef(FILE *stream, const char *status, const char *content_type, const char *fmt, ...);
|
||||||
void http_200(FILE *stream, const char *data);
|
void http_200(FILE *stream, const char *data);
|
||||||
void http_400(FILE *stream, const char *data);
|
void http_400(FILE *stream, const char *data);
|
||||||
void http_404(FILE *stream, const char *data);
|
void http_404(FILE *stream, const char *data);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
@ -23,6 +24,17 @@ void http_write_response(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void http_write_responsef(FILE *stream, const char *status, const char *content_type, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list arg;
|
||||||
|
va_start(arg, fmt);
|
||||||
|
|
||||||
|
char *body = NULL;
|
||||||
|
size_t n = vasprintf(&body, fmt, arg);
|
||||||
|
http_write_response(stream, status, content_type, body, n);
|
||||||
|
free(body);
|
||||||
|
}
|
||||||
|
|
||||||
void http_content(http_worker_t *worker, FILE *stream)
|
void http_content(http_worker_t *worker, FILE *stream)
|
||||||
{
|
{
|
||||||
if (worker->current_method) {
|
if (worker->current_method) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user