WebRTC (Web Real-Time Communications) is a real-time communication technology that allows web applications or sites to establish peer-to-peer connections between browsers without an intermediary, enabling the transmission of video streams, audio streams, or any other data. The standards included in WebRTC allow users to create peer-to-peer data sharing and audio/video conferencing without installing any plug-ins or third-party software. WebRTC applies to all modern browsers and native clients on major platforms. The underlying technology is implemented as an open web standard and is provided as a regular JavaScript API in all major browsers.
The Web component can access the camera and microphone through W3C standard protocol APIs, and receive permission request notifications through the onPermissionRequest API. You need to declare the corresponding audio and video permissions in the configuration file and perform system-side authorization and app-side authorization.
Before using the camera and microphone, add the audio and video permissions in module.json5. For details about how to add permissions, see Declaring Permissions in the Configuration File.
// src/main/resources/base/element/string.json
"string":[
{
"name": "reason_for_camera",
"value": "reason_for_camera"
},
{
"name": "reason_for_microphone",
"value": "reason_for_microphone"
}
] // src/main/module.json5
"requestPermissions":[
{
"name" : "ohos.permission.CAMERA",
"reason": "$string:reason_for_camera",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when":"inuse"
}
},
{
"name" : "ohos.permission.MICROPHONE",
"reason": "$string:reason_for_microphone",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when":"inuse"
}
}
] Call the W3C standard protocol API navigator.mediaDevices.getUserMedia() in JavaScript to open the camera and microphone. The constraints parameter is a MediaStreamConstraints object that contains two members, video and audio, and specifies the requested media types.
In the Complete Example Code, requestPermissionsFromUser() is called so that after the app is opened, a dialog box asking whether to allow the app to access the camera and microphone is displayed. Click Always allow or Ask every time to authorize the app to access the camera and microphone.
In the Complete Example, after you click the Enable Camera button on the frontend page, a permission request is triggered through onPermissionRequest. After you click the onConfirm button in the dialog box, the camera and microphone are opened.
import { webview } from '@kit.ArkWeb';
import { BusinessError } from '@kit.BasicServicesKit';
import { abilityAccessCtrl } from '@kit.AbilityKit';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
uiContext: UIContext = this.getUIContext();
aboutToAppear() {
// Enable web frontend page debugging.
webview.WebviewController.setWebDebuggingAccess(true);
// Obtain the camera and microphone permissions, and proactively request the permissions when the component is created.
let atManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(this.uiContext.getHostContext(), ['ohos.permission.CAMERA', 'ohos.permission.MICROPHONE'])
.then((data) => {
console.info('data:' + JSON.stringify(data));
console.info('data permissions:' + data.permissions);
console.info('data authResults:' + data.authResults);
}).catch((error: BusinessError) => {
console.error(`Failed to request permissions from user. Code is ${error.code}, message is ${error.message}`);
})
}
build() {
Column() {
Web({ src: $rawfile('index.html'), controller: this.controller })
.onPermissionRequest((event) => {
if (event) {
this.uiContext.showAlertDialog({
title: 'title',
message: 'text',
primaryButton: {
value: 'deny',
action: () => {
event.request.deny();
}
},
secondaryButton: {
value: 'onConfirm',
action: () => {
let resources = event.request.getAccessibleResource();
console.info('Granting permissions for resources: ' + JSON.stringify(resources));
event.request.grant(resources);
}
},
cancel: () => {
event.request.deny();
}
})
}
})
}
}
} Code of the index.html page.
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<video id="video" width="500px" height="500px" autoplay></video>
<canvas id="canvas" width="500px" height="500px"></canvas>
<br>
<input type="button" title="HTML5 Camera" value="Enable Camera" onclick="getMedia()"/>
<script>
function getMedia()
{
let constraints = {
video: {width: 500, height: 500},
audio: true
};
// Obtain the video camera area.
let video = document.getElementById("video");
// Returned Promise object
let promise = navigator.mediaDevices.getUserMedia(constraints);
// then() is asynchronous. Invoke the MediaStream object as a parameter.
promise.then(function(MediaStream) {
video.srcObject = MediaStream;
video.play();
}).catch(function(err) {
console.error(err.name + ": " + err.message);
});
}
</script>
</body>
</html> In actual application, WebRTC may encounter device invoking issues. The common issues are as follows:
Symptom
When the camera is opened on a website, the telephoto camera is selected by default, and the focus cannot be adjusted.
Possible Causes
The camera list on the HarmonyOS platform returns device IDs in sequence.
HUAWEI Browser is a system app, and the last camera is the telephoto camera.
Some websites traverse devices and forcibly select the last camera based on the device ID. As a result, the telephoto camera is selected in HUAWEI Browser.
Solution
The camera list on the HarmonyOS platform returns device IDs in sequence. Therefore, there are two solutions.
Solution 1: Use facingMode: 'environment' to enable the browser to automatically select the rear camera, bypassing the logic of directly specifying the device ID.
Examples:
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Rear Camera Preview</title>
<style>
body { text-align: center; padding-top: 50px; }
button { font-size: 18px; padding: 12px 24px; margin: 10px; }
video { width: 80%; max-width: 500px; border: 1px solid #ccc; margin-top: 20px; background: #222; }
</style>
</head>
<body>
<h2>Rear Camera Preview facingMode</h2>
<video id="video" autoplay playsinline></video>
<br>
<button id="onBtn">Open Rear Camera</button>
<button id="offBtn" disabled>Close Camera</button>
<script>
let currentStream = null;
const video = document.getElementById('video');
const onBtn = document.getElementById('onBtn');
const offBtn = document.getElementById('offBtn');
async function openCamera() {
try {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
currentStream = null;
}
// Use facingMode:'environment' to enable the browser to automatically select the rear camera.
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' },
audio: false
});
video.srcObject = stream;
currentStream = stream;
onBtn.disabled = true;
offBtn.disabled = false;
console.info('Rear camera is on.');
} catch (err) {
console.error ('Failed to open the camera:', err);
alert('Failed to open the rear camera. Check the permission or HTTPS environment.');
}
}
function closeCamera() {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
currentStream = null;
}
video.srcObject = null;
onBtn.disabled = false;
offBtn.disabled = true;
console.info('Camera closed.');
}
onBtn.addEventListener('click', openCamera);
offBtn.addEventListener('click', closeCamera);
window.addEventListener('beforeunload', () => {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
}
});
</script>
</body>
</html> Solution 2: Traverse the rear cameras and select the first rear camera.
Traverse the devices to obtain the device ID of the first rear camera.
Use the device ID to forcibly select the first rear camera.
Example:
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Open Rear Camera</title>
<style>
body { text-align: center; padding-top: 50px; }
button { font-size: 18px; padding: 12px 24px; margin: 10px; }
video { width: 80%; max-width: 500px; border: 1px solid #ccc; margin-top: 20px; background: #222; }
</style>
</head>
<body>
<h2>Rear Camera Preview</h2>
<video id="video" autoplay playsinline></video>
<br>
<button id="onBtn">Open Rear Camera</button>
<button id="offBtn" disabled>Close Camera</button>
<script>
let currentStream = null;
const video = document.getElementById('video');
const onBtn = document.getElementById('onBtn');
const offBtn = document.getElementById('offBtn');
// Traverse the devices to obtain the device ID of the first rear camera.
async function getFirstBackCameraId() {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(d => d.kind === 'videoinput');
const back = videoDevices.find(d => {
const label = d.label.toLowerCase();
return label.includes('back') || label.includes('rear') || label.includes('Rear');
});
return back ? back.deviceId : (videoDevices[0] ? videoDevices[0].deviceId : null);
}
async function openCamera() {
try {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
currentStream = null;
}
// Use the device ID to forcibly select the first rear camera.
const deviceId = await getFirstBackCameraId();
if (!deviceId) {
alert('No camera found.');
return;
}
const stream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: deviceId } },
audio: false
});
video.srcObject = stream;
currentStream = stream;
onBtn.disabled = true;
offBtn.disabled = false;
console.info('Rear camera is on.');
} catch (err) {
console.error ('Failed to open the camera:', err);
alert('Failed to open the camera. Check the permission or HTTPS environment.');
}
}
function closeCamera() {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
currentStream = null;
}
video.srcObject = null;
onBtn.disabled = false;
offBtn.disabled = true;
console.info('Camera closed.');
}
onBtn.addEventListener('click', openCamera);
offBtn.addEventListener('click', closeCamera);
window.addEventListener('beforeunload', () => {
if (currentStream) {
currentStream.getTracks().forEach(t => t.stop());
}
});
</script>
</body>
</html>