目录

Starting a Camera and Microphone

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.

Requesting Permissions

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.

[h2]Configuring Permissions in module.json5

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.

[h2]System-Side Authorization

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.

[h2]App-Side Authorization

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.

Complete Example

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();
              }
            })
          }
        })
    }
  }
}

FAQs

In actual application, WebRTC may encounter device invoking issues. The common issues are as follows:

[h2]The telephoto camera cannot focus on an HTML5 page in HUAWEI Browser.

Symptom

When the camera is opened on a website, the telephoto camera is selected by default, and the focus cannot be adjusted.

Possible Causes

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.

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>