Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

playback info #30

Merged
merged 15 commits into from
Jul 9, 2024
28 changes: 27 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"roundware-web-framework": "^0.12.8-alpha.14",
"ts-overlapping-marker-spiderfier": "^1.0.3",
"wavesurfer-react": "https://github.com/shreyas-jadhav/wavesurfer-react/raw/tarball/wavesurfer-react-2.0.13.tgz",
"wavesurfer.js": "^5.2.0"
"wavesurfer.js": "^5.2.0",
"web-permission-messages": "github:shreyas-jadhav/web-permission-messages"
},
"devDependencies": {
"@babel/preset-react": "^7.14.5",
Expand Down
30 changes: 15 additions & 15 deletions src/404.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Roundware App Template</title>
<style type="text/css">
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
</style>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<div id="app"></div>
<script type="module" src="index.tsx"></script>
</body>
<head>
<title>Roundware App Template</title>
<style type="text/css">
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
</style>
<meta name="viewport" content="width=device-width,initial-scale=1" />
</head>
<body>
<div id="app"></div>
<script type="module" src="index.tsx"></script>
</body>
</html>
9 changes: 4 additions & 5 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import PlatformMessage from 'components/PlatformMessage';
import config from 'config';
import React, { useState } from 'react';
import Helmet from 'react-helmet';
import { BrowserRouter, Link, NavLink, Route, Switch, useLocation } from 'react-router-dom';
import { BrowserRouter, Link, NavLink, Route, Switch } from 'react-router-dom';
import { getMessageOnLoad } from 'utils/platformMessages';
import favicon from '../../assets/favicon.png';
import logoSmall from '../../assets/rw-full-logo-wb.png';
Expand All @@ -28,17 +28,16 @@ import ShareButton from './ShareButton';
import ShareDialog from './ShareDialog';
import SpeakButton from './SpeakButton';
import useStyles from './styles';
import ErrorBoundary from 'components/elements/ErrorBoundary';

export const App = () => {
const [theme] = useState(defaultTheme);
const classes = useStyles();
const { roundware } = useRoundware();
const isExtraSmallScreen = useMediaQuery<boolean>(theme.breakpoints.down('xs'));

let location = useLocation();

return (
<>
<ErrorBoundary>
<BrowserRouter getUserConfirmation={(message, callback) => UserConfirmation(message, callback)}>
<CssBaseline />

Expand Down Expand Up @@ -115,6 +114,6 @@ export const App = () => {
</AppBar>
</DrawerSensitiveWrapper>
</BrowserRouter>
</>
</ErrorBoundary>
);
};
2 changes: 1 addition & 1 deletion src/components/ErrorDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React from 'react';

interface ErrorDialogProps {
error: Error | null;
set_error: React.Dispatch<React.SetStateAction<Error | null>>;
set_error: (error: Error | null) => void;
}
const ErrorDialog = ({ error, set_error }: ErrorDialogProps) => {
return (
Expand Down
28 changes: 16 additions & 12 deletions src/components/ListenPage/Map/WalkingModeButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { useState, useEffect } from 'react';
import { useRoundware } from '../../../../hooks';
import { GeoListenMode } from 'roundware-web-framework';
import { useGoogleMap } from '@react-google-maps/api';
import { makeStyles, useTheme } from '@mui/styles';
import Button from '@mui/material/Button';
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, useMediaQuery } from '@mui/material';
import DirectionsWalkIcon from '@mui/icons-material/DirectionsWalk';
import MapIcon from '@mui/icons-material/Map';
import ListenerLocationMarker from './ListenerLocationMarker';
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, useMediaQuery } from '@mui/material';
import Button from '@mui/material/Button';
import { makeStyles, useTheme } from '@mui/styles';
import { useGoogleMap } from '@react-google-maps/api';
import clsx from 'clsx';
import LoadingOverlay from './LoadingOverlay';
import messages from '../../../../locales/en_US.json';
import PermissionDeniedDialog from 'components/elements/PermissionDeniedDialog';
import config from 'config';
import { useURLSync } from 'context/URLContext';
import { useLocation } from 'react-router';
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
import { GeoListenMode } from 'roundware-web-framework';
import { useRoundware } from '../../../../hooks';
import messages from '../../../../locales/en_US.json';
import ListenerLocationMarker from './ListenerLocationMarker';
import LoadingOverlay from './LoadingOverlay';
const useStyles = makeStyles((theme) => {
return {
walkingModeButton: {
Expand Down Expand Up @@ -215,7 +216,10 @@ const walkingModeButton = () => {
return (
<div>
<LoadingOverlay open={walkingModeStatus === 'locating'} message={'Locating... \nPlease allow location permissions.'} />
<Dialog open={walkingModeStatus === ('error' || 'out-of-range')}>

{/* permission denied dialog */}
<PermissionDeniedDialog open={walkingModeStatus === 'error' && isEqual(walkingModeErrorMessage, messages.errors.permissionDenied)} onClose={() => setWalkingModeStatus('')} functionality={'location'} />
<Dialog open={(walkingModeStatus === 'error' && !isEqual(walkingModeErrorMessage, messages.errors.permissionDenied)) || walkingModeStatus === 'out-of-range'}>
<DialogTitle>{walkingModeErrorMessage?.title}</DialogTitle>
<DialogContent>
<DialogContentText>{walkingModeErrorMessage?.message}</DialogContentText>
Expand Down
5 changes: 4 additions & 1 deletion src/components/ListenPage/Map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { useURLSync } from 'context/URLContext';
import ShareDialog from 'components/App/ShareDialog';
import ResetButton from './ResetButton';
import SpeakerImages from './Speakers/SpeakerImages';
import SpeakerToggle from '../SpeakerToggle';
import PlaybackInfoOverlay from '../PlaybackInfoOverlay';

const useStyles = makeStyles((theme) => {
return {
Expand Down Expand Up @@ -126,13 +128,14 @@ const RoundwareMap = (props: RoundwareMapProps) => {
<AssetLayer updateLocation={updateListenerLocation} />
<RangeCircleOverlay updateLocation={updateListenerLocation} />
{map && roundware.mixer?.playlist && <WalkingModeButton />}
{config.features.speakerToggleIds?.length > 0 && <SpeakerToggle />}
{config.map.speakerDisplay == 'polygons' && <SpeakerPolygons />}
{config.map.speakerDisplay == 'images' && <SpeakerImages />}
<SpeakerLoadingIndicator />
{!config.listen.speaker.loop && <SpeakerReplayButton />}
<ShareDialog />
<ResetButton updateLocation={updateListenerLocation} />

<PlaybackInfoOverlay />
{config.map.showBoundsMarkers && roundware && (
<Marker
position={{
Expand Down
90 changes: 90 additions & 0 deletions src/components/ListenPage/PlaybackInfoOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Card, CardContent, Fade, Link, Paper, Stack, ThemeProvider, Typography } from '@mui/material';
import { useEffect, useState } from 'react';
import { lightTheme } from 'styles';
import OpenInNew from '@mui/icons-material/OpenInNew';
import playbackInfo from '../../playbackInfo.json';
import { useRoundware } from 'hooks';
import CustomMapControl from './Map/CustomControl';

type PlaybackInfo = {
id: number;
startTime: number;
stopTime: number;
displayText: string;
url: string;
};

const PLAYBACK_INFO: PlaybackInfo[] = playbackInfo;

const PlaybackInfoOverlay = () => {
const [displayPlaybackInfo, setDisplayPlaybackInfo] = useState<PlaybackInfo | null>(null);

const { roundware } = useRoundware();

const [timeouts, setTimeouts] = useState<NodeJS.Timer[]>([]);

useEffect(() => {
if (roundware.mixer.playing) {
const elapsedTimeMs = roundware.mixer.playlist?.elapsedTimeMs;
if (typeof elapsedTimeMs !== 'number') return;

const elapsedSeconds = elapsedTimeMs / 1000;
// set intervals such that the playback info are displayed at the correct time;

PLAYBACK_INFO.forEach((info) => {
// if the elapsed time is within the range of the playback info, display it
if (elapsedSeconds >= info.startTime && elapsedSeconds <= info.stopTime) {
setDisplayPlaybackInfo(info);

setTimeout(
() => {
setDisplayPlaybackInfo(null);
},
// remaining time
(info.stopTime - elapsedSeconds) * 1000
);

// if the elapsed time is before the start time, schedule the display
} else if (elapsedSeconds < info.startTime) {
const timeout = setTimeout(() => {
setDisplayPlaybackInfo(info);
setTimeout(() => {
setDisplayPlaybackInfo(null);
}, (info.stopTime - info.startTime) * 1000);
}, (info.startTime - elapsedSeconds) * 1000);

setTimeouts([...timeouts, timeout]);
}
});
} else {
// cancel the intervals
timeouts.forEach((timeout) => clearTimeout(timeout));
}
}, [roundware.mixer.playing]);

return (
<CustomMapControl position={google.maps.ControlPosition.TOP_CENTER}>
<ThemeProvider theme={lightTheme}>
<Fade in={displayPlaybackInfo !== null}>
<Paper
sx={{
p: 2,
borderRadius: 2,
mt: 2,
}}
>
<Link href={displayPlaybackInfo?.url} target='_blank' rel='noreferrer'>
<Stack direction={'row'} spacing={2} alignItems={'center'}>
<Typography variant='h6'>{displayPlaybackInfo?.displayText}</Typography>

<OpenInNew />
</Stack>
</Link>
</Paper>
</Fade>
</ThemeProvider>
</CustomMapControl>
);
};

export default PlaybackInfoOverlay;
42 changes: 39 additions & 3 deletions src/components/ListenPage/RoundwareMixerControl.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import React, { useEffect, useState } from 'react';
import Forward5Icon from '@mui/icons-material/Forward5';
import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline';
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline';
import Replay5Icon from '@mui/icons-material/Replay5';
import SkipNextIcon from '@mui/icons-material/SkipNext';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import Snackbar, { SnackbarProps } from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import { useRoundware } from '../../hooks';
import { useEffect, useState } from 'react';
import { GeoListenMode } from 'roundware-web-framework';
import { useRoundware } from '../../hooks';

const RoundwareMixerControl = () => {
const { roundware, forceUpdate } = useRoundware();
Expand Down Expand Up @@ -43,8 +45,36 @@ const RoundwareMixerControl = () => {
};
}, [roundware]);

function seek(offset: number): void {
roundware.mixer.speakerTracks?.forEach((s) => {
const currentTime = s.player.audio.currentTime;
let newTime = currentTime + offset;

// Ensure the new time is within the bounds of the audio duration
if (newTime < 0) {
newTime = 0;
} else if (newTime > s.player.audio.duration) {
newTime = s.player.audio.duration;
}

s.player.audio.currentTime = newTime;

// Adjust global._roundwareSpeakerStartedAt to match the new seek time
// @ts-ignore
if (global._roundwareSpeakerStartedAt instanceof Date) {
const seekDifference = newTime * 1000;
// @ts-ignore
global._roundwareSpeakerStartedAt = new Date(new Date().getTime() - seekDifference);
}
});
}

return (
<>
<Button onClick={() => seek(-5)} disabled={isPlaying ? false : true}>
<Replay5Icon />
</Button>

<Button
onClick={() => {
if (!roundware.mixer || !roundware.mixer?.playlist) {
Expand All @@ -70,6 +100,11 @@ const RoundwareMixerControl = () => {
>
{roundware && roundware.mixer && roundware.mixer.playing ? <PauseCircleOutlineIcon fontSize='large' /> : <PlayCircleOutlineIcon fontSize='large' />}
</Button>

<Button disabled={isPlaying ? false : true} onClick={() => seek(5)}>
<Forward5Icon />
</Button>

<Button
disabled={isPlaying ? false : true}
onClick={() => {
Expand All @@ -84,6 +119,7 @@ const RoundwareMixerControl = () => {
>
<SkipNextIcon />
</Button>

<Snackbar open={snackbarOpen} autoHideDuration={4000} onClose={handleSnackbarClose} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} style={{ marginBottom: 50 }}>
<Alert elevation={6} severity='success'>
Remixing audio: skipping ahead!
Expand Down
Loading