# Web MIDI API

> navigator.requestMIDIAccess() 请求访问 MIDI 输入与输出端口。本页说明选项、权限失败、设备事件，以及没有 MIDI 时的回退方案。

Web MIDI API 让页面在取得权限后访问 MIDI 输入与输出端口；`navigator.requestMIDIAccess()` 返回 `MIDIAccess` 对象的 Promise，对象的 `inputs` 和 `outputs` 集合保存可用端口。

## 支持范围

BCD 记录桌面版 Chrome 43 和 Firefox 108 起提供 `requestMIDIAccess()`。Firefox 条目注明需要站点权限附加组件；Firefox Android 与 Safari 记为不支持（BCD `api.Navigator.requestMIDIAccess`）。

## 语法

```js
navigator.requestMIDIAccess()
navigator.requestMIDIAccess(options)
```

此方法只在安全上下文中暴露；浏览器可以询问权限，也可以采用之前保存的选择，Promise 兑现后得到 `MIDIAccess` 对象。

## 参数

可选的 `options` 对象控制普通硬件 MIDI 端口以外的访问。

| 成员 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `sysex` | `boolean` | `false` | 请求收发系统独占消息的权限。 |
| `software` | `boolean` | `false` | 请求访问已安装的软件合成器。 |

`MIDIAccess.inputs` 和 `MIDIAccess.outputs` 是只读映射，分别保存 `MIDIInput` 与 `MIDIOutput` 端口。端口连接或状态改变时会触发 `statechange` 事件。

## 异常

[`requestMIDIAccess()`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMIDIAccess)（MDN）记载了返回的 Promise 会因以下 `DOMException` 拒绝。

| 异常 | 条件 |
|---|---|
| `AbortError` | 导航关闭了文档或页面。 |
| `InvalidStateError` | 底层 MIDI 系统报告错误。 |
| `NotSupportedError` | 系统不支持请求的功能或选项。 |
| `NotAllowedError` | 用户或系统拒绝访问，或 Permissions Policy 不允许访问。 |

:::caution
Web MIDI 需要安全上下文，并受 `midi` Permissions Policy 控制。页面还须取得用户明确授权，或已有保存的授权。Web MIDI 规范要求：用户或安全设置拒绝访问，或文档不允许使用此功能时，`requestMIDIAccess()` 必须以名为 `NotAllowedError` 的 `DOMException` 拒绝（Web MIDI API 的 [`requestMIDIAccess()` 失败步骤](https://webaudio.github.io/web-midi-api/#dom-navigator-requestmidiaccess)）。
:::

## 示例

请求访问前先检查方法是否存在。方法缺失或请求失败时，保留不依赖 MIDI 的控件。

### 报告访问结果并回退到键盘

下面的代码创建 MIDI 按钮、状态文字和屏幕键盘。没有 MIDI 访问权限时，音符按钮仍可使用。

```js
const midiButton = document.body.appendChild(document.createElement("button"));
const status = document.body.appendChild(document.createElement("p"));
const keyboard = document.body.appendChild(document.createElement("div"));
midiButton.textContent = "连接 MIDI";
keyboard.setAttribute("role", "group");
keyboard.setAttribute("aria-label", "屏幕键盘");

for (const note of ["C4", "D4", "E4", "F4", "G4"]) {
  const key = keyboard.appendChild(document.createElement("button"));
  key.textContent = note;
  key.addEventListener("click", () => {
    status.textContent = `屏幕键盘音符：${note}`;
  });
}

if (!("requestMIDIAccess" in navigator)) {
  midiButton.disabled = true;
  status.textContent = "MIDI 不可用，请使用屏幕键盘。";
} else {
  midiButton.addEventListener("click", async () => {
    midiButton.disabled = true;
    try {
      const access = await navigator.requestMIDIAccess();
      status.textContent = `MIDI 输入数：${access.inputs.size}`;
    } catch (error) {
      status.textContent = `MIDI 访问失败：${error.name}。请使用屏幕键盘。`;
    }
  });
}
```

不支持与请求被拒绝两条路径都会保留可用的音符按钮，并禁用 MIDI 按钮。

### 读取已连接输入端口的消息

取得权限后，给返回映射中的每个输入端口添加监听器。事件的 `data` 值保存 MIDI 消息字节。

```js
async function listenForMidi(onMessage) {
  if (!("requestMIDIAccess" in navigator)) return false;

  const access = await navigator.requestMIDIAccess();
  for (const input of access.inputs.values()) {
    input.addEventListener("midimessage", (event) => onMessage(event.data));
  }
  return true;
}

listenForMidi((bytes) => console.log(Array.from(bytes))).catch(console.error);
```

API 缺失时函数返回 `false`，调用方可以据此启用屏幕乐器。

## 另请参阅

- [WebHID API](/zh/reference/capabilities/web-hid/)，用于暴露 HID 报告的设备
- [WebUSB API](/zh/reference/capabilities/web-usb/)，用于通过厂商接口访问的 USB 设备
- [Web MIDI API](https://webaudio.github.io/web-midi-api/)（webaudio.github.io）
- [Navigator: requestMIDIAccess()](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMIDIAccess)（MDN）