<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Complete WebRTC Demo - Video Call</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<style>
body {
font-family: Arial, sans-serif;
margin: 10px;
padding: 0;
background-color: #f7f7f7;
}
h1 {
text-align: center;
color: #333;
}
.section {
background-color: #fff;
margin: 10px auto;
padding: 15px;
border: 1px solid #ccc;
border-radius: 5px;
max-width: 800px;
}
.section h2 {
margin-top: 0;
color: #444;
}
video {
width: 100%;
max-width: 320px;
height: auto;
background-color: #000;
margin: 5px;
border: 1px solid #333;
}
textarea, select, .output {
width: 90%;
max-width: 700px;
display: block;
margin: 10px auto;
padding: 8px;
font-size: 14px;
}
button {
margin: 5px;
padding: 8px 12px;
font-size: 14px;
cursor: pointer;
border: none;
background-color: #4285F4;
color: #fff;
border-radius: 3px;
}
button:hover {
background-color: #3367D6;
}
.output {
border: 1px solid #ccc;
background-color: #eef;
border-radius: 3px;
min-height: 30px;
padding: 5px;
}
@media (max-width: 600px) {
button, textarea, select, .output { width: 95%; }
}
</style>
<!-- cordova.js will be injected during build -->
<script type="text/javascript" src="cordova.js"></script>
</head>
<body>
<h1>Complete WebRTC Demo - Video Call</h1>
<!-- Media & Camera Section -->
<div class="section">
<h2>Media & Camera</h2>
<!-- Local video for displaying stream -->
<video id="localVideo" autoplay muted></video>
<!-- Remote video for the video call -->
<video id="remoteVideo" autoplay style="display:none;"></video>
<br>
<!-- Buttons to control media & switch camera -->
<button id="startButton">Start Media (Default)</button>
<button id="useFrontCamera">Front Camera</button>
<button id="useBackCamera">Back Camera</button>
<button id="closeCamera">Close Camera</button>
</div>
<!-- Video Call Section (Manual Signaling) -->
<div class="section">
<h2>Video Call</h2>
<button id="startCall">Create Offer</button>
<button id="answerCall">Create Answer</button>
<button id="setRemoteSDP">Set Remote SDP</button>
<br>
<textarea id="sdpArea" placeholder="Paste or copy SDP here..."></textarea>
</div>
<!-- Additional Features Section: Screenshot & Recording -->
<div class="section">
<h2>Additional Features</h2>
<button id="captureScreenshot">Capture Screenshot</button>
<button id="startRecording">Start Recording</button>
<button id="stopRecording">Stop Recording</button>
</div>
<!-- File Functions Section: Select Files & Select Target Path -->
<div class="section">
<h2>File Functions</h2>
<button id="selectFiles">Select Files</button>
<div id="selectFilesOutput" class="output" placeholder="selectFiles output"></div>
<br>
<button id="selectTargetPath">Select Target Path</button>
<div id="selectTargetPathOutput" class="output" placeholder="selectTargetPath output"></div>
</div>
<!-- Permissions Section: Dropdown & Check Permissions -->
<div class="section">
<h2>Check Permissions</h2>
<label for="permissionsDropdown">Select permissions (multiple):</label>
<select id="permissionsDropdown" multiple>
<option value="CAMERA">CAMERA</option>
<option value="RECORD_AUDIO">RECORD_AUDIO</option>
<option value="MODIFY_AUDIO_SETTINGS">MODIFY_AUDIO_SETTINGS</option>
<option value="MANAGE_EXTERNAL_STORAGE">MANAGE_EXTERNAL_STORAGE</option>
<option value="READ_EXTERNAL_STORAGE">READ_EXTERNAL_STORAGE</option>
<option value="WRITE_EXTERNAL_STORAGE">WRITE_EXTERNAL_STORAGE</option>
<option value="READ_MEDIA_AUDIO">READ_MEDIA_AUDIO</option>
<option value="READ_MEDIA_VIDEO">READ_MEDIA_VIDEO</option>
<option value="READ_MEDIA_IMAGES">READ_MEDIA_IMAGES</option>
</select>
<button id="checkPermissionsBtn">Check Permissions</button>
<div id="permissionsOutput" class="output"></div>
</div>
<script>
// WebRTC: https://webrtc.github.io/samples/
document.addEventListener("deviceready", function () {
// Initialize the CordovaSaveBlob plugin
cordova.plugin.CordovaSaveBlob.registerWebRTC();
// Call initial checkAndRequestPermissions with default permissions
cordova.plugin.CordovaSaveBlob.checkAndRequestPermissions({
permissions: ["CAMERA", "RECORD_AUDIO", "MODIFY_AUDIO_SETTINGS", "MANAGE_EXTERNAL_STORAGE"]
}, function(success) {
console.log("Initial Permissions:", success);
}, function(error) {
console.error("Initial Permissions error:", error);
});
let localStream;
let mediaRecorder;
let recordedChunks = [];
let pc; // RTCPeerConnection for video call
let currentCamera = "user"; // Default: front camera
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const sdpArea = document.getElementById("sdpArea");
// Function to start media with the selected camera
async function startMedia() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
try {
const constraints = {
video: { facingMode: currentCamera },
audio: true
};
localStream = await navigator.mediaDevices.getUserMedia(constraints);
localVideo.srcObject = localStream;
alert("Media stream started with camera: " + currentCamera);
} catch (err) {
console.error("Failed to get media:", err);
alert("Failed to get media: " + err);
}
}
// Media & Camera control event listeners
document.getElementById("startButton").addEventListener("click", startMedia);
document.getElementById("useFrontCamera").addEventListener("click", function() {
currentCamera = "user";
startMedia();
});
document.getElementById("useBackCamera").addEventListener("click", function() {
currentCamera = "environment";
startMedia();
});
document.getElementById("closeCamera").addEventListener("click", function() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
localStream = null;
localVideo.srcObject = null;
alert("Camera has been closed.");
} else {
alert("Camera is not active.");
}
});
// -------------------------------
// VIDEO CALL FUNCTIONS (Manual Signaling)
// -------------------------------
document.getElementById("startCall").addEventListener("click", async function() {
if (!localStream) {
alert("Media is not started. Please click 'Start Media' first.");
return;
}
if (!pc) {
pc = new RTCPeerConnection(configuration);
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
pc.addEventListener('track', event => {
remoteVideo.style.display = "block";
remoteVideo.srcObject = event.streams[0];
});
pc.addEventListener('icecandidate', event => {
if (event.candidate) {
console.log("ICE Candidate:", event.candidate);
}
});
}
try {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sdpArea.value = JSON.stringify(pc.localDescription);
alert("Offer created. Copy the SDP from the textarea and share with the remote peer.");
} catch (err) {
console.error("Error creating offer:", err);
alert("Error creating offer: " + err);
}
});
document.getElementById("answerCall").addEventListener("click", async function() {
if (!localStream) {
alert("Media is not started. Please click 'Start Media' first.");
return;
}
if (!pc) {
pc = new RTCPeerConnection(configuration);
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
pc.addEventListener('track', event => {
remoteVideo.style.display = "block";
remoteVideo.srcObject = event.streams[0];
});
pc.addEventListener('icecandidate', event => {
if (event.candidate) {
console.log("ICE Candidate:", event.candidate);
}
});
}
try {
const remoteDesc = JSON.parse(sdpArea.value);
await pc.setRemoteDescription(remoteDesc);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sdpArea.value = JSON.stringify(pc.localDescription);
alert("Answer created. Share the SDP with the remote peer.");
} catch (err) {
console.error("Error creating answer:", err);
alert("Error creating answer: " + err);
}
});
document.getElementById("setRemoteSDP").addEventListener("click", async function() {
if (!pc) {
alert("RTCPeerConnection is not created. Please start a call first.");
return;
}
try {
const remoteDesc = JSON.parse(sdpArea.value);
await pc.setRemoteDescription(remoteDesc);
alert("Remote SDP is set.");
} catch (err) {
console.error("Error setting remote SDP:", err);
alert("Error setting remote SDP: " + err);
}
});
// -------------------------------
// ADDITIONAL FEATURES: SCREENSHOT & RECORDING
// -------------------------------
document.getElementById("captureScreenshot").addEventListener("click", function() {
if (!localStream) {
alert("Media is not started. Please click 'Start Media' first.");
return;
}
const canvas = document.createElement("canvas");
const videoWidth = localVideo.videoWidth;
const videoHeight = localVideo.videoHeight;
if (videoWidth === 0 || videoHeight === 0) {
alert("Video is not ready for capture. Please try again later.");
return;
}
canvas.width = videoWidth;
canvas.height = videoHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(localVideo, 0, 0, videoWidth, videoHeight);
canvas.toBlob(function(blob) {
if (blob) {
const reader = new FileReader();
reader.onloadend = function() {
const dataUrl = reader.result; // Format: data:image/png;base64,...
const base64Data = dataUrl.replace(/^data:.*;base64,/, '');
const uniqueName = "screenshot_" + new Date().getTime() + ".png";
// Call plugin to save the file
cordova.plugin.CordovaSaveBlob.downloadBlob({
base64Data: base64Data,
fileName: uniqueName
},
function(msg) { alert("Screenshot saved successfully: " + msg); },
function(err) { alert("Download error: " + err); });
};
reader.readAsDataURL(blob);
} else {
alert("Failed to create blob from screenshot.");
}
}, "image/png");
});
document.getElementById("startRecording").addEventListener("click", function() {
if (localStream) {
recordedChunks = [];
const options = { mimeType: 'video/webm; codecs=vp9' };
try {
mediaRecorder = new MediaRecorder(localStream, options);
} catch (e) {
console.error("MediaRecorder does not support the given options:", e);
alert("MediaRecorder not supported: " + e);
return;
}
mediaRecorder.ondataavailable = (event) => {
if (event.data && event.data.size > 0) {
recordedChunks.push(event.data);
}
};
mediaRecorder.start();
console.log("Recording started.");
alert("Recording started.");
} else {
alert("Media is not started. Please click 'Start Media' first.");
}
});
document.getElementById("stopRecording").addEventListener("click", function() {
if (mediaRecorder && mediaRecorder.state !== "inactive") {
mediaRecorder.stop();
mediaRecorder.onstop = function() {
const blob = new Blob(recordedChunks, { type: "video/webm" });
console.log("Recording finished. Blob size:", blob.size);
const reader = new FileReader();
reader.onloadend = function() {
const dataUrl = reader.result;
const base64Data = dataUrl.replace(/^data:.*;base64,/, '');
const uniqueName = "recording_" + new Date().getTime() + ".webm";
cordova.plugin.CordovaSaveBlob.downloadBlob({
base64Data: base64Data,
fileName: uniqueName
},
function(msg) { alert("Recording saved successfully: " + msg); },
function(err) { alert("Download error: " + err); });
};
reader.readAsDataURL(blob);
};
} else {
alert("Recording is not started or already stopped.");
}
});
// -------------------------------
// FILE FUNCTIONS: Select Files & Select Target Path with Callback Outputs
// -------------------------------
document.getElementById("selectFiles").addEventListener("click", function() {
if (cordova.plugin.CordovaSaveBlob && cordova.plugin.CordovaSaveBlob.selectFiles) {
// example > all mime: "" | image/* | video/* | audio/* | application/pdf | image/png | image/jpeg | video/mp4 | audio/mpeg
cordova.plugin.CordovaSaveBlob.selectFiles({ mime: "image/*" },
function(response) {
document.getElementById("selectFilesOutput").innerText = "Response: " + response;
},
function(error) {
document.getElementById("selectFilesOutput").innerText = "Error: " + error;
}
);
} else {
alert("selectFiles function is not available.");
}
});
document.getElementById("selectTargetPath").addEventListener("click", function() {
if (cordova.plugin.CordovaSaveBlob && cordova.plugin.CordovaSaveBlob.selectTargetPath) {
cordova.plugin.CordovaSaveBlob.selectTargetPath(
function(response) {
document.getElementById("selectTargetPathOutput").innerText = "Response: " + response;
},
function(error) {
document.getElementById("selectTargetPathOutput").innerText = "Error: " + error;
}
);
} else {
alert("selectTargetPath function is not available.");
}
});
// -------------------------------
// PERMISSIONS: Dropdown & Check Permissions
// -------------------------------
document.getElementById("checkPermissionsBtn").addEventListener("click", function() {
const dropdown = document.getElementById("permissionsDropdown");
const selectedOptions = Array.from(dropdown.selectedOptions).map(opt => opt.value);
// Call checkAndRequestPermissions with selected permissions
cordova.plugin.CordovaSaveBlob.checkAndRequestPermissions({ permissions: selectedOptions },
function(success) {
document.getElementById("permissionsOutput").innerText = "Success: " + success;
},
function(error) {
document.getElementById("permissionsOutput").innerText = "Error: " + error;
}
);
});
}, false);
</script>
</body>
</html>