diff --git a/src/components/Group.tsx b/src/components/Group.tsx index 3831e3a..62b4246 100644 --- a/src/components/Group.tsx +++ b/src/components/Group.tsx @@ -19,6 +19,7 @@ type GroupProps = { group: Snapcast.Group; snapcontrol: SnapControl; showOffline: boolean; + autoPlay: boolean; }; type GroupVolumeChange = { diff --git a/src/components/Server.tsx b/src/components/Server.tsx index d1ce61d..543457b 100644 --- a/src/components/Server.tsx +++ b/src/components/Server.tsx @@ -7,13 +7,14 @@ type ServerProps = { server: Snapcast.Server; snapcontrol: SnapControl; showOffline: boolean; + autoPlay: boolean; }; export default function Server(props: ServerProps) { console.log("Render Server"); return ( - {props.server.groups.map(group => )} + {props.server.groups.map(group => )} ); } diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 305644d..7f1c036 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -6,12 +6,14 @@ export default function SettingsDialog(props: { open: boolean, onClose: (_apply: const [serverurl, setServerurl] = useState(config.baseUrl); const [theme, setTheme] = useState(config.theme); const [showOffline, setShowOffline] = useState(config.showOffline); + const [autoPlay, setAutoPlay] = useState(config.autoPlay); function handleClose(apply: boolean) { if (apply) { config.baseUrl = serverurl; config.theme = theme; config.showOffline = showOffline; + config.autoPlay = autoPlay; } props.onClose(apply); } @@ -43,6 +45,7 @@ export default function SettingsDialog(props: { open: boolean, onClose: (_apply: , checked: boolean) => setShowOffline(checked)} />} label="Show offline clients" /> + , checked: boolean) => setAutoPlay(checked)} />} label="Autoplay on connect" /> diff --git a/src/components/SnapWeb.tsx b/src/components/SnapWeb.tsx index 73a302e..77b22d3 100644 --- a/src/components/SnapWeb.tsx +++ b/src/components/SnapWeb.tsx @@ -93,6 +93,8 @@ export default function SnapWeb() { const [server, setServer] = useState(new Snapcast.Server()); const [drawerOpen, setDrawerOpen] = useState(false); const [showOffline, setShowOffline] = useState(config.showOffline); + const [autoPlay, setAutoPlay] = useState(config.autoPlay); + const [autoplaySuccess, setAutoplaySuccess] = useState(true); const [theme, setTheme] = useState(config.theme); const [serverUrl, setServerUrl] = useState(config.baseUrl); const [aboutOpen, setAboutOpen] = useState(false); @@ -134,6 +136,25 @@ export default function SnapWeb() { updateMediaSession(); } + function shouldAutoplay(): boolean { + return (autoPlay || (document.location.hash.match(/autoplay/) !== null)); + } + + function isAutoplaySupported(): string { + if ("getAutoplayPolicy" in navigator) { + try { + const policy = (navigator as any).getAutoplayPolicy("mediaelement"); + return policy; + } catch (error) { + console.warn("getAutoplayPolicy failed:", error); + } + } + + return "unknown" + }; + + const autoplayPolicy = isAutoplaySupported() + snapControlRef.current.onChange = (_control: SnapControl, server: Snapcast.Server) => handleChange(server); snapControlRef.current.onConnectionChanged = (_control: SnapControl, connected: boolean, error?: string) => { console.log("Connection state changed: " + connected + ", error: " + error); @@ -144,6 +165,18 @@ export default function SnapWeb() { setConnectError(error); } setConnected(connected); + if (shouldAutoplay()) { + if (autoplayPolicy === "allowed") { + console.debug("autoplayPolicy:", autoplayPolicy) + setIsPlaying(true); + } else if (autoplayPolicy === "unknown") { + console.warn("autoplayPolicy unknown, attempting autoplay anyway") + setIsPlaying(true); + } else { + console.warn("autoplayPolicy:", autoplayPolicy) + setAutoplaySuccess(false) + } + } }; @@ -277,9 +310,22 @@ export default function SnapWeb() { console.debug("isPlaying changed to true"); audioRef.current.src = silence; audioRef.current.loop = true; - audioRef.current.play().then(() => { - snapstreamRef.current = new SnapStream(config.baseUrl); - }); + audioRef.current.play().then( + () => { + setAutoplaySuccess(true) + snapstreamRef.current = new SnapStream(config.baseUrl); + }, + (error) => { + setAutoplaySuccess(false) + if (snapstreamRef.current) + snapstreamRef.current.stop(); + snapstreamRef.current = null; + audioRef.current.pause(); + audioRef.current.src = ''; + setIsPlaying(false) + console.error("Playing failed, likely due to disallowed autoplay:", error) + } + ); // updateMediaSession(); // }); } else { @@ -322,6 +368,19 @@ export default function SnapWeb() { function snackbar() { if (isConnected) { + if (shouldAutoplay() && !autoplaySuccess) { + return ( + { if (reason !== 'clickaway') { console.log("Snackbar - onClose") } }}> + { console.log("Snackbar - alert onClose") }} severity="error" sx={{ width: '100%' }}> + {"Autoplay failed with policy: " + autoplayPolicy} + + + ) + } return (null); } return ( @@ -370,7 +429,7 @@ export default function SnapWeb() { sx={{ mr: 2 }} onClick={(_) => { setIsPlaying(!isPlaying); }} > - {isPlaying ? : } + {isPlaying && autoplaySuccess ? : } : } @@ -381,7 +440,7 @@ export default function SnapWeb() { > {list()} - + {snackbar()} { setAboutOpen(false); }} /> { @@ -391,6 +450,7 @@ export default function SnapWeb() { setServerUrl(config.baseUrl); setTheme(config.theme); setShowOffline(config.showOffline); + setAutoPlay(config.autoPlay); } }} /> diff --git a/src/config.ts b/src/config.ts index f2ac686..8ac52e8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,8 @@ const host = import.meta.env.VITE_APP_SNAPSERVER_HOST || window.location.host; const keys = { snapserver_host: "snapserver.host", theme: "theme", - showoffline: "showoffline" + showoffline: "showoffline", + autoPlay: "autoPlay" } enum Theme { @@ -48,6 +49,12 @@ const config = { }, set showOffline(value: boolean) { setPersistentValue(keys.showoffline, String(value)); + }, + get autoPlay() { + return getPersistentValue(keys.autoPlay, String(false)) === String(true); + }, + set autoPlay(value: boolean) { + setPersistentValue(keys.autoPlay, String(value)); } };