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.
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.
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.
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.
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.
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.
<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>
startRec(), requests screen share permission and begins recordingstopRec(), stops MediaRecorder and stream; starts as disableddownloadRec() to save the .webm fileJavaScript: 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.
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.
onclick handlers expect to find named functions.
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(); }
recChunks array. On stop, all chunks are combined into a single Blob of type video/webm.<video> element for instant in-page playback with no upload and no server round-trip.<a> tag, sets a timestamped filename, and programmatically clicks it. The browser saves the .webm file straight to the user's Downloads folder.
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!
Source > HTML Code attributePage > Function and Global Variable Declaration, not Execute when Page Loads
Comments
Post a Comment