Skip to main content

Screen Recorder in Oracle APEX (Single Page)

Oracle APEX Tutorial

Screen Recorder in Oracle APEX :
Single Page

Build a fully functional browser-based screen recorder inside Oracle APEX using just one Static Content region and native JavaScript. No plugins, no external libraries, no server uploads required.

✍️ Why I Built This

I was working on a client project where the support team needed to record screen issues and share them directly from the APEX application, without switching to any external tool. Installing third-party software was not an option on their machines, and every screen recorder extension required IT approval.

That is when I thought: the browser already has everything we need. Why not build it right inside APEX? That idea turned into this.

🎬 Start / Stop Recording
👁 Instant Preview
⬇️ One-click Download
🔊 Audio + Video
🚫 No Plugins Needed
📅 Coming Soon: A Multi-Page Screen Recorder with advanced controls, session management, and upload to APEX collections will be covered in an upcoming blog post. Stay tuned!
Step 01
Static Content Region
Step 02
HTML UI Code
Step 03
JavaScript Functions
💼 Real Use Case

One of our end users was trying to report a bug in an APEX form: a validation error that only appeared under a very specific sequence of steps. Describing it over a chat message took 20 minutes and still was not clear. With this recorder built into the same APEX page, she clicked Start Recording, reproduced the issue in 30 seconds, clicked Stop, and downloaded the video. The developer understood it immediately.

That is the kind of problem this solves: not just "screen recording" as a feature, but removing friction from real day-to-day workflows inside APEX.

1

Create a Static Content Region

In APEX Page Designer, add a new region and configure it as a Static Content type. This is where the recorder UI (buttons, video preview, and status text) will live.

Page Designer Regions Create Region Type: Static Content
Region TypeStatic Content
TitleScreen Recorder (or any label you prefer)
TemplateStandard or Blank, your choice
Source / HTML CodePaste the HTML from Step 2 here
💡 This entire recorder runs 100% client-side. The MediaRecorder API is built into all modern browsers. No APEX plug-ins, no PL/SQL, and no server storage are needed for this single-page version.
2

HTML Code: Region Source

Paste this HTML into the region's Source > HTML Code attribute. It renders the Start and Stop buttons, a status label, a hidden video preview, and a download button. All elements are shown or hidden dynamically by the JavaScript in Step 3.

HTML
<div id="apexScreenRecorder">

  <button type="button"
          id="startBtn"
          class="t-Button t-Button--primary"
          onclick="startRec()">
    Start Recording
  </button>

  <button type="button"
          id="stopBtn"
          class="t-Button t-Button--danger"
          onclick="stopRec()"
          disabled>
    Stop
  </button>

  <span id="recStatus"
        style="margin-left:12px;font-size:13px;color:gray;">
    Idle
  </span>

  <br><br>

  <video id="previewVideo"
         controls
         style="width:100%;display:none;border-radius:8px;">
  </video>

  <br>

  <button type="button"
          id="downloadBtn"
          class="t-Button"
          onclick="downloadRec()"
          style="display:none;margin-top:8px;">
    Download Recording
  </button>

</div>
startBtnTriggers startRec(), requests screen share permission and begins recording
stopBtnTriggers stopRec(), stops MediaRecorder and stream; starts as disabled
recStatusLive text label showing: Idle / Recording... / Done / Permission denied
previewVideoHidden until recording stops; auto-fills with the recorded blob URL
downloadBtnHidden until recording stops; triggers downloadRec() to save the .webm file
3

JavaScript: Function and Global Variable Declaration

Paste this into Page > Function and Global Variable Declaration. This declares all global state variables and the three recorder functions that the HTML buttons call.

Page Designer Page Function and Global Variable Declaration
🐛 Bug I Faced

I initially put the JavaScript inside Execute when Page Loads and the functions worked fine the first time. But after navigating away and coming back (APEX partial page refresh), calling startRec() threw a ReferenceError: startRec is not defined. It took me a while to figure out why.

The problem was that Execute when Page Loads wraps your code inside an anonymous function scope, so startRec never becomes a global and the HTML onclick attributes cannot reach it after a refresh.

Moving everything to Function and Global Variable Declaration fixed it completely. That section places your code in the true global scope, exactly where onclick handlers expect to find named functions.
JavaScript
var mediaRecorder, recChunks = [], recStream, recBlobUrl;

function startRec() {
  navigator.mediaDevices.getDisplayMedia({ video: true, audio: true })
    .then(function(stream) {

      recStream  = stream;
      recChunks  = [];
      mediaRecorder = new MediaRecorder(stream);

      mediaRecorder.ondataavailable = function(e) {
        if (e.data.size > 0) recChunks.push(e.data);
      };

      mediaRecorder.onstop = function() {
        var blob = new Blob(recChunks, { type: 'video/webm' });
        recBlobUrl = URL.createObjectURL(blob);

        var vid = document.getElementById('previewVideo');
        vid.src           = recBlobUrl;
        vid.style.display = 'block';

        document.getElementById('downloadBtn').style.display = 'inline-block';
        document.getElementById('recStatus').textContent    = 'Done';
      };

      mediaRecorder.start(100);

      document.getElementById('startBtn').disabled = true;
      document.getElementById('stopBtn').disabled  = false;
      document.getElementById('recStatus').textContent = 'Recording...';

      stream.getVideoTracks()[0].onended = stopRec;

    })
    .catch(function() {
      document.getElementById('recStatus').textContent = 'Permission denied';
    });
}

function stopRec() {
  if (mediaRecorder && mediaRecorder.state !== 'inactive')
    mediaRecorder.stop();
  if (recStream)
    recStream.getTracks().forEach(function(t) { t.stop(); });

  document.getElementById('startBtn').disabled = false;
  document.getElementById('stopBtn').disabled  = true;
}

function downloadRec() {
  if (!recBlobUrl) return;
  var a      = document.createElement('a');
  a.href     = recBlobUrl;
  a.download = 'recording-' + Date.now() + '.webm';
  a.click();
}
How It Works
getDisplayMedia() is the browser's native screen-capture API. It prompts the user to choose which screen, window, or tab to share. The browser handles all permissions natively with no APEX involvement.
MediaRecorder receives the display stream and records in chunks every 100ms. The small chunk interval keeps memory usage manageable for longer recordings and prevents data loss if the tab crashes.
ondataavailable fires every 100ms, pushing each chunk into the recChunks array. On stop, all chunks are combined into a single Blob of type video/webm.
URL.createObjectURL(blob) creates a temporary in-memory URL. This is assigned to the <video> element for instant in-page playback with no upload and no server round-trip.
stream.getVideoTracks()[0].onended is a safety net. If the user closes the screen-share dialog via the browser's own UI rather than clicking Stop, the recording still ends cleanly.
downloadRec() creates an invisible <a> tag, sets a timestamped filename, and programmatically clicks it. The browser saves the .webm file straight to the user's Downloads folder.
📼
📅 Coming in a Future Blog

This blog covers the Single Page screen recorder: a lightweight, zero-dependency solution that gets the job done with minimal setup. The next blog will cover the Multi-Page Screen Recorder with enhanced features including session management, recording history, upload to APEX Collections or REST APIs, and more polished UI controls.

Follow the blog series to get notified when it is published!

🎥
Quick Checklist
Create a Static Content region on your APEX page
Paste the HTML into the region's Source > HTML Code attribute
Paste the JavaScript into Page > Function and Global Variable Declaration, not Execute when Page Loads
Run the page, click Start Recording, select a screen or window, and allow permission
Click Stop. The preview appears instantly. Click Download Recording to save the file.

Comments

Popular posts from this blog

Face Detection in Oracle Apex

Face Detection Blog - Embedded JS Jefith Shalin Oracle APEX Developer · May 2025 Live Demo GitHub Oracle APEX · AI Integration A Developer's Real Story Building a Face Detection Attendance System in Oracle APEX A real story of trial, error, and finally making face-api.js actually work inside an APEX app. No fluff — just what happened and what finally worked. Try Live Demo View on GitHub 0.22.2 face-api.js Version 2 APEX Pages Built 4 AJAX Processes face-api.js Oracle APEX JavaScript AI / ML Face Recognition CDN Attendance System PL/SQL The Beginning So here is how it all started I was tasked with building an attendance system for our off...

Sticky Notes Widget Inside Oracle APEX

Oracle APEX Project Building a Sticky Notes Widget in Oracle APEX How I built a fully draggable, color-coded, per-user sticky notes board using jQuery UI, APEX Ajax callbacks, and a bit of patience. Live Demo GitHub Repo Oracle APEX jQuery UI PL/SQL Ajax Callbacks JavaScript CSS Introduction Why I Built This I have been building internal tools on Oracle APEX for a while now, and one thing I always felt was missing was a place where users could quickly jot down thoughts without leaving the page. Think of it like a personal scratchpad that lives right inside the app. I had seen sticky note UIs in some Google products and I thought, how hard can this be in APEX? It turned out to be more interesting than I expected. There were a few wrong tu...