From 8085a80928ea35a786a6d1bac4e69b9ee705bb03 Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 13 Sep 2020 14:20:17 +0200 Subject: [PATCH 1/3] App-part of streamviewer --- app_httpd.cpp | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/app_httpd.cpp b/app_httpd.cpp index 05df2c5..ed3ff53 100644 --- a/app_httpd.cpp +++ b/app_httpd.cpp @@ -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; @@ -638,6 +637,20 @@ static esp_err_t status_handler(httpd_req_t *req){ 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++ = '}'; + *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 favicon_16x16_handler(httpd_req_t *req){ flashLED(75); // a little feedback to user delay(75); @@ -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, &nfo_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); From befaa72877419a1060573499d60c3fa9fedb55eb Mon Sep 17 00:00:00 2001 From: Owen Date: Sun, 13 Sep 2020 17:20:50 +0200 Subject: [PATCH 2/3] dedicated stream viewer --- app_httpd.cpp | 8 +- css.h | 6 +- index_ov2640.h | 33 ++++--- index_ov3660.h | 33 ++++--- miniviewer.h => viewers.h | 181 +++++++++++++++++++++++++++++++++++++- 5 files changed, 215 insertions(+), 46 deletions(-) rename miniviewer.h => viewers.h (63%) diff --git a/app_httpd.cpp b/app_httpd.cpp index ed3ff53..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" @@ -628,8 +628,7 @@ 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"); @@ -644,6 +643,7 @@ static esp_err_t info_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++ = '}'; *p++ = 0; httpd_resp_set_type(req, "application/json"); @@ -844,7 +844,7 @@ 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, &nfo_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); 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 63% rename from miniviewer.h rename to viewers.h index 71b0664..3be050b 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,175 @@ 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); From 3151a9ef5670d6eff266974fe3a71c83d8ec9b33 Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 14 Sep 2020 15:07:42 +0200 Subject: [PATCH 3/3] streamviewer scale and fullscreen --- viewers.h | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/viewers.h b/viewers.h index 3be050b..3276510 100644 --- a/viewers.h +++ b/viewers.h @@ -300,12 +300,13 @@ size_t miniviewer_html_len = sizeof(miniviewer_html); font-family: Arial,Helvetica,sans-serif; background: #181818; color: #EFEFEF; - font-size: 16px + font-size: 16px; + margin: 0px; } figure { padding: 0px; - margin: 0; + margin: 0px; -webkit-margin-before: 0; margin-block-start: 0; -webkit-margin-after: 0; @@ -317,23 +318,20 @@ size_t miniviewer_html_len = sizeof(miniviewer_html); } figure img { + object-fit: contain; display: block; - max-width: 100%; - width: auto; - height: auto; - border-radius: 4px; - margin-top: 8px; + width: 100vw; + height: 100vh; } .image-container { position: relative; - min-width: 160px; transform-origin: top left } .loader { - border: 0.5em solid #f3f3f3; /* Light grey */ - border-top: 0.5em solid #000000; /* white */ + border: 0.5em solid #f3f3f3; + border-top: 0.5em solid #000000; border-radius: 50%; width: 1em; height: 1em; @@ -402,7 +400,7 @@ size_t miniviewer_html_len = sizeof(miniviewer_html); } else if(!updateRemote){ if(el.id === "cam_name"){ window.document.title = value; - viewContainer.setAttribute("title", value); + viewContainer.setAttribute("title", value + "\n(doubleclick for fullscreen)"); console.log('Name set to: ' + value); } else if(el.id === "code_ver"){ console.log('Firmware Build: ' + value); @@ -447,6 +445,18 @@ size_t miniviewer_html_len = sizeof(miniviewer_html); viewContainer.style.transform = `rotate(0deg)`; } console.log('Rotation ' + rot + ' applied'); + + view.ondblclick = () => { + if (view.requestFullscreen) { + view.requestFullscreen(); + } else if (view.mozRequestFullScreen) { /* Firefox */ + view.mozRequestFullScreen(); + } else if (view.webkitRequestFullscreen) { /* Chrome, Safari and Opera */ + view.webkitRequestFullscreen(); + } else if (view.msRequestFullscreen) { /* IE/Edge */ + view.msRequestFullscreen(); + } + } } })