From 48a3dc50be068bb7c4f8075695f1226ac778e604 Mon Sep 17 00:00:00 2001 From: Owen Carter Date: Tue, 15 Sep 2020 00:04:45 +0200 Subject: [PATCH] Streamviewer (#39) * dedicated stream viewer * minimal functionality, but does rotate correctly * stream autostarts and scales to window size preserving aspect ratio --- app_httpd.cpp | 45 ++++++++- css.h | 6 +- index_ov2640.h | 33 ++++--- index_ov3660.h | 33 ++++--- miniviewer.h => viewers.h | 191 +++++++++++++++++++++++++++++++++++++- 5 files changed, 262 insertions(+), 46 deletions(-) rename miniviewer.h => viewers.h (61%) diff --git a/app_httpd.cpp b/app_httpd.cpp index 05df2c5..edec7e3 100644 --- a/app_httpd.cpp +++ b/app_httpd.cpp @@ -19,7 +19,7 @@ #include "index_ov2640.h" #include "index_ov3660.h" -#include "miniviewer.h" +#include "viewers.h" #include "css.h" #include "favicon/favicons.h" @@ -592,7 +592,6 @@ static esp_err_t cmd_handler(httpd_req_t *req){ static esp_err_t status_handler(httpd_req_t *req){ - static char json_response[1024]; sensor_t * s = esp_camera_sensor_get(); char * p = json_response; @@ -629,8 +628,22 @@ static esp_err_t status_handler(httpd_req_t *req){ p+=sprintf(p, "\"cam_name\":\"%s\",", myName); p+=sprintf(p, "\"code_ver\":\"%s\",", myVer); p+=sprintf(p, "\"rotate\":\"%s\",", myRotation); - p+=sprintf(p, "\"stream_url\":\"%s\",", streamURL); - p+=sprintf(p, "\"http\":%i", 80); + p+=sprintf(p, "\"stream_url\":\"%s\"", streamURL); + *p++ = '}'; + *p++ = 0; + httpd_resp_set_type(req, "application/json"); + httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); + return httpd_resp_send(req, json_response, strlen(json_response)); +} + +static esp_err_t info_handler(httpd_req_t *req){ + static char json_response[256]; + char * p = json_response; + *p++ = '{'; + p+=sprintf(p, "\"cam_name\":\"%s\",", myName); + p+=sprintf(p, "\"code_ver\":\"%s\",", myVer); + p+=sprintf(p, "\"rotate\":\"%s\",", myRotation); + p+=sprintf(p, "\"stream_url\":\"%s\"", streamURL); *p++ = '}'; *p++ = 0; httpd_resp_set_type(req, "application/json"); @@ -683,6 +696,15 @@ static esp_err_t miniviewer_handler(httpd_req_t *req){ return httpd_resp_send(req, (const char *)miniviewer_html, miniviewer_html_len); } +static esp_err_t streamviewer_handler(httpd_req_t *req){ + flashLED(75); // a little feedback to user + delay(75); + flashLED(75); + httpd_resp_set_type(req, "text/html"); + httpd_resp_set_hdr(req, "Content-Encoding", "identity"); + return httpd_resp_send(req, (const char *)streamviewer_html, streamviewer_html_len); +} + static esp_err_t index_handler(httpd_req_t *req){ flashLED(75); // a little feedback to user delay(75); @@ -769,6 +791,19 @@ void startCameraServer(int hPort, int sPort){ .user_ctx = NULL }; + httpd_uri_t streamviewer_uri = { + .uri = "/view", + .method = HTTP_GET, + .handler = streamviewer_handler, + .user_ctx = NULL + }; + + httpd_uri_t info_uri = { + .uri = "/info", + .method = HTTP_GET, + .handler = info_handler, + .user_ctx = NULL + }; ra_filter_init(&ra_filter, 20); @@ -809,6 +844,8 @@ void startCameraServer(int hPort, int sPort){ Serial.printf("Starting stream server on port: '%d'\n", config.server_port); if (httpd_start(&stream_httpd, &config) == ESP_OK) { httpd_register_uri_handler(stream_httpd, &stream_uri); + httpd_register_uri_handler(stream_httpd, &info_uri); + httpd_register_uri_handler(stream_httpd, &streamviewer_uri); httpd_register_uri_handler(stream_httpd, &favicon_16x16_uri); httpd_register_uri_handler(stream_httpd, &favicon_32x32_uri); httpd_register_uri_handler(stream_httpd, &favicon_ico_uri); diff --git a/css.h b/css.h index 657e2d5..409c30a 100644 --- a/css.h +++ b/css.h @@ -34,7 +34,7 @@ section.main { #menu { display: none; flex-wrap: nowrap; - width: 340px; + width: 380px; background: #363636; padding: 8px; border-radius: 4px; @@ -63,10 +63,6 @@ figure { figure img { display: block; -/* - no max-width: - width: 100%; -*/ max-width: 100%; width: auto; height: auto; diff --git a/index_ov2640.h b/index_ov2640.h index 47038d2..d2535e6 100644 --- a/index_ov2640.h +++ b/index_ov2640.h @@ -8,7 +8,7 @@ const uint8_t index_ov2640_html[] = R"=====( - ESP32 OV3660 + ESP32 OV2640 @@ -19,15 +19,14 @@ const uint8_t index_ov2640_html[] = R"=====( flex-wrap: nowrap; align-items: stretch } - - + figure img { display: block; max-width: 100%; width: auto; height: auto } - + figure { padding: 0 0 0 0px; margin: 0; @@ -270,11 +269,11 @@ const uint8_t index_ov2640_html[] = R"=====(
+ title="ESP32 Cam Webserver on GitHub" target="_blank">Firmware
@@ -307,7 +306,7 @@ const uint8_t index_ov2640_html[] = R"=====( const streamButton = document.getElementById('toggle-stream') const enrollButton = document.getElementById('face_enroll') const closeButton = document.getElementById('close-stream') - const streamLink = document.getElementById('stream_url') + const streamLink = document.getElementById('stream_link') const detect = document.getElementById('face_detect') const recognize = document.getElementById('face_recognize') const framesize = document.getElementById('framesize') @@ -341,7 +340,7 @@ const uint8_t index_ov2640_html[] = R"=====( initialValue = el.value el.value = value } - + if (updateRemote && initialValue !== value) { updateConfig(el); } else if(!updateRemote){ @@ -377,9 +376,9 @@ const uint8_t index_ov2640_html[] = R"=====( applyRotation(); } else if(el.id === "stream_url"){ stream_url.innerHTML = value; - stream_url.setAttribute("title", "Open raw stream URL in new window"); - stream_url.style.textDecoration = "underline"; - stream_url.style.cursor = "pointer"; + stream_link.setAttribute("title", "Open stream viewer ( " + value + "view )"); + stream_link.style.textDecoration = "underline"; + stream_link.style.cursor = "pointer"; streamURL = value; streamButton.setAttribute("title", `You can also browse to '${streamURL}' for a raw stream`); show(streamGroup) @@ -479,7 +478,7 @@ const uint8_t index_ov2640_html[] = R"=====( // Attach actions to controls streamLink.onclick = () => { - window.open(streamURL, "_blank"); + window.open(streamURL + "view", "_blank"); } stillButton.onclick = () => { @@ -529,7 +528,7 @@ const uint8_t index_ov2640_html[] = R"=====( show(agcGain) } } - + // Exposure const aec = document.getElementById('aec') const exposure = document.getElementById('aec_value-group') @@ -537,7 +536,7 @@ const uint8_t index_ov2640_html[] = R"=====( updateConfig(aec) aec.checked ? hide(exposure) : show(exposure) } - + // AWB const awb = document.getElementById('awb_gain') const wb = document.getElementById('wb_mode-group') @@ -545,7 +544,7 @@ const uint8_t index_ov2640_html[] = R"=====( updateConfig(awb) awb.checked ? show(wb) : hide(wb) } - + // Detection and framesize rotate.onchange = () => { applyRotation(); @@ -559,7 +558,7 @@ const uint8_t index_ov2640_html[] = R"=====( updateValue(recognize, false) } } - + detect.onchange = () => { if (framesize.value > 5) { alert("Please select CIF or lower resolution before enabling this feature!"); @@ -572,7 +571,7 @@ const uint8_t index_ov2640_html[] = R"=====( updateValue(recognize, false) } } - + recognize.onchange = () => { if (framesize.value > 5) { alert("Please select CIF or lower resolution before enabling this feature!"); diff --git a/index_ov3660.h b/index_ov3660.h index 5b3c043..70126b3 100644 --- a/index_ov3660.h +++ b/index_ov3660.h @@ -19,15 +19,14 @@ const uint8_t index_ov3660_html[] = R"=====( flex-wrap: nowrap; align-items: stretch } - - + figure img { display: block; max-width: 100%; width: auto; height: auto } - + figure { padding: 0 0 0 0px; margin: 0; @@ -283,11 +282,11 @@ const uint8_t index_ov3660_html[] = R"=====(
+ title="ESP32 Cam Webserver on GitHub" target="_blank">Firmware
@@ -320,7 +319,7 @@ const uint8_t index_ov3660_html[] = R"=====( const streamButton = document.getElementById('toggle-stream') const enrollButton = document.getElementById('face_enroll') const closeButton = document.getElementById('close-stream') - const streamLink = document.getElementById('stream_url') + const streamLink = document.getElementById('stream_link') const detect = document.getElementById('face_detect') const recognize = document.getElementById('face_recognize') const framesize = document.getElementById('framesize') @@ -354,7 +353,7 @@ const uint8_t index_ov3660_html[] = R"=====( initialValue = el.value el.value = value } - + if (updateRemote && initialValue !== value) { updateConfig(el); } else if(!updateRemote){ @@ -388,9 +387,9 @@ const uint8_t index_ov3660_html[] = R"=====( applyRotation(); } else if(el.id === "stream_url"){ stream_url.innerHTML = value; - stream_url.setAttribute("title", "Open raw stream URL in new window"); - stream_url.style.textDecoration = "underline"; - stream_url.style.cursor = "pointer"; + stream_link.setAttribute("title", "Open stream viewer ( " + value + "view )"); + stream_link.style.textDecoration = "underline"; + stream_link.style.cursor = "pointer"; streamURL = value; streamButton.setAttribute("title", `You can also browse to '${streamURL}' for a raw stream`); show(streamGroup) @@ -490,7 +489,7 @@ const uint8_t index_ov3660_html[] = R"=====( // Attach actions to controls streamLink.onclick = () => { - window.open(streamURL, "_blank"); + window.open(streamURL + "view", "_blank"); } stillButton.onclick = () => { @@ -537,7 +536,7 @@ const uint8_t index_ov3660_html[] = R"=====( show(agcGain) } } - + // Exposure const aec = document.getElementById('aec') const exposure = document.getElementById('aec_value-group') @@ -545,7 +544,7 @@ const uint8_t index_ov3660_html[] = R"=====( updateConfig(aec) aec.checked ? hide(exposure) : show(exposure) } - + // AWB const awb = document.getElementById('awb_gain') const wb = document.getElementById('wb_mode-group') @@ -553,7 +552,7 @@ const uint8_t index_ov3660_html[] = R"=====( updateConfig(awb) awb.checked ? show(wb) : hide(wb) } - + // Detection and framesize rotate.onchange = () => { applyRotation(); @@ -567,7 +566,7 @@ const uint8_t index_ov3660_html[] = R"=====( updateValue(recognize, false) } } - + detect.onchange = () => { if (framesize.value > 5) { alert("Please select CIF or lower resolution before enabling this feature!"); @@ -580,7 +579,7 @@ const uint8_t index_ov3660_html[] = R"=====( updateValue(recognize, false) } } - + recognize.onchange = () => { if (framesize.value > 5) { alert("Please select CIF or lower resolution before enabling this feature!"); @@ -597,7 +596,7 @@ const uint8_t index_ov3660_html[] = R"=====( } swapButton.onclick = () => { - window.open('/','_self'); + window.open('/view','_self'); } }) diff --git a/miniviewer.h b/viewers.h similarity index 61% rename from miniviewer.h rename to viewers.h index 71b0664..3276510 100644 --- a/miniviewer.h +++ b/viewers.h @@ -1,5 +1,8 @@ -//File: index_ov2640.html -const uint8_t miniviewer_html[] = R"=====( +/* + * Miniviewer and streamviewer + */ + + const uint8_t miniviewer_html[] = R"=====( @@ -110,7 +113,7 @@ const uint8_t miniviewer_html[] = R"=====( initialValue = el.value el.value = value } - + if (updateRemote && initialValue !== value) { updateConfig(el); } else if(!updateRemote){ @@ -279,3 +282,185 @@ const uint8_t miniviewer_html[] = R"=====( )====="; size_t miniviewer_html_len = sizeof(miniviewer_html); + +/* Stream Viewer */ + + const uint8_t streamviewer_html[] = R"=====( + + + + + + ESP32-CAM StreamViewer + + + + + + +
+ +
+
+ + + + + +
+
+ +
+
+
+ + + + +)====="; + +size_t streamviewer_html_len = sizeof(streamviewer_html);