Skip to content
academia.sh

Lesson 12 / 24

Media and Device APIs

Access to capabilities that require permission; permission's states and preconditions, the secure-context and user-gesture requirements, feature detection and graceful degradation, media tracks' lifecycle, and the privacy balance of location access.

Contents

Locale, time zone, and language preference get read from the user’s device without asking permission; these are information the page already knows. There are other things the measurement station page will want to ask for: the coordinates of the point a measurement got taken at, a photo to attach to a fault report, a voice note.

None of these get given without asking. What they have in common is that they carry information out of the user’s device. The browser puts these capabilities behind a common permission mechanism that, once learned, applies to all of them.

Capabilities That Require Permission

Capabilities split into three sets.

Those that require no permission are the device’s general properties that do not identify the user: viewport dimensions, language preference, time zone, whether the connection is online. These get read without asking.

Those that require explicit permission are the ones that collect data from the user’s surroundings: camera, microphone, location, screen sharing, motion and orientation sensors, reading the clipboard, sending notifications.

Those limited by user gesture are capabilities that do not ask permission but cannot get triggered on their own: going fullscreen, prompting a file selection, keeping the screen awake, writing to the clipboard. These only run while an event arising from a user action gets processed.

A capability can also be closed from the very start. The permissions-policy declaration defined in the Embedded Content lesson determines which capabilities get delegated to a frame; an undelegated capability does not work even with user consent.

Permission’s States

Permission sits in one of three states: not yet asked, granted, denied. When a request gets made, the browser checks the preconditions first, then looks at the state.

// permission.mjs — the permission state machine and a request's preconditions
function request(state, env) {
  if (env.secureContext === false)
    return { state, result: "error: no secure context, not asked" };
  if (state === "granted") return { state, result: "granted directly, not asked" };
  if (state === "denied") return { state, result: "denied, not asked again" };
  if (env.userGesture === false)
    return { state, result: "error: no user gesture, not asked" };
  return { state: env.response, result: `asked the user -> ${env.response}` };
}

const scenarios = [
  ["insecure context", { secureContext: false, userGesture: true, response: "granted" }],
  ["gestureless call", { secureContext: true, userGesture: false, response: "granted" }],
  ["user dismissed", { secureContext: true, userGesture: true, response: "not-asked" }],
  ["user granted", { secureContext: true, userGesture: true, response: "granted" }],
  ["second request", { secureContext: true, userGesture: true, response: "granted" }],
];

let state = "not-asked";
console.log("initial state:", state);
for (const [label, env] of scenarios) {
  const result = request(state, env);
  state = result.state;
  console.log(label.padEnd(18), "->", result.result.padEnd(38), "| state:", state);
}

// When the user revokes permission from settings, the state changes; the page only learns this by asking.
state = "denied";
console.log("permission revoked ->", request(state, { secureContext: true, userGesture: true, response: "granted" }).result);
initial state: not-asked
insecure context   -> error: no secure context, not asked    | state: not-asked
gestureless call   -> error: no user gesture, not asked      | state: not-asked
user dismissed     -> asked the user -> not-asked            | state: not-asked
user granted       -> asked the user -> granted              | state: granted
second request     -> granted directly, not asked            | state: granted
permission revoked -> denied, not asked again

Five rules come together in this output.

A secure context is required. A page served unencrypted cannot request these capabilities; without the privacy guarantee defined in the Role of HTTPS lesson, data coming from the camera would travel exposed over the network. The local development address is a defined exception to this rule.

A user gesture is required. Code that asks for permission the moment the page loads can get rejected by the browser. The request gets made while processing the event started by the user pressing a button.

Dismissing is not denying. When the user closes the prompt without answering it, the state stays “not asked”; the next request can ask again. This also opens the door to insistent asking, which the browser blocks on its own once abused.

Granted permission does not get asked again. The second request resolves without bothering the user.

Denied permission does not get asked again either. The request fails directly; what gets shown to the user is not a question, but an explanation of how to enable it in the browser’s settings.

Permission state can also get queried: what the state is can get learned without making a request, and a notification can get received when the state changes. This makes it possible to update the interface when the user revokes permission from settings.

Feature Detection and Graceful Degradation

A capability being absent and being denied are separate situations and get handled separately.

Feature detection is testing at runtime whether the relevant interface exists. Code that checks the browser’s name or version is the wrong path: testing the name does not test the capability. The test checks for the presence of the object or method that will get used; if negative, the interface hides that option entirely.

These three situations have different responses. If the capability is absent, the option does not get shown. If it exists but permission got denied, the option shows with an explanation of how to enable it. If the capability exists, permission exists, but the operation failed, an error gets reported and can get retried.

The common rule in every case is this: the page has to do its job even without the capability. If it cannot read location, it asks the user to type the coordinate; if it cannot open the camera, it prompts a file selection. A design with only a capability-dependent path leaves a declining user outside the page.

Media Tracks and Releasing the Device

A camera or microphone request returns a stream. A stream consists of tracks, each tied to a single source, and releasing the source depends on stopping the tracks.

// stream.mjs — media track lifecycle and releasing the device
const devices = { camera: 0, microphone: 0 };   // how many tracks are using the device

const track = (kind, device, settings) => {
  devices[device] += 1;
  return {
    kind, device, settings, live: true,
    stop() { if (this.live) { this.live = false; devices[device] -= 1; } },
  };
};

function getStream(constraints) {
  const tracks = [];
  if (constraints.video) tracks.push(track("video", "camera", { width: 1280, height: 720 }));
  if (constraints.audio) tracks.push(track("audio", "microphone", { echoCancellation: true }));
  return { tracks, stop() { for (const t of this.tracks) t.stop(); } };
}

const log = (label) =>
  console.log(label.padEnd(24), "camera:", devices.camera, "| microphone:", devices.microphone);

log("start");
const stream = getStream({ video: true, audio: true });
log("stream acquired");
console.log("tracks:", stream.tracks.map((t) => `${t.kind}(${JSON.stringify(t.settings)})`).join(" "));

stream.tracks[0].stop();                        // only the video track gets stopped
log("video track stopped");
stream.tracks[0].stop();                        // a second stop has no effect
log("same track stopped again");
stream.stop();
log("stream stopped");
console.log("track states:", stream.tracks.map((t) => `${t.kind}=${t.live ? "live" : "ended"}`).join(" "));
start                    camera: 0 | microphone: 0
stream acquired          camera: 1 | microphone: 1
tracks: video({"width":1280,"height":720}) audio({"echoCancellation":true})
video track stopped      camera: 0 | microphone: 1
same track stopped again camera: 0 | microphone: 1
stream stopped           camera: 0 | microphone: 0
track states: video=ended audio=ended

The fourth line shows the most common mistake. Clearing a video element’s source or removing the element from the tree does not stop the track; the recording indicator keeps glowing, and the user thinks the page is still listening. The device only gets released once every track gets stopped individually. Stopping is idempotent: a second call does not break the count.

The constraints given during the request declare settings like the requested resolution and audio processing. These are a wish, not a guarantee: the actual settings the device provides get read back from the track and may differ. Giving a constraint in mandatory form means the request fails entirely if it cannot get met.

Location and Sensors

Location access has two forms: a one-time read and continuous watching. Watching produces a notification as the location changes and continues until it gets explicitly released; watching that does not get released drains the battery.

A high-accuracy request declared in the request can bring satellite-based positioning into play; this is slower and more expensive. The accuracy needed to know which valley a point is in differs from the accuracy needed to know which side of a sidewalk someone stands on; accuracy that is not needed does not get requested.

The incoming location has an accuracy radius, and this value has to get reflected in the interface; showing a single point claims a precision that is not available. Timeout and cached-reading age also get declared in the request: whether a stale reading is acceptable, and how long to wait.

Motion and orientation sensors sit within a similar framework, and also require permission on some platforms. A use like recording the device’s tilt while taking a measurement has to get weighed against sensor data’s potential to identify the user.

The Privacy Contract

These capabilities’ shared contract has four articles. Permission gets requested in context: the moment the user understands why, when they press the relevant button. The data collected is minimal: if location gets attached to a measurement, the station name may be enough instead of the exact coordinate. Access is visible: the interface shows it while the capability is in use. And access gets released: once the job is done, tracks get stopped and watches get ended.

Summary

  • Capabilities split into sets that require no permission, require explicit permission, and get limited by user gesture; a capability not delegated to frames does not open even with permission.
  • A permission request depends on the secure-context and user-gesture preconditions; granted and denied permissions do not get asked again, while a dismissed prompt does not change the state.
  • Feature detection checks for the interface’s presence, not the browser’s name; capability absence, permission denial, and operation failure each require a different response.
  • The page has to do its job even without the capability; a design that leaves only a path dependent on the capability leaves a declining user outside.
  • The device only gets released once every track in the stream gets stopped; removing the element from the tree does not stop the track.
  • Requesting accuracy for location has a cost, and the incoming reading’s accuracy radius gets reflected in the interface; watching gets ended explicitly.

Next Step

Most of this lesson’s capabilities produce heavy data: frames coming from the camera, sensor readings, thousands of rows in a selected measurement file. Processing these on the main thread exhausts the Timing and the Paint Cycle lesson’s frame budget in a single call, and the interface stops responding. Splitting the work into chunks spreads out the delay but does not shorten the total time. The next lesson takes up web workers, which move the computation outside the main thread, message passing between them, and the cost of this separation.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close