{"version":3,"file":"media-access.cjs","names":[],"sources":["../../src/audio/media-access.ts"],"sourcesContent":["/**\n * What a media error is about.\n *\n * `\"screen\"` is not a device in the `getUserMedia` sense — it is what\n * `getDisplayMedia` captures — but it fails through the same `DOMException` names, so\n * it is classified by the same function rather than by a second copy of the table.\n */\nexport type MediaDeviceKindLabel = \"microphone\" | \"camera\" | \"screen\";\n\n/** Classified reason a capture device could not be acquired. */\nexport type MediaAccessErrorKind =\n    \"unsupported\" | \"insecure\" | \"permission-denied\" | \"not-found\" | \"in-use\" | \"unknown\";\n\n/** A classified capture error with a human-readable, English message. */\nexport interface MediaAccessError {\n    kind: MediaAccessErrorKind;\n    message: string;\n}\n\n/** Sentence fragment naming the device, so one classifier serves both. */\nconst NOUN: Record<MediaDeviceKindLabel, string> = {\n    microphone: \"Microphone\",\n    camera: \"Camera\",\n    screen: \"Screen\",\n};\n\n/**\n * Map an unknown `getUserMedia` failure into a {@link MediaAccessError}.\n *\n * The order matters. A missing `navigator.mediaDevices` is almost never \"this\n * browser cannot do audio\" — it is a page served over plain HTTP, where the whole\n * API is simply absent. Reporting `unsupported` there sends the developer looking\n * for a polyfill for a problem an `https://` URL fixes, so the secure-context check\n * runs first and wins.\n *\n * The `DOMException.name` values are then collapsed into kinds an app can actually\n * branch on, because the raw names do not group the way a UI needs them:\n * `NotFoundError` and `OverconstrainedError` both mean \"you will not get a device\n * with these constraints\", and `NotReadableError` and `AbortError` both mean \"the\n * hardware is busy\" — most often another tab of the same app.\n *\n * @param err - Whatever `getUserMedia` (or a device query) rejected with.\n * @param device - Which device was being opened, for the message.\n * @returns A stable kind plus a message safe to show a user.\n */\nexport function classifyMediaError(\n    err: unknown,\n    device: MediaDeviceKindLabel = \"microphone\",\n): MediaAccessError {\n    const noun = NOUN[device];\n\n    if (typeof window === \"undefined\") {\n        return { kind: \"unsupported\", message: `${noun} is unavailable in this environment.` };\n    }\n    if (!window.isSecureContext) {\n        return {\n            kind: \"insecure\",\n            message: `${noun} access requires a secure (HTTPS) connection.`,\n        };\n    }\n    if (err instanceof DOMException) {\n        switch (err.name) {\n            case \"NotAllowedError\":\n            case \"SecurityError\":\n                return {\n                    kind: \"permission-denied\",\n                    message: `${noun} permission denied. Enable access in your browser settings.`,\n                };\n            case \"NotFoundError\":\n            case \"OverconstrainedError\":\n                return {\n                    kind: \"not-found\",\n                    message: `No ${device} available on this device.`,\n                };\n            case \"NotReadableError\":\n            case \"AbortError\":\n                return {\n                    kind: \"in-use\",\n                    message: `The ${device} is in use by another app. Close it and try again.`,\n                };\n        }\n    }\n    return {\n        kind: \"unknown\",\n        message:\n            err instanceof Error ? err.message : `Unexpected error while accessing the ${device}.`,\n    };\n}\n\n/**\n * Whether `getUserMedia` is reachable at all.\n *\n * Separate from the classifier because a page often wants to hide a \"record\" button\n * entirely rather than render it and fail on click.\n */\nexport function isMediaCaptureSupported(): boolean {\n    return (\n        typeof navigator !== \"undefined\" &&\n        navigator.mediaDevices !== undefined &&\n        typeof navigator.mediaDevices.getUserMedia === \"function\"\n    );\n}\n\n/**\n * The error for \"the API is not here at all\", which is a different question from\n * \"the API rejected\".\n *\n * `classifyMediaError` takes a thrown value; a missing `navigator.mediaDevices` never\n * threw one, and handing it `null` would come back `unknown` — the least actionable\n * kind there is. The two real causes are an insecure page (the fix is a URL) and an\n * engine that genuinely cannot capture (the fix is another browser), and telling them\n * apart is the whole point.\n *\n * @param device - Which device was being opened, for the message.\n */\nexport function missingCaptureApiError(\n    device: MediaDeviceKindLabel = \"microphone\",\n): MediaAccessError {\n    if (typeof window !== \"undefined\" && !window.isSecureContext) {\n        return classifyMediaError(null, device);\n    }\n    return {\n        kind: \"unsupported\",\n        message: `${NOUN[device]} capture is not supported in this browser.`,\n    };\n}\n"],"mappings":"AAoBA,IAAM,EAA6C,CAC/C,WAAY,aACZ,OAAQ,SACR,OAAQ,QACZ,EAqBA,SAAgB,EACZ,EACA,EAA+B,aACf,CAChB,IAAM,EAAO,EAAK,GAElB,GAAI,OAAO,OAAW,IAClB,MAAO,CAAE,KAAM,cAAe,QAAS,GAAG,EAAK,qCAAsC,EAEzF,GAAI,CAAC,OAAO,gBACR,MAAO,CACH,KAAM,WACN,QAAS,GAAG,EAAK,8CACrB,EAEJ,GAAI,aAAe,aACf,OAAQ,EAAI,KAAZ,CACI,IAAK,kBACL,IAAK,gBACD,MAAO,CACH,KAAM,oBACN,QAAS,GAAG,EAAK,4DACrB,EACJ,IAAK,gBACL,IAAK,uBACD,MAAO,CACH,KAAM,YACN,QAAS,MAAM,EAAO,2BAC1B,EACJ,IAAK,mBACL,IAAK,aACD,MAAO,CACH,KAAM,SACN,QAAS,OAAO,EAAO,mDAC3B,CACR,CAEJ,MAAO,CACH,KAAM,UACN,QACI,aAAe,MAAQ,EAAI,QAAU,wCAAwC,EAAO,EAC5F,CACJ,CAQA,SAAgB,GAAmC,CAC/C,OACI,OAAO,UAAc,KACrB,UAAU,eAAiB,IAAA,IAC3B,OAAO,UAAU,aAAa,cAAiB,UAEvD,CAcA,SAAgB,EACZ,EAA+B,aACf,CAIhB,OAHI,OAAO,OAAW,KAAe,CAAC,OAAO,gBAClC,EAAmB,KAAM,CAAM,EAEnC,CACH,KAAM,cACN,QAAS,GAAG,EAAK,GAAQ,2CAC7B,CACJ"}