Skip to content

Commit 2f31ecf

Browse files
authored
Add commands for focusing audible/muted tabs and for muting/unmuting tabs (#311)
* Add command to focus the last tab that emitted sound * Add command to focus the next tab that is reproducing sound * Add command to focus the next muted tab * Add command to focus next tab that is playing audio * Add several commands for muting or unmuting tabs
1 parent abca9f8 commit 2f31ecf

File tree

7 files changed

+232
-0
lines changed

7 files changed

+232
-0
lines changed
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import browser from "webextension-polyfill";
2+
import { notify } from "../utils/notify";
3+
import { getNextTabByIndex } from "../utils/tabUtils";
4+
5+
let tabLastSounded: number | undefined;
6+
7+
export function setTabLastSounded(tabId: number) {
8+
tabLastSounded = tabId;
9+
}
10+
11+
export async function focusTabLastSounded() {
12+
if (!tabLastSounded)
13+
return notify("No tab has emitted sound since startup.", {
14+
type: "warning",
15+
});
16+
17+
const tab = await browser.tabs.get(tabLastSounded);
18+
await browser.windows.update(tab.windowId!, { focused: true });
19+
await browser.tabs.update(tabLastSounded, { active: true });
20+
}
21+
22+
export async function focusNextTabWithSound() {
23+
const tabsWithSound = await browser.tabs.query({
24+
audible: true,
25+
muted: false,
26+
});
27+
28+
const nextTabWithSound = await getNextTabByIndex(tabsWithSound);
29+
if (!nextTabWithSound) return;
30+
31+
await browser.windows.update(nextTabWithSound.windowId!, { focused: true });
32+
await browser.tabs.update(nextTabWithSound.id, { active: true });
33+
}
34+
35+
export async function focusNextMutedTab() {
36+
const mutedTabs = await browser.tabs.query({ muted: true });
37+
const nextMutedTab = await getNextTabByIndex(mutedTabs);
38+
if (!nextMutedTab) return;
39+
40+
await browser.windows.update(nextMutedTab.windowId!, { focused: true });
41+
await browser.tabs.update(nextMutedTab.id, { active: true });
42+
}
43+
44+
export async function focusNextAudibleTab() {
45+
const audibleTabs = await browser.tabs.query({ audible: true });
46+
const nextAudibleTab = await getNextTabByIndex(audibleTabs);
47+
if (!nextAudibleTab) return;
48+
49+
await browser.windows.update(nextAudibleTab.windowId!, { focused: true });
50+
await browser.tabs.update(nextAudibleTab.id, { active: true });
51+
}

src/background/actions/muteTabs.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import browser from "webextension-polyfill";
2+
import { getCurrentTabId } from "../utils/getCurrentTab";
3+
import { getTabIdForMarker } from "../misc/tabMarkers";
4+
import { getNextTabByIndex } from "../utils/tabUtils";
5+
import { notify } from "../utils/notify";
6+
7+
export async function muteTab(tabMarkers?: string[], mute = true) {
8+
if (tabMarkers) {
9+
const tabsToMute = await Promise.all(tabMarkers.map(getTabIdForMarker));
10+
11+
return Promise.all(
12+
tabsToMute.map(async (tabId) =>
13+
browser.tabs.update(tabId, { muted: mute })
14+
)
15+
);
16+
}
17+
18+
const tabToMute = await getCurrentTabId();
19+
return browser.tabs.update(tabToMute, { muted: mute });
20+
}
21+
22+
export async function muteNextTabWithSound() {
23+
const tabsWithSound = await browser.tabs.query({
24+
audible: true,
25+
muted: false,
26+
});
27+
28+
const nextTabWithSound = await getNextTabByIndex(tabsWithSound);
29+
if (!nextTabWithSound)
30+
return notify("There are currently no tabs with sound", {
31+
type: "warning",
32+
});
33+
34+
await browser.tabs.update(nextTabWithSound.id, { muted: true });
35+
}
36+
37+
export async function unmuteNextMutedTab() {
38+
const mutedTabs = await browser.tabs.query({ muted: true });
39+
const nextMutedTab = await getNextTabByIndex(mutedTabs);
40+
if (!nextMutedTab)
41+
return notify("There are currently no muted tabs", {
42+
type: "warning",
43+
});
44+
45+
await browser.tabs.update(nextMutedTab.id, { muted: false });
46+
}
47+
48+
export async function muteAllTabsWithSound() {
49+
const tabsWithSound = await browser.tabs.query({
50+
audible: true,
51+
muted: false,
52+
});
53+
54+
await Promise.all(
55+
tabsWithSound.map(async (tab) =>
56+
browser.tabs.update(tab.id, { muted: true })
57+
)
58+
);
59+
}
60+
61+
export async function unmuteAllMutedTabs() {
62+
const mutedTabs = await browser.tabs.query({ muted: true });
63+
64+
await Promise.all(
65+
mutedTabs.map(async (tab) => browser.tabs.update(tab.id, { muted: false }))
66+
);
67+
}

src/background/commands/dispatchCommand.ts

+12
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ const backgroundCommands = new Set<RangoAction["type"]>([
4343
"focusOrCreateTabByUrl",
4444
"focusTabByText",
4545
"cycleTabsByText",
46+
"focusNextTabWithSound",
47+
"focusNextMutedTab",
48+
"focusNextAudibleTab",
49+
"focusTabLastSounded",
50+
"muteCurrentTab",
51+
"unmuteCurrentTab",
52+
"muteTab",
53+
"unmuteTab",
54+
"muteNextTabWithSound",
55+
"unmuteNextMutedTab",
56+
"muteAllTabsWithSound",
57+
"unmuteAllMutedTabs",
4658
]);
4759

4860
export async function dispatchCommand(

src/background/commands/runBackgroundCommand.ts

+61
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ import { refreshTabMarkers } from "../misc/tabMarkers";
2121
import { getCurrentTab } from "../utils/getCurrentTab";
2222
import { notifySettingRemoved } from "../utils/notify";
2323
import { closeTab } from "../actions/closeTab";
24+
import {
25+
focusNextAudibleTab,
26+
focusNextMutedTab,
27+
focusNextTabWithSound,
28+
focusTabLastSounded,
29+
} from "../actions/focusTabBySound";
30+
import {
31+
muteAllTabsWithSound,
32+
muteNextTabWithSound,
33+
muteTab,
34+
unmuteAllMutedTabs,
35+
unmuteNextMutedTab,
36+
} from "../actions/muteTabs";
2437

2538
export async function runBackgroundCommand(
2639
command: RangoAction
@@ -147,6 +160,54 @@ export async function runBackgroundCommand(
147160
await focusPreviousTab();
148161
break;
149162

163+
case "focusNextTabWithSound":
164+
await focusNextTabWithSound();
165+
break;
166+
167+
case "focusNextMutedTab":
168+
await focusNextMutedTab();
169+
break;
170+
171+
case "focusNextAudibleTab":
172+
await focusNextAudibleTab();
173+
break;
174+
175+
case "focusTabLastSounded":
176+
await focusTabLastSounded();
177+
break;
178+
179+
case "muteCurrentTab":
180+
await muteTab();
181+
break;
182+
183+
case "unmuteCurrentTab":
184+
await muteTab(undefined, false);
185+
break;
186+
187+
case "muteTab":
188+
await muteTab(command.target);
189+
break;
190+
191+
case "unmuteTab":
192+
await muteTab(command.target, false);
193+
break;
194+
195+
case "muteNextTabWithSound":
196+
await muteNextTabWithSound();
197+
break;
198+
199+
case "unmuteNextMutedTab":
200+
await unmuteNextMutedTab();
201+
break;
202+
203+
case "muteAllTabsWithSound":
204+
await muteAllTabsWithSound();
205+
break;
206+
207+
case "unmuteAllMutedTabs":
208+
await unmuteAllMutedTabs();
209+
break;
210+
150211
case "copyLocationProperty":
151212
if (currentTab) {
152213
return copyLocationProperty(currentTab, command.arg);

src/background/setup/initBackgroundScript.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import browser from "webextension-polyfill";
22
import { retrieve, store } from "../../common/storage";
33
import { urls } from "../../common/urls";
4+
import { setTabLastSounded } from "../actions/focusTabBySound";
45
import { watchNavigation } from "../hints/watchNavigation";
56
import { sendRequestToContent } from "../messaging/sendRequestToContent";
67
import { createContextMenus } from "../misc/createContextMenus";
@@ -117,3 +118,10 @@ browser.bookmarks?.onCreated.addListener(resetBookmarkTitle);
117118
// the title of the bookmark will be changed again to the value of the input
118119
// field of the popup window.
119120
browser.bookmarks?.onChanged.addListener(resetBookmarkTitle);
121+
122+
browser.tabs.onUpdated.addListener(async (tabId, { audible }) => {
123+
if (audible === true) {
124+
const tab = await browser.tabs.get(tabId);
125+
if (!tab.mutedInfo?.muted) setTabLastSounded(tabId);
126+
}
127+
});

src/background/utils/tabUtils.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import browser from "webextension-polyfill";
2+
import { getCurrentTab } from "./getCurrentTab";
3+
4+
/**
5+
* Given an array of tabs as a parameter, return the first tab in the array that
6+
* has a greater index than the current tab. If no such tab exists in the
7+
* current window cycle through all existing windows returning to the start of
8+
* the current window if necessary.
9+
*/
10+
export async function getNextTabByIndex(tabs: browser.Tabs.Tab[]) {
11+
const currentTab = await getCurrentTab();
12+
13+
return (
14+
tabs.find(
15+
(tab) =>
16+
(tab.windowId === currentTab.windowId &&
17+
tab.index > currentTab.index) ||
18+
tab.windowId !== currentTab.windowId
19+
) ?? tabs[0]
20+
);
21+
}

src/typings/RangoAction.ts

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ interface RangoActionWithoutTargetWithoutArg {
1212
| "moveCurrentTabToNewWindow"
1313
| "focusPreviousTab"
1414
| "focusFirstInput"
15+
| "focusNextTabWithSound"
16+
| "focusNextMutedTab"
17+
| "focusNextAudibleTab"
18+
| "focusTabLastSounded"
19+
| "muteCurrentTab"
20+
| "unmuteCurrentTab"
21+
| "muteNextTabWithSound"
22+
| "unmuteNextMutedTab"
23+
| "muteAllTabsWithSound"
24+
| "unmuteAllMutedTabs"
1525
| "unhoverAll"
1626
| "copyCurrentTabMarkdownUrl"
1727
| "getBareTitle"
@@ -103,6 +113,8 @@ interface RangoActionWithoutTargetWithOptionalNumberArg {
103113
export interface RangoActionWithTargets {
104114
type:
105115
| "activateTab"
116+
| "muteTab"
117+
| "unmuteTab"
106118
| "closeTab"
107119
| "openInBackgroundTab"
108120
| "clickElement"

0 commit comments

Comments
 (0)