Add button to mute audio when media is playing in webview (#1036)

![image](https://github.com/user-attachments/assets/202636dc-aab5-4164-9729-fecd0cc804ae)

Also fixes atom usage for the url bar so it will properly update when
any upstream atom states change
This commit is contained in:
Evan Simkowitz 2024-10-15 18:15:33 -04:00 committed by GitHub
parent 359974d505
commit 69c99dd13a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 18 deletions

View File

@ -221,15 +221,6 @@
font-size: 11px; font-size: 11px;
} }
} }
.block-frame-div-url,
.block-frame-div-search {
background: rgba(255, 255, 255, 0.1);
input {
opacity: 1;
}
}
} }
.block-frame-end-icons { .block-frame-end-icons {

View File

@ -15,3 +15,16 @@
transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0);
will-change: transform; will-change: transform;
} }
.block-frame-div-url {
background: rgba(255, 255, 255, 0.1);
input {
opacity: 1;
}
.iconbutton {
width: fit-content !important;
margin-right: 5px;
}
}

View File

@ -46,6 +46,8 @@ export class WebViewModel implements ViewModel {
urlInputRef: React.RefObject<HTMLInputElement>; urlInputRef: React.RefObject<HTMLInputElement>;
nodeModel: NodeModel; nodeModel: NodeModel;
endIconButtons?: Atom<IconButtonDecl[]>; endIconButtons?: Atom<IconButtonDecl[]>;
mediaPlaying: PrimitiveAtom<boolean>;
mediaMuted: PrimitiveAtom<boolean>;
constructor(blockId: string, nodeModel: NodeModel) { constructor(blockId: string, nodeModel: NodeModel) {
this.nodeModel = nodeModel; this.nodeModel = nodeModel;
@ -70,12 +72,18 @@ export class WebViewModel implements ViewModel {
this.urlInputRef = React.createRef<HTMLInputElement>(); this.urlInputRef = React.createRef<HTMLInputElement>();
this.webviewRef = React.createRef<WebviewTag>(); this.webviewRef = React.createRef<WebviewTag>();
this.mediaPlaying = atom(false);
this.mediaMuted = atom(false);
this.viewText = atom((get) => { this.viewText = atom((get) => {
let url = get(this.blockAtom)?.meta?.url || get(this.homepageUrl); const homepageUrl = get(this.homepageUrl);
const metaUrl = get(this.blockAtom)?.meta?.url;
const currUrl = get(this.url); const currUrl = get(this.url);
if (currUrl !== undefined) { const urlWrapperClassName = get(this.urlWrapperClassName);
url = currUrl; const refreshIcon = get(this.refreshIcon);
} const mediaPlaying = get(this.mediaPlaying);
const mediaMuted = get(this.mediaMuted);
const url = currUrl ?? metaUrl ?? homepageUrl;
return [ return [
{ {
elemtype: "iconbutton", elemtype: "iconbutton",
@ -97,7 +105,7 @@ export class WebViewModel implements ViewModel {
}, },
{ {
elemtype: "div", elemtype: "div",
className: clsx("block-frame-div-url", get(this.urlWrapperClassName)), className: clsx("block-frame-div-url", urlWrapperClassName),
onMouseOver: this.handleUrlWrapperMouseOver.bind(this), onMouseOver: this.handleUrlWrapperMouseOver.bind(this),
onMouseOut: this.handleUrlWrapperMouseOut.bind(this), onMouseOut: this.handleUrlWrapperMouseOut.bind(this),
children: [ children: [
@ -111,26 +119,31 @@ export class WebViewModel implements ViewModel {
onFocus: this.handleFocus.bind(this), onFocus: this.handleFocus.bind(this),
onBlur: this.handleBlur.bind(this), onBlur: this.handleBlur.bind(this),
}, },
mediaPlaying && {
elemtype: "iconbutton",
icon: mediaMuted ? "volume-slash" : "volume",
click: this.handleMuteChange.bind(this),
},
{ {
elemtype: "iconbutton", elemtype: "iconbutton",
icon: get(this.refreshIcon), icon: refreshIcon,
click: this.handleRefresh.bind(this), click: this.handleRefresh.bind(this),
}, },
], ].filter((v) => v),
}, },
] as HeaderElem[]; ] as HeaderElem[];
}); });
this.endIconButtons = atom((get) => { this.endIconButtons = atom((get) => {
const url = get(this.url);
return [ return [
{ {
elemtype: "iconbutton", elemtype: "iconbutton",
icon: "arrow-up-right-from-square", icon: "arrow-up-right-from-square",
title: "Open in External Browser", title: "Open in External Browser",
click: () => { click: () => {
const url = this.getUrl();
if (url != null && url != "") { if (url != null && url != "") {
return getApi().openExternal(this.getUrl()); return getApi().openExternal(url);
} }
}, },
}, },
@ -180,6 +193,25 @@ export class WebViewModel implements ViewModel {
this.loadUrl(globalStore.get(this.homepageUrl), "home"); this.loadUrl(globalStore.get(this.homepageUrl), "home");
} }
setMediaPlaying(isPlaying: boolean) {
console.log("setMediaPlaying", isPlaying);
globalStore.set(this.mediaPlaying, isPlaying);
}
handleMuteChange(e: React.ChangeEvent<HTMLInputElement>) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
try {
const newMutedVal = !this.webviewRef.current?.isAudioMuted();
globalStore.set(this.mediaMuted, newMutedVal);
this.webviewRef.current?.setAudioMuted(newMutedVal);
} catch (e) {
console.error("Failed to change mute value", e);
}
}
handleUrlWrapperMouseOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) { handleUrlWrapperMouseOver(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
const urlInputFocused = globalStore.get(this.urlInputFocused); const urlInputFocused = globalStore.get(this.urlInputFocused);
if (e.type === "mouseover" && !urlInputFocused) { if (e.type === "mouseover" && !urlInputFocused) {
@ -536,6 +568,12 @@ const WebView = memo(({ model }: WebViewProps) => {
setDomReady(true); setDomReady(true);
setBgColor(); setBgColor();
}; };
const handleMediaPlaying = () => {
model.setMediaPlaying(true);
};
const handleMediaPaused = () => {
model.setMediaPlaying(false);
};
webview.addEventListener("did-navigate-in-page", navigateListener); webview.addEventListener("did-navigate-in-page", navigateListener);
webview.addEventListener("did-navigate", navigateListener); webview.addEventListener("did-navigate", navigateListener);
@ -546,6 +584,8 @@ const WebView = memo(({ model }: WebViewProps) => {
webview.addEventListener("focus", webviewFocus); webview.addEventListener("focus", webviewFocus);
webview.addEventListener("blur", webviewBlur); webview.addEventListener("blur", webviewBlur);
webview.addEventListener("dom-ready", handleDomReady); webview.addEventListener("dom-ready", handleDomReady);
webview.addEventListener("media-started-playing", handleMediaPlaying);
webview.addEventListener("media-paused", handleMediaPaused);
// Clean up event listeners on component unmount // Clean up event listeners on component unmount
return () => { return () => {
@ -558,6 +598,8 @@ const WebView = memo(({ model }: WebViewProps) => {
webview.removeEventListener("focus", webviewFocus); webview.removeEventListener("focus", webviewFocus);
webview.removeEventListener("blur", webviewBlur); webview.removeEventListener("blur", webviewBlur);
webview.removeEventListener("dom-ready", handleDomReady); webview.removeEventListener("dom-ready", handleDomReady);
webview.removeEventListener("media-started-playing", handleMediaPlaying);
webview.removeEventListener("media-paused", handleMediaPaused);
}; };
}, []); }, []);