Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/Group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type GroupProps = {
group: Snapcast.Group;
snapcontrol: SnapControl;
showOffline: boolean;
autoPlay: boolean;
};

type GroupVolumeChange = {
Expand Down
3 changes: 2 additions & 1 deletion src/components/Server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Box sx={{ m: 1.5 }} >
{props.server.groups.map(group => <Group group={group} key={group.id} server={props.server} snapcontrol={props.snapcontrol} showOffline={props.showOffline} />)}
{props.server.groups.map(group => <Group group={group} key={group.id} server={props.server} snapcontrol={props.snapcontrol} showOffline={props.showOffline} autoPlay={props.autoPlay} />)}
</Box>
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -43,6 +45,7 @@ export default function SettingsDialog(props: { open: boolean, onClose: (_apply:
</FormControl>
<Box sx={{ py: 1 }} />
<FormControlLabel control={<Checkbox checked={showOffline} onChange={(_event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => setShowOffline(checked)} />} label="Show offline clients" />
<FormControlLabel control={<Checkbox checked={autoPlay} onChange={(_event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => setAutoPlay(checked)} />} label="Autoplay on connect" />
</DialogContent>
<DialogActions>
<Button onClick={() => { handleClose(false) }}>Cancel</Button>
Expand Down
70 changes: 65 additions & 5 deletions src/components/SnapWeb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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)
}
}
};


Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -322,6 +368,19 @@ export default function SnapWeb() {

function snackbar() {
if (isConnected) {
if (shouldAutoplay() && !autoplaySuccess) {
return (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
key='autoplay-error'
onClose={(_, reason: string) => { if (reason !== 'clickaway') { console.log("Snackbar - onClose") } }}>
<Alert onClose={(_) => { console.log("Snackbar - alert onClose") }} severity="error" sx={{ width: '100%' }}>
{"Autoplay failed with policy: " + autoplayPolicy}
</Alert>
</Snackbar >
)
}
return (null);
}
return (
Expand Down Expand Up @@ -370,7 +429,7 @@ export default function SnapWeb() {
sx={{ mr: 2 }}
onClick={(_) => { setIsPlaying(!isPlaying); }}
>
{isPlaying ? <StopIcon fontSize="large" /> : <PlayArrowIcon fontSize="large" />}
{isPlaying && autoplaySuccess ? <StopIcon fontSize="large" /> : <PlayArrowIcon fontSize="large" />}
</IconButton> : <IconButton></IconButton>}
</Toolbar>
</AppBar>
Expand All @@ -381,7 +440,7 @@ export default function SnapWeb() {
>
{list()}
</Drawer>
<Server server={server} snapcontrol={snapControlRef.current} showOffline={showOffline} />
<Server server={server} snapcontrol={snapControlRef.current} showOffline={showOffline} autoPlay={autoPlay} />
{snackbar()}
<AboutDialog open={aboutOpen} onClose={() => { setAboutOpen(false); }} />
<SettingsDialog open={settingsOpen} onClose={(apply: boolean) => {
Expand All @@ -391,6 +450,7 @@ export default function SnapWeb() {
setServerUrl(config.baseUrl);
setTheme(config.theme);
setShowOffline(config.showOffline);
setAutoPlay(config.autoPlay);
}
}} />
</div >
Expand Down
9 changes: 8 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
}
};

Expand Down