diff --git a/browser/actors/WebRTCParent.jsm b/browser/actors/WebRTCParent.jsm
index 11782c26135814c09040b60d482433035852ce4a..c7e9331d2ec710b3b2293ab76e5f059e0558f0a7 100644
--- a/browser/actors/WebRTCParent.jsm
+++ b/browser/actors/WebRTCParent.jsm
@@ -380,8 +380,36 @@ class WebRTCParent extends JSWindowActorParent {
     if (aRequest.sharingScreen) {
       return false;
     }
-    if (aRequest.audioOutputDevices?.length) {
-      return false;
+    let {
+      callID,
+      windowID,
+      audioInputDevices,
+      videoInputDevices,
+      audioOutputDevices,
+      hasInherentAudioConstraints,
+      hasInherentVideoConstraints,
+      audioOutputId,
+    } = aRequest;
+
+    if (audioOutputDevices?.length) {
+      // Prompt if a specific device is not requested, available and allowed.
+      let device = audioOutputDevices.find(({ id }) => id == audioOutputId);
+      if (
+        !device ||
+        !lazy.SitePermissions.getForPrincipal(
+          aPrincipal,
+          ["speaker", device.id].join("^"),
+          this.getBrowser()
+        ).state == lazy.SitePermissions.ALLOW
+      ) {
+        return false;
+      }
+      this.sendAsyncMessage("webrtc:Allow", {
+        callID,
+        windowID,
+        devices: [device.deviceIndex],
+      });
+      return true;
     }
 
     let { perms } = Services;
@@ -400,15 +428,6 @@ class WebRTCParent extends JSWindowActorParent {
       aRequest.secondOrigin;
 
     let map = lazy.webrtcUI.activePerms.get(this.manager.outerWindowId);
-    let {
-      callID,
-      windowID,
-      audioInputDevices,
-      videoInputDevices,
-      hasInherentAudioConstraints,
-      hasInherentVideoConstraints,
-    } = aRequest;
-
     // We consider a camera or mic active if it is active or was active within a
     // grace period of milliseconds ago.
     const isAllowed = ({ mediaSource, rawId }, permissionID) =>
@@ -1149,6 +1168,14 @@ function prompt(aActor, aBrowser, aRequest) {
           let allowSpeaker = audioDeviceIndex != "-1";
           if (allowSpeaker) {
             allowedDevices.push(audioDeviceIndex);
+            let { id } = audioOutputDevices.find(
+              ({ deviceIndex }) => deviceIndex == audioDeviceIndex
+            );
+            lazy.SitePermissions.setForPrincipal(
+              principal,
+              ["speaker", id].join("^"),
+              lazy.SitePermissions.ALLOW
+            );
           }
         }
 
diff --git a/browser/base/content/test/webrtc/browser_devices_select_audio_output.js b/browser/base/content/test/webrtc/browser_devices_select_audio_output.js
index 9b6f3538909545c9f921aafff53387e871289804..ceafc3269a71775c94e801f27daf464b607e07f7 100644
--- a/browser/base/content/test/webrtc/browser_devices_select_audio_output.js
+++ b/browser/base/content/test/webrtc/browser_devices_select_audio_output.js
@@ -8,12 +8,18 @@ const permissionError =
   "error: NotAllowedError: The request is not allowed " +
   "by the user agent or the platform in the current context.";
 
-async function requestAudioOutputExpectingPrompt() {
+async function requestAudioOutput(options) {
   await Promise.all([
-    promisePopupNotificationShown("webRTC-shareDevices"),
     expectObserverCalled("getUserMedia:request"),
     expectObserverCalled("recording-window-ended"),
-    promiseRequestAudioOutput(),
+    promiseRequestAudioOutput(options),
+  ]);
+}
+
+async function requestAudioOutputExpectingPrompt(options) {
+  await Promise.all([
+    promisePopupNotificationShown("webRTC-shareDevices"),
+    requestAudioOutput(options),
   ]);
 
   is(
@@ -54,22 +60,26 @@ async function simulateAudioOutputRequest(options) {
   );
 }
 
-async function allow() {
+async function allowPrompt() {
   const observerPromise = expectObserverCalled("getUserMedia:response:allow");
-  await promiseMessage("ok", () => {
-    PopupNotifications.panel.firstElementChild.button.click();
-  });
+  PopupNotifications.panel.firstElementChild.button.click();
   await observerPromise;
 }
 
-async function deny() {
+async function allow() {
+  await Promise.all([promiseMessage("ok"), allowPrompt()]);
+}
+
+async function denyPrompt() {
   const observerPromise = expectObserverCalled("getUserMedia:response:deny");
-  await promiseMessage(permissionError, () => {
-    activateSecondaryAction(kActionDeny);
-  });
+  activateSecondaryAction(kActionDeny);
   await observerPromise;
 }
 
+async function deny() {
+  await Promise.all([promiseMessage(permissionError), denyPrompt()]);
+}
+
 async function escapePrompt() {
   const observerPromise = expectObserverCalled("getUserMedia:response:deny");
   EventUtils.synthesizeKey("KEY_Escape");
@@ -82,13 +92,27 @@ async function escape() {
 
 var gTests = [
   {
-    desc: 'User clicks "Allow"',
+    desc: 'User clicks "Allow" and revokes',
     run: async function checkAllow() {
       await requestAudioOutputExpectingPrompt();
       await allow();
+
       info("selectAudioOutput() with no deviceId again should prompt again.");
       await requestAudioOutputExpectingPrompt();
       await allow();
+
+      info("selectAudioOutput() with same deviceId should not prompt again.");
+      await Promise.all([
+        expectObserverCalled("getUserMedia:response:allow"),
+        promiseMessage("ok"),
+        requestAudioOutput({ requestSameDevice: true }),
+      ]);
+
+      await revokePermission("speaker", true);
+      info("Same deviceId should prompt again after revoked permission.");
+      await requestAudioOutputExpectingPrompt({ requestSameDevice: true });
+      await allow();
+      await revokePermission("speaker", true);
     },
   },
   {
@@ -106,6 +130,7 @@ var gTests = [
       info("selectAudioOutput() after Esc should prompt again.");
       await requestAudioOutputExpectingPrompt();
       await allow();
+      await revokePermission("speaker", true);
     },
   },
   {
@@ -122,16 +147,39 @@ var gTests = [
   {
     desc: "Multi Device with deviceId",
     run: async function checkMulti() {
+      const deviceCount = 4;
       await Promise.all([
         promisePopupNotificationShown("webRTC-shareDevices"),
-        simulateAudioOutputRequest({ deviceCount: 4, deviceId: "id 2" }),
+        simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
       ]);
       const selectorList = document.getElementById(
         `webRTC-selectSpeaker-menulist`
       );
       is(selectorList.selectedIndex, 2, "pre-selected index");
       checkDeviceSelectors(["speaker"]);
+      await allowPrompt();
+
+      info("Expect same-device request allowed without prompt");
+      await Promise.all([
+        expectObserverCalled("getUserMedia:response:allow"),
+        simulateAudioOutputRequest({ deviceCount, deviceId: "id 2" }),
+      ]);
+
+      info("Expect prompt for different-device request");
+      await Promise.all([
+        promisePopupNotificationShown("webRTC-shareDevices"),
+        simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
+      ]);
+      await denyPrompt();
+
+      info("Expect prompt again for denied-device request");
+      await Promise.all([
+        promisePopupNotificationShown("webRTC-shareDevices"),
+        simulateAudioOutputRequest({ deviceCount, deviceId: "id 1" }),
+      ]);
       await escapePrompt();
+
+      await revokePermission("speaker", true);
     },
   },
   {
diff --git a/browser/base/content/test/webrtc/get_user_media.html b/browser/base/content/test/webrtc/get_user_media.html
index 3692373fa472e547c41350ad8d4388cc2b146289..844c6428ccd319d2486d6c7fa27d785a22014f2b 100644
--- a/browser/base/content/test/webrtc/get_user_media.html
+++ b/browser/base/content/test/webrtc/get_user_media.html
@@ -74,10 +74,15 @@ async function requestDevice(aAudio, aVideo, aShare, aBadDevice = false) {
   }
 }
 
-async function requestAudioOutput() {
+let selectedAudioOutputId;
+async function requestAudioOutput(options = {}) {
+  const audioOutputOptions = options.requestSameDevice && {
+    deviceId: selectedAudioOutputId,
+  };
   SpecialPowers.wrap(document).notifyUserGestureActivation();
   try {
-    await navigator.mediaDevices.selectAudioOutput();
+    ({ deviceId: selectedAudioOutputId } =
+     await navigator.mediaDevices.selectAudioOutput(audioOutputOptions));
     message("ok");
   } catch (err) {
     message("error: " + err);
diff --git a/browser/base/content/test/webrtc/head.js b/browser/base/content/test/webrtc/head.js
index 456ee77a67b19314d10600f24d361f89b7df8939..484af268ec41e3caf87f8b249d56a648b071a3dc 100644
--- a/browser/base/content/test/webrtc/head.js
+++ b/browser/base/content/test/webrtc/head.js
@@ -705,12 +705,12 @@ async function promiseRequestDevice(
   );
 }
 
-async function promiseRequestAudioOutput() {
+async function promiseRequestAudioOutput(options) {
   info("requesting audio output");
   const bc = gBrowser.selectedBrowser;
-  return SpecialPowers.spawn(bc, [], async function() {
+  return SpecialPowers.spawn(bc, [options], async function(opts) {
     const global = content.wrappedJSObject;
-    global.requestAudioOutput();
+    global.requestAudioOutput(Cu.cloneInto(opts, content));
   });
 }