Skip to content

Commit

Permalink
Adjust drive definition from unix time range to openpilot route (#486)
Browse files Browse the repository at this point in the history
* Some initial changes.

* Revert some changes.

* Base changes.

* More changes.

* Redirect working.

* Some more stuff working.

* Fixed loop setting.

* Fix map, need to add some tests.

* Working.

* Updated more tests.

* Updated analytics and loop setting.

* Clean up.

* remove getSegmentFetchRange, simplify syncVideo.

* Use seconds instead of ms in url.

* Clean up.

* Updating tests.

* Fixed bug where changing time filter popups routes.

* Sort routes by create_time.
  • Loading branch information
0x7B5 authored Jun 18, 2024
1 parent 1a42f80 commit 28e7653
Show file tree
Hide file tree
Showing 29 changed files with 251 additions and 285 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,4 @@ The current playback is tracked not by storing the current offset, but instead s
(Date.now() - state.startTime) * state.playSpeed + state.offset
```

With this central authority on current offset time, it becomes much easier to have each data source keep themselves in sync instead of trying to manage synchronizing all of them.


With this central authority on current offset time, it becomes much easier to have each data source keep themselves in sync instead of trying to manage synchronizing all of them.
4 changes: 2 additions & 2 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CircularProgress, Grid } from '@material-ui/core';
import MyCommaAuth, { config as AuthConfig, storage as AuthStorage } from '@commaai/my-comma-auth';
import { athena as Athena, auth as Auth, billing as Billing, request as Request } from '@commaai/api';

import { getZoom } from './url';
import { getZoom, getSegmentRange } from './url';
import store, { history } from './store';

import ErrorFallback from './components/ErrorFallback';
Expand Down Expand Up @@ -122,7 +122,7 @@ class App extends Component {
return this.renderLoading();
}

const showLogin = !MyCommaAuth.isAuthenticated() && !getZoom(window.location.pathname);
const showLogin = !MyCommaAuth.isAuthenticated() && !getZoom(window.location.pathname) && !getSegmentRange(window.location.pathname);
let content = (
<Suspense fallback={this.renderLoading()}>
{ showLogin ? this.anonymousRoutes() : this.authRoutes() }
Expand Down
4 changes: 2 additions & 2 deletions src/__puppeteer__/drive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { configureViewport, goto } from './utils';

const DEMO_DEVICE_URL = '/1d3dc3e03047b0c7';
const DEMO_ROUTE_URL = '/1d3dc3e03047b0c7/1716484475499/1716485004466';
const ZOOMED_DEMO_URL = '/1d3dc3e03047b0c7/1716484476499/1716485003466';
const DEMO_ROUTE_URL = '/1d3dc3e03047b0c7/000000dd--455f14369d';
const ZOOMED_DEMO_URL = '/1d3dc3e03047b0c7/000000dd--455f14369d/109/423';

jest.setTimeout(60000);

Expand Down
2 changes: 1 addition & 1 deletion src/__puppeteer__/routing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('routing', () => {
});

it('load route from URL', async () => {
await goto('/1d3dc3e03047b0c7/1716484475499/1716485004466', { timeout: 50000 });
await goto('/1d3dc3e03047b0c7/000000dd--455f14369d', { timeout: 50000 });

// Wait for video src to be set
await page.waitForFunction(
Expand Down
30 changes: 25 additions & 5 deletions src/actions/history.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { LOCATION_CHANGE } from 'connected-react-router';
import { getDongleID, getZoom, getPrimeNav } from '../url';
import { primeNav, selectDevice, pushTimelineRange } from './index';
import { getDongleID, getZoom, getSegmentRange, getPrimeNav } from '../url';
import { primeNav, selectDevice, pushTimelineRange, updateSegmentRange } from './index';
import { drives as Drives } from '@commaai/api';

export const onHistoryMiddleware = ({ dispatch, getState }) => (next) => (action) => {
export const onHistoryMiddleware = ({ dispatch, getState }) => (next) => async (action) => {
if (!action) {
return;
}
Expand All @@ -18,8 +19,27 @@ export const onHistoryMiddleware = ({ dispatch, getState }) => (next) => (action
}

const pathZoom = getZoom(action.payload.location.pathname);
if (pathZoom !== state.zoom) {
dispatch(pushTimelineRange(pathZoom?.start, pathZoom?.end, false));
const pathSegmentRange = getSegmentRange(action.payload.location.pathname);

if ((pathZoom !== state.zoom) && pathZoom && !pathSegmentRange) {
const [start, end] = [pathZoom.start, pathZoom.end];

Drives.getRoutesSegments(pathDongleId, start, end).then((routesData) => {
if (routesData && routesData.length > 0) {
const log_id = routesData[0].fullname.split('|')[1];
const duration = routesData[0].end_time_utc_millis - routesData[0].start_time_utc_millis;

dispatch(pushTimelineRange(log_id, null, null, true));
dispatch(updateSegmentRange(log_id, 0, duration));
}
}).catch((err) => {
console.error('Error fetching routes data for log ID conversion', err);
});
}


if (pathSegmentRange !== state.segmentRange) {
dispatch(pushTimelineRange(pathSegmentRange?.log_id, pathSegmentRange?.start, pathSegmentRange?.end, false));
}

const pathPrimeNav = getPrimeNav(action.payload.location.pathname);
Expand Down
10 changes: 5 additions & 5 deletions src/actions/history.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('history middleware', () => {
};
invoke(action);
expect(next).toHaveBeenCalledWith(action);
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledTimes(2);
expect(store.dispatch).toHaveBeenCalledWith(fakeInner);
expect(actionsIndex.selectDevice).toHaveBeenCalledWith('0000aaaa0000aaaa', false);
});
Expand All @@ -96,14 +96,14 @@ describe('history middleware', () => {
type: LOCATION_CHANGE,
payload: {
action: 'POP',
location: { pathname: '0000aaaa0000aaaa/1230/1234' },
location: { pathname: '0000aaaa0000aaaa/00000000--000f00000d/1230/1234' },
},
};
invoke(action);
expect(next).toHaveBeenCalledWith(action);
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith(fakeInner);
expect(actionsIndex.pushTimelineRange).toHaveBeenCalledWith(1230, 1234, false);
expect(actionsIndex.pushTimelineRange).toHaveBeenCalledWith("00000000--000f00000d", 1230000, 1234000, false);
});

it('should call prime nav with history', async () => {
Expand All @@ -130,7 +130,7 @@ describe('history middleware', () => {
expect(store.dispatch).toHaveBeenCalledTimes(2);
expect(store.dispatch).toHaveBeenCalledWith(fakeInner);
expect(store.dispatch).toHaveBeenCalledWith(fakeInner2);
expect(actionsIndex.pushTimelineRange).toHaveBeenCalledWith(undefined, undefined, false);
expect(actionsIndex.pushTimelineRange).toHaveBeenCalledWith(undefined, undefined, undefined, false);
expect(actionsIndex.primeNav).toHaveBeenCalledWith(true);
});
});
});
58 changes: 39 additions & 19 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import MyCommaAuth from '@commaai/my-comma-auth';

import * as Types from './types';
import { resetPlayback, selectLoop } from '../timeline/playback';
import { getSegmentFetchRange, hasRoutesData } from '../timeline/segments';
import {hasRoutesData } from '../timeline/segments';
import { getDeviceFromState, deviceVersionAtLeast } from '../utils';

let routesRequest = null;
Expand All @@ -26,7 +26,7 @@ export function checkRoutesData() {
}
console.debug('We need to update the segment metadata...');
const { dongleId } = state;
const fetchRange = getSegmentFetchRange(state);
const fetchRange = state.filter;

routesRequest = {
req: Drives.getRoutesSegments(dongleId, fetchRange.start, fetchRange.end),
Expand All @@ -35,7 +35,7 @@ export function checkRoutesData() {

routesRequest.req.then((routesData) => {
state = getState();
const currentRange = getSegmentFetchRange(state);
const currentRange = state.filter;
if (currentRange.start !== fetchRange.start
|| currentRange.end !== fetchRange.end
|| state.dongleId !== dongleId) {
Expand Down Expand Up @@ -66,12 +66,15 @@ export function checkRoutesData() {
return {
...r,
url: r.url.replace('chffrprivate.blob.core.windows.net', 'chffrprivate.azureedge.net'),
offset: Math.round(startTime) - state.filter.start,
log_id: r.fullname.split('|')[1],
duration: endTime - startTime,
start_time_utc_millis: startTime,
end_time_utc_millis: endTime,
segment_offsets: r.segment_start_times.map((x) => x - state.filter.start),
// TODO: get this from the API, this isn't correct for segments with a time jump
segment_durations: r.segment_start_times.map((x, i) => r.segment_end_times[i] - x),
};
}).sort((a, b) => {
return b.create_time - a.create_time;
});

dispatch({
Expand All @@ -91,20 +94,23 @@ export function checkRoutesData() {
};
}

export function urlForState(dongleId, start, end, prime) {
export function urlForState(dongleId, log_id, start, end, prime) {
const path = [dongleId];

if (start && end) {
path.push(start);
path.push(end);
if (log_id) {
path.push(log_id);
if (start && end && start > 0) {
path.push(start);
path.push(end);
}
} else if (prime) {
path.push('prime');
}

return `/${path.join('/')}`;
}

function updateTimeline(state, dispatch, start, end, allowPathChange) {
function updateTimeline(state, dispatch, log_id, start, end, allowPathChange) {
dispatch(checkRoutesData());

if (!state.loop || !state.loop.startTime || !state.loop.duration || state.loop.startTime < start
Expand All @@ -114,14 +120,14 @@ function updateTimeline(state, dispatch, start, end, allowPathChange) {
}

if (allowPathChange) {
const desiredPath = urlForState(state.dongleId, start, end, false);
const desiredPath = urlForState(state.dongleId, log_id, Math.floor(start/1000), Math.floor(end/1000), false);
if (window.location.pathname !== desiredPath) {
dispatch(push(desiredPath));
}
}
}

export function popTimelineRange(allowPathChange = true) {
export function popTimelineRange(log_id, allowPathChange = true) {
return (dispatch, getState) => {
const state = getState();
if (state.zoom.previous) {
Expand All @@ -130,26 +136,30 @@ export function popTimelineRange(allowPathChange = true) {
});

const { start, end } = state.zoom.previous;
updateTimeline(state, dispatch, start, end, allowPathChange);
updateTimeline(state, dispatch, log_id, start, end, allowPathChange);
}
};
}

export function pushTimelineRange(start, end, allowPathChange = true) {
export function pushTimelineRange(log_id, start, end, allowPathChange = true) {
return (dispatch, getState) => {
const state = getState();
if (state.zoom?.start !== start || state.zoom?.end !== end) {

if (state.zoom?.start !== start || state.zoom?.end !== end || state.segmentRange?.log_id !== log_id) {
dispatch({
type: Types.TIMELINE_PUSH_SELECTION,
log_id,
start,
end,
});
}

updateTimeline(state, dispatch, start, end, allowPathChange);
updateTimeline(state, dispatch, log_id, start, end, allowPathChange);
};

}


export function primeGetSubscription(dongleId, subscription) {
return {
type: Types.ACTION_PRIME_SUBSCRIPTION,
Expand Down Expand Up @@ -206,6 +216,15 @@ export function fetchDeviceOnline(dongleId) {
};
}

export function updateSegmentRange(log_id, start, end) {
return {
type: Types.ACTION_UPDATE_SEGMENT_RANGE,
log_id,
start,
end,
};
}

export function selectDevice(dongleId, allowPathChange = true) {
return (dispatch, getState) => {
const state = getState();
Expand All @@ -222,7 +241,8 @@ export function selectDevice(dongleId, allowPathChange = true) {
dongleId,
});

dispatch(pushTimelineRange(null, null, false));
dispatch(pushTimelineRange(null, null, null, false));
dispatch(updateSegmentRange(null, null, null));
if ((device && !device.shared) || state.profile?.superuser) {
dispatch(primeFetchSubscription(dongleId, device));
dispatch(fetchDeviceOnline(dongleId));
Expand All @@ -231,7 +251,7 @@ export function selectDevice(dongleId, allowPathChange = true) {
dispatch(checkRoutesData());

if (allowPathChange) {
const desiredPath = urlForState(dongleId, null, null, null);
const desiredPath = urlForState(dongleId, null, null, null, null);
if (window.location.pathname !== desiredPath) {
dispatch(push(desiredPath));
}
Expand All @@ -255,7 +275,7 @@ export function primeNav(nav, allowPathChange = true) {

if (allowPathChange) {
const curPath = document.location.pathname;
const desiredPath = urlForState(state.dongleId, null, null, nav);
const desiredPath = urlForState(state.dongleId, null, null, null, nav);
if (curPath !== desiredPath) {
dispatch(push(desiredPath));
}
Expand Down
6 changes: 3 additions & 3 deletions src/actions/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ describe('timeline actions', () => {
it('should push history state when editing zoom', () => {
const dispatch = jest.fn();
const getState = jest.fn();
const actionThunk = pushTimelineRange(123, 1234);
const actionThunk = pushTimelineRange("log_id", 123, 1234);

getState.mockImplementationOnce(() => ({
dongleId: 'statedongle',
loop: {},
zoom: {},
}));
actionThunk(dispatch, getState);
expect(push).toBeCalledWith('/statedongle/123/1234');
expect(push).toBeCalledWith('/statedongle/log_id');
});
});
});
2 changes: 1 addition & 1 deletion src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ACTION_BUFFER_VIDEO = 'action_buffer_video';
export const ACTION_RESET = 'action_reset';

// segments
export const ACTION_UPDATE_SEGMENTS = 'update_segments';
export const ACTION_UPDATE_SEGMENT_RANGE = 'update_segment_range';
export const ACTION_ROUTES_METADATA = 'routes_metadata';

// files
Expand Down
4 changes: 2 additions & 2 deletions src/analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ export function attachRelTime(obj, key, ms = true, cluster = null) {
}

function getVideoPercent(state, offset) {
const { zoom, filter } = state;
const { zoom } = state;
if (!offset) {
offset = state.offset;
}
return (offset - (zoom.start - filter.start)) / (zoom.end - zoom.start);
return (offset - (zoom.start)) / (zoom.end - zoom.start);
}

function logAction(action, prevState, state) {
Expand Down
4 changes: 2 additions & 2 deletions src/components/AppHeader/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const styles = () => ({
});

const AppHeader = ({
profile, classes, dispatch, drawerIsOpen, annotating, showDrawerButton,
profile, classes, dispatch, drawerIsOpen, viewingRoute, showDrawerButton,
forwardRef, handleDrawerStateChanged, primeNav, dongleId,
}) => {
const [anchorEl, setAnchorEl] = useState(null);
Expand Down Expand Up @@ -112,7 +112,7 @@ const AppHeader = ({
</a>
</div>
<div className="flex order-4 w-full justify-center sm:order-none sm:w-auto">
{Boolean(!primeNav && !annotating && dongleId) && <TimeFilter />}
{Boolean(!primeNav && !viewingRoute && dongleId) && <TimeFilter />}
</div>
<div className="flex flex-row gap-2">
<Suspense><PWAIcon /></Suspense>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Dashboard/DriveListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const DriveListItem = (props) => {
}, [drive, dispatch, isVisible, el]);

const onClick = filterRegularClick(
() => dispatch(pushTimelineRange(drive.start_time_utc_millis, drive.end_time_utc_millis)),
() => dispatch(pushTimelineRange(drive.log_id, 0, drive.duration, true)),
);

const small = windowWidth < 580;
Expand Down Expand Up @@ -117,7 +117,7 @@ const DriveListItem = (props) => {
key={drive.fullname}
className={`${classes.drive} DriveEntry`}
ref={el}
href={`/${drive.dongle_id}/${drive.start_time_utc_millis}/${drive.end_time_utc_millis}`}
href={`/${drive.dongle_id}/${drive.log_id}`}
onClick={onClick}
>
<div className={classes.driveHeader} style={!small ? { padding: '18px 32px' } : { padding: 18 }}>
Expand Down Expand Up @@ -148,7 +148,7 @@ const DriveListItem = (props) => {
<Timeline
route={drive}
thumbnailsVisible={isVisible}
zoomOverride={{ start: drive.start_time_utc_millis, end: drive.end_time_utc_millis }}
zoomOverride={{ start: 0, end: drive.duration }}
/>
</a>
);
Expand Down
Loading

0 comments on commit 28e7653

Please sign in to comment.