

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 将您的网络应用程序与 WebRTC 重定向集成
<a name="webrtc-redirection-integration"></a>

WebRTC 重定向允许在会话 WorkSpaces 内运行的 Web 应用程序使用原生客户端音频设备。本主题向Web应用程序开发人员展示了如何检测WebRTC重定向环境并与之集成。

在上启用 WebRTC 重定向后 WorkSpace，Amazon DCV WebRTC 重定向浏览器扩展会在网页中注入代理软件开发工具包。您的应用程序可以检测到此代理，并使用它来将标准的WebRTC API调用（包括UserMedia 获取 RTCPeerConnection 和）重定向到用户的本地设备而不是 WorkSpace远程设备的虚拟设备。与通过 DCV 显示协议的流媒体相比，这提供了明显更好的性能和更低的延迟。

您的应用程序必须处理这两种情况：在启用了WebRTC重定向的情况下在中运行时，以及在未启用WebRTC重定向功能的标准浏览器中运行时。 WorkSpace 代理是可选的，如果不存在，您的应用程序可以回退到标准的WebRTC。

## 先决条件
<a name="webrtc-prereqs"></a>

### 面向最终用户
<a name="webrtc-prereqs-users"></a>

最终用户需要以下内容：
+ A WorkSpace 使用 DCV 协议。有关更多信息，请参阅 [WorkSpaces 个人协议](amazon-workspaces-networking.md#amazon-workspaces-protocols)。
**注意**  
目前只有 Windows 支持服务器端的 WebRTC 重定向。 WorkSpaces
+ 来自 [clients.amazon](https://clients.amazonworkspaces.com) workspaces.com 的支持 WorkSpaces 客户端。支持的平台：Windows、macOS、Linux（Ubuntu 22.04 和 Ubuntu 24.04、amd64）和 Web。
+ 通过组策略启用 WebRTC 重定向。启用后，该政策会自动通过注册表在 Chrome 和 Edge 上安装浏览器扩展程序。有关更多信息，请参阅 [在 “ WorkSpaces 个人版” WorkSpaces 中管理你的 Windows](group_policy.md)。如果需要，用户可以手动安装扩展程序：[Chrome](https://chromewebstore.google.com/detail/amazon-dcv-webrtc-redirec/diilpfplcnhehakckkpmcmibmhbingnd) 和 [Edg](https://microsoftedge.microsoft.com/addons/detail/amazon-dcv-webrtc-redirec/kjbbkjjiecchbcdoollhgffghfjnbhef) e。

### 对于开发者
<a name="webrtc-prereqs-devs"></a>

您的 Web 应用程序必须：
+ 使用标准的 WebRTC API（例如 get 和）UserMedia RTCPeerConnection
+ 包括本主题中所述的检测和初始化代码

## 如何集成
<a name="webrtc-how-to-integrate"></a>

集成包括四个步骤：检测重定向环境、覆盖 WebRTC API、映射音频元素和处理重新连接。

### 步骤 1：检测重定向环境
<a name="webrtc-step1-detect"></a>

将此初始化代码添加到您的应用程序中。回调将在代理准备就绪时运行，如果重定向不可用，则在超时之后运行。

```
let proxy = null;

function handleInitCallback(result) {
    if (result.success) {
        // Redirection is available
        proxy = result.proxy;
        console.log('WebRTC redirection enabled, version:', proxy.getVersion());
        console.log('Client info:', proxy.clientInfo);

        // Override WebRTC APIs to use redirection
        proxy.overrideWebRTC();

        // Set up reconnection handling
        setupReconnectionHandling();
    } else {
        // Not in a redirection environment  - use standard WebRTC
        console.log('WebRTC redirection not available:', result.error);
    }
}

// Register the initialization callback
if (globalThis.DCVWebRTCPeerConnectionProxyV2) {
    globalThis.DCVWebRTCPeerConnectionProxyV2.setInitCallback(handleInitCallback, 5000);
}
```

关键点：
+ 检查`globalThis.DCVWebRTCPeerConnectionProxyV2`以检测扩展
+ `setInitCallback()`用你的处理器调用然后超时——建议超时 5000 毫秒
+ 回调接收一个带有`success`布尔值和`proxy`或的结果对象 `error`
+ 调用重定`proxy.overrideWebRTC()`向标准 WebRTC API

### 第 2 步：使用标准的 WebRTC API
<a name="webrtc-step2-apis"></a>

调用后`overrideWebRTC()`，请正常使用 WebRTC 接口。代理会透明地将它们重定向到本地客户端。

**注意**  
目前不支持视频重定向。`video: false`在`getUserMedia`请求中设置。

```
// Get user media  - automatically redirected
const stream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: false   // Video redirection not yet supported
});

// Create peer connection  - automatically redirected
const pc = new RTCPeerConnection(configuration);

// Everything else works as standard WebRTC
pc.addTrack(stream.getAudioTracks()[0], stream);
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
```

如果您需要访问原始浏览器 API（例如，从远程浏览器而不是本地客户端捕获视频），它们将保留在`proxy.overridenApis`：

```
// Get the original getUserMedia (runs in remote browser, not redirected)
const originalGetUserMedia = proxy.overridenApis.get('navigator.mediaDevices.getUserMedia');

// Available original APIs:
// 'navigator.mediaDevices.getUserMedia'
// 'navigator.mediaDevices.addEventListener'
// 'navigator.mediaDevices.removeEventListener'
// 'navigator.mediaDevices.enumerateDevices'
// 'navigator.mediaDevices.getDisplayMedia'
// 'window.RTCPeerConnection'
// 'window.RTCPeerConnection.generateCertificate'
// 'window.AudioContext'
// 'window.Worker' (experimental)

const remoteStream = await originalGetUserMedia.call(navigator.mediaDevices, {
    audio: false,
    video: true
});
```

### 第 3 步：映射音频元素
<a name="webrtc-step3-audio"></a>

要播放音频，`mapAudioElement()`请在设置`srcObject`任何音频元素之前先调用。

```
const audioElement = document.querySelector('audio#remote-audio');

// Map the audio element before setting srcObject
proxy.mapAudioElement(audioElement);

// Now use the audio element normally
audioElement.srcObject = remoteStream;
await audioElement.play();

// Control playback
audioElement.pause();
audioElement.volume = 0.8;
audioElement.muted = false;

// Change output device
await audioElement.setSinkId(deviceId);
```

### 步骤 4：处理重新连接
<a name="webrtc-step4-reconnection"></a>

重定向服务可能会暂时不可用，例如由于网络问题或客户端重新连接。

**重要**  
重新连接后，所有先前创建的代理对象都将失效，因为客户端浏览器上下文已完全重新加载。您必须关闭现有连接并创建新的 WebRTC 对象。

```
function setupReconnectionHandling() {
    proxy.addStatusChangeEventListener((event) => {
        switch (event.status) {
            case 'unavailable':
                console.error('Redirection lost');
                handleRedirectionLost();
                break;
            case 'available':
                console.info('Redirection restored');
                handleRedirectionRestored();
                break;
        }
    });
}

function handleRedirectionLost() {
    // Try to close cleanly  - proxies may throw exceptions after reconnection
    try {
        if (peerConnection) { peerConnection.close(); }
    } catch (error) {
        console.warn('Error closing peer connection (proxy may be invalid):', error);
    }
    showReconnectingMessage();
}

function handleRedirectionRestored() {
    // IMPORTANT: All existing proxy objects are invalid after reconnection.
    // Create new RTCPeerConnection, MediaStream, and other WebRTC objects.
    hideReconnectingMessage();
    restartCall(); // Must create fresh WebRTC objects
}
```

您可以自定义心跳时间：

```
proxy.resetHeartbeat({
    heartbeatTimeoutMs: 5000,      // Time before marking unavailable
    heartbeatIntervalPeriodMs: 500 // How often to check
});
```

## 设备枚举
<a name="webrtc-device-enumeration"></a>

设备更改（例如，用户插入头戴式耳机）会被自动检测到。`makeMediaDevicesProxy()`用于监听本地客户端上的设备更改：

```
const mediaDevices = proxy.makeMediaDevicesProxy();
mediaDevices.ondevicechange = async () => {
    const devices = await navigator.mediaDevices.enumerateDevices();
    updateDeviceList(devices);
};
```

用于`proxy.clientInfo`确定用户从哪个客户端平台进行连接：

```
// clientInfo contains:
// - platform: 'web' | 'windows' | 'macOS' | 'linux'
// - version: SDK version string
// - userAgent: browser user agent string
// - browserDetails: { browser: string, version: string }

switch (proxy.clientInfo.platform) {
    case 'web':     console.log('Connecting from web client'); break;
    case 'windows': console.log('Connecting from Windows native client'); break;
    case 'macOS':   console.log('Connecting from macOS native client'); break;
    case 'linux':   console.log('Connecting from Linux native client'); break;
}
```

## 高级：使用 Web 音频 API 进行音频处理
<a name="webrtc-advanced-audio"></a>

在通过对等连接发送音频流之前，您可以使用 Web Audio API 来处理音频流：

```
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(micStream);
const gainNode = audioContext.createGain();
const destination = audioContext.createMediaStreamDestination();

source.connect(gainNode);
gainNode.connect(destination);

// Control microphone volume
gainNode.gain.value = 0.5; // 50% volume

// Use the processed stream
pc.addTrack(destination.stream.getAudioTracks()[0], destination.stream);
```

## 完整示例
<a name="webrtc-complete-example"></a>

以下示例演示了与初始化、重新连接处理、设备更改检测和呼叫设置的完整集成：

```
let proxy = null;
let peerConnection = null;
let localStream = null;

function initializeRedirection() {
    if (globalThis.DCVWebRTCPeerConnectionProxyV2) {
        globalThis.DCVWebRTCPeerConnectionProxyV2.setInitCallback((result) => {
            if (result.success) {
                proxy = result.proxy;
                proxy.overrideWebRTC();
                proxy.addStatusChangeEventListener(handleStatusChange);
                proxy.resetHeartbeat({ heartbeatTimeoutMs: 5000, heartbeatIntervalPeriodMs: 500 });
                setupDeviceChangeListener();
            } else {
                console.log('Redirection not available:', result.error);
            }
        }, 5000);
    }
}

function handleStatusChange(event) {
    if (event.status === 'unavailable') {
        try {
            if (peerConnection) { peerConnection.close(); peerConnection = null; }
            if (localStream) { localStream.getTracks().forEach(t => t.stop()); localStream = null; }
        } catch (error) {
            console.warn('Error cleaning up (proxies invalid):', error);
            peerConnection = null;
            localStream = null;
        }
    } else if (event.status === 'available') {
        startCall(); // Create fresh WebRTC objects
    }
}

function setupDeviceChangeListener() {
    const mediaDevices = proxy.makeMediaDevicesProxy();
    mediaDevices.ondevicechange = async () => {
        const devices = await navigator.mediaDevices.enumerateDevices();
        updateDeviceUI(devices);
    };
}

async function startCall() {
    try {
        localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
        peerConnection = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
        localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));

        peerConnection.ontrack = (event) => {
            const remoteAudio = document.querySelector('audio#remote');
            if (proxy) { proxy.mapAudioElement(remoteAudio); }
            remoteAudio.srcObject = event.streams[0];
            remoteAudio.play();
        };

        peerConnection.onicecandidate = (event) => {
            if (event.candidate) { sendToSignalingServer({ type: 'ice-candidate', candidate: event.candidate }); }
        };

        const offer = await peerConnection.createOffer();
        await peerConnection.setLocalDescription(offer);
        sendToSignalingServer({ type: 'offer', sdp: offer });
    } catch (error) {
        console.error('Failed to start call:', error);
    }
}

initializeRedirection();
```

## 测试您的集成
<a name="webrtc-testing"></a>

### 没有重定向
<a name="webrtc-testing-without"></a>

当未安装扩展程序、在外部运行或未通过组策略启用 WebRTC 重定向时 WorkSpace，您的应用程序可以正常运行。通过在普通浏览器中打开应用程序进行测试。

### 使用重定向
<a name="webrtc-testing-with"></a>

要在启用重定向的情况下进行测试，请执行以下操作：

1. 创建 WorkSpace 使用 DCV 协议并启用 WebRTC 重定向组策略设置。请参阅[在 “ WorkSpaces 个人版” WorkSpaces 中管理你的 Windows](group_policy.md)。

1. 从 c [lients.amazonworkspaces.com](https://clients.amazonworkspaces.com) 下载并安装 WorkSpaces 客户端，然后连接到。 WorkSpace验证是否安装了 Chrome 或 Edge 扩展程序（设置 GPO 后自动安装）。

1. 在 WorkSpace 浏览器中打开您的 Web 应用程序。在控制台上查看 “WebRTC重定向已启用” 消息。验证本地设备的音频功能，测试设备枚举和切换，并验证重新连接的处理方式。

## 最佳实践
<a name="webrtc-best-practices"></a>

在与 WebRTC 重定向集成时，请遵循以下建议：
+ 务必检查重定向的可用性-永远不要假设代理存在
+ 在创建任何 WebRTC 对象之前，请`overrideWebRTC()`尽早致电
+ 使用前映射音频元素-设置`mapAudioElement()`前调用 `srcObject`
+ 处理重新连接-重新连接后，所有代理对象均无效；请务必创建新的 WebRTC 对象
+ 测试这两种模式-确保您的应用程序在重定向和不使用重定向的情况下都能正常运行
+ 使用标准的 WebRTC API-代理会透明地重定向它们；基本用途不需要自定义 API
+ 清理资源-移除事件侦听器并在完成后关闭连接

## 问题排查
<a name="webrtc-troubleshooting"></a>

### 未检测到重定向
<a name="webrtc-troubleshooting-not-detected"></a>
+ 验证浏览器扩展程序是否已安装并启用（选中`chrome://extensions`）
+ 确认WebRTC重定向组策略已在上启用 WorkSpace
+ 验证您是否在 WorkSpace 使用 DCV 协议内运行
+ 检查浏览器控制台是否有初始化消息
+ 验证 WorkSpaces 客户端版本是否支持 WebRTC 重定向（Windows 5.21.0 或更高版本、macOS 5.31.0 或更高版本、Linux 2026.0 或更高版本或 Web 客户端）

### 音频或视频无法正常工作
<a name="webrtc-troubleshooting-audio-video"></a>
+ 在创建任何 WebRTC 对象之前`overrideWebRTC()`已调用验证
+ 在设置`mapAudioElement()`之前，请验证音频元素是否与之映射 `srcObject`
+ 检查浏览器控制台是否有错误
+ 验证是否已授予设备权限

### 重新连接问题
<a name="webrtc-troubleshooting-reconnection"></a>
+ 实施`addStatusChangeEventListener()`以检测可用性变化
+ 重新连接后，请务必创建新的 RTCPeerConnection 和 MediaStream对象-旧的代理无效并且会抛出
+ 如果检测到不可用的速度太慢或太快，请查看心跳配置

## API 参考
<a name="webrtc-api-reference"></a>

以下总结了代理 API：

```
interface DCVWebRTCProxy {
    // Version and client info
    getVersion(): string;
    clientInfo: {
        platform: 'web' | 'windows' | 'macOS' | 'linux';
        version: string;
        userAgent: string;
        browserDetails: { browser: string; version: string; };
    };

    // Core methods
    overrideWebRTC(): void;
    mapAudioElement(element: HTMLAudioElement): void;

    // Device management
    makeMediaDevicesProxy(): MediaDevices;

    // Status monitoring
    addStatusChangeEventListener(callback: (event: StatusChangeEvent) => void): void;
    resetHeartbeat(config: { heartbeatTimeoutMs: number, heartbeatIntervalPeriodMs: number }): void;

    // Access original (non-redirected) browser APIs
    overridenApis: Map<string, Function>;
}

interface StatusChangeEvent {
    status: 'available' | 'unavailable';
    lastHeartbeat?: number;
}
```