ESP32 Streaming Video NGROK

First time post. I looked through many posts on this topic but couldn’t figure it out. Thanks in advance for the help.

I am trying to use the video widget to remotely access my ESP32CAM stream (code pasted below). I have successfully done this with the board’s local IP address. In other words, the video widget works locally. But when I use that IP address to get an NGROK link, the widget buffers very briefly and then errors out. The NGROK link checks out on my browser and on my phone when using cellular data. The code pasted below came from a YouTuber who was able to get it working, although the Blynk app he was using looked very different from the current app. Any and all help would be greatly appreciated. Please let me know what further information you would need to help.

Details :
• iOS 14.8
• Blynk server
• Blynk Library version 1.0.1


  This is a simple MJPEG streaming webserver implemented for AI-Thinker ESP32-CAM and
  ESP32-EYE modules.
  This is tested to work with VLC and Blynk video widget.

  Inspired by and based on this Instructable: $9 RTSP Video Streamer Using the ESP32-CAM Board

  Board: AI-Thinker ESP32-CAM


#include "src/OV2640.h"
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClient.h>

// Select camera model

#include "camera_pins.h"

#define SSID1 ""
#define PWD1 ""

//#include "home_wifi_multi.h"

OV2640 cam;

WebServer server(80);

const char HEADER[] = "HTTP/1.1 200 OK\r\n" \
                      "Access-Control-Allow-Origin: *\r\n" \
                      "Content-Type: multipart/x-mixed-replace; boundary=123456789000000000000987654321\r\n";
const char BOUNDARY[] = "\r\n--123456789000000000000987654321\r\n";
const char CTNTTYPE[] = "Content-Type: image/jpeg\r\nContent-Length: ";
const int hdrLen = strlen(HEADER);
const int bdrLen = strlen(BOUNDARY);
const int cntLen = strlen(CTNTTYPE);

void handle_jpg_stream(void)
  char buf[32];
  int s;

  WiFiClient client = server.client();

  client.write(HEADER, hdrLen);
  client.write(BOUNDARY, bdrLen);

  while (true)
    if (!client.connected()) break;;
    s = cam.getSize();
    client.write(CTNTTYPE, cntLen);
    sprintf( buf, "%d\r\n\r\n", s );
    client.write(buf, strlen(buf));
    client.write((char *)cam.getfb(), s);
    client.write(BOUNDARY, bdrLen);

const char JHEADER[] = "HTTP/1.1 200 OK\r\n" \
                       "Content-disposition: inline; filename=capture.jpg\r\n" \
                       "Content-type: image/jpeg\r\n\r\n";
const int jhdLen = strlen(JHEADER);

void handle_jpg(void)
  WiFiClient client = server.client();;
  if (!client.connected()) return;

  client.write(JHEADER, jhdLen);
  client.write((char *)cam.getfb(), cam.getSize());

void handleNotFound()
  String message = "Server is running!\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  server.send(200, "text / plain", message);

void setup()

  //while (!Serial);            //wait for serial connection.

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;

  // Frame parameters
  //  config.frame_size = FRAMESIZE_UXGA;
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 9;
  config.fb_count = 2;

  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);


  IPAddress ip;

  WiFi.begin(SSID1, PWD1);
  while (WiFi.status() != WL_CONNECTED)
  ip = WiFi.localIP();
  Serial.println(F("WiFi connected"));
  Serial.print("Stream Link: http://");
  server.on("/mjpeg/1", HTTP_GET, handle_jpg_stream);
  server.on("/jpg", HTTP_GET, handle_jpg);

void loop()

Clearly the problem isn’t to do with your sketch, but the way that you’ve made the local IP address public, and maybe how you’ve configured the app to point to that publicly resolvable video stream. But, you’ve provided no detail about any of that.


Thanks Pete. I appreciate your time. Let me know if the following information is helpful or not.

With respect to the creation of the link, I did the following:

  1. Downloaded the NGROK executable (
  2. I used the command NGROK authtoken [my auth token]
  3. Then I used the command NGROK http [my ESP32 CAM IP]

The NGROK executable gave me a long link. This is the actual link…

I added /mjpeg/1 to the end of the link to get this…

I then pasted the above address into the video streaming app url address. I tried playing around with the jpeg quality and frame size because I had read that it might help, but it unfortunately didn’t. If it’s helpful, I’ve left the camera feed live for now (currently pointed up at my ceiling).

Thank you for your time and advice.

That URL just gives a black square on a white background for me.


Yes, that’s right. You’re looking at my ceiling. There’s no action.

Hi Pete. I may have misread your last email, but do you have any advice for me on how I might proceed?

I’d try something other than ngrok, as it doesn’t seem to be working as needed. As I said, it only showed a black square, no image.