chore!: duration in milliseconds (#28003)

* server changes

* openapi

* web changes

* mobile changes

* assume 3.0 client

* deprecate

* review feedback

* update medium tests

* linting
This commit is contained in:
Mert
2026-04-30 09:44:27 -04:00
committed by GitHub
parent 97c62136b7
commit b554664791
44 changed files with 1092 additions and 335 deletions
@@ -5,7 +5,6 @@
import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte';
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
import { timeToSeconds } from '$lib/utils/date-time';
import { moveFocus } from '$lib/utils/focus-util';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
import { getAltText } from '$lib/utils/thumbnail-util';
@@ -274,7 +273,7 @@
url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })}
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
curve={selected}
durationInSeconds={asset.duration ? timeToSeconds(asset.duration) : 0}
durationInSeconds={asset.duration ? asset.duration / 1000 : 0}
playbackOnIconHover={!$playVideoThumbnailOnHover}
/>
</div>
@@ -29,7 +29,7 @@ export type TimelineAsset = {
isVideo: boolean;
isImage: boolean;
stack: AssetStackResponseDto | null;
duration: string | null;
duration: number | null;
projectionType: string | null;
livePhotoVideoId: string | null;
city: string | null;
+5 -5
View File
@@ -50,7 +50,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const url = getAssetUrl({ asset });
@@ -65,7 +65,7 @@ describe('utils', () => {
originalPath: 'image.webp',
originalMimeType: 'image/webp',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const url = getAssetUrl({ asset });
@@ -119,7 +119,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
@@ -134,7 +134,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] });
@@ -150,7 +150,7 @@ describe('utils', () => {
originalPath: 'image.gif',
originalMimeType: 'image/gif',
type: AssetTypeEnum.Image,
duration: '2.0',
duration: 2000,
});
const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] });
+1 -49
View File
@@ -1,53 +1,5 @@
import { writable } from 'svelte/store';
import { getAlbumDateRange, getShortDateRange, timeToSeconds } from './date-time';
describe('converting time to seconds', () => {
it('parses hh:mm:ss correctly', () => {
expect(timeToSeconds('01:02:03')).toBeCloseTo(3723);
});
it('parses hh:mm:ss.SSS correctly', () => {
expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456);
});
it('parses h:m:s.S correctly', () => {
expect(timeToSeconds('1:2:3.4')).toBe(0); // Non-standard format, Luxon returns NaN
});
it('parses hhh:mm:ss.SSS correctly', () => {
expect(timeToSeconds('100:02:03.456')).toBe(0); // Non-standard format, Luxon returns NaN
});
it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {
expect(timeToSeconds('01:02:03.456.123456')).toBe(0); // Non-standard format, Luxon returns NaN
});
// Test edge cases that can cause crashes
it('handles "0" string input', () => {
expect(timeToSeconds('0')).toBe(0);
});
it('handles empty string input', () => {
expect(timeToSeconds('')).toBe(0);
});
it('parses HH:MM format correctly', () => {
expect(timeToSeconds('01:02')).toBe(3720); // 1 hour 2 minutes = 3720 seconds
});
it('handles malformed time strings', () => {
expect(timeToSeconds('invalid')).toBe(0);
});
it('parses single hour format correctly', () => {
expect(timeToSeconds('01')).toBe(3600); // Luxon interprets "01" as 1 hour
});
it('handles time strings with invalid numbers', () => {
expect(timeToSeconds('aa:bb:cc')).toBe(0);
expect(timeToSeconds('01:bb:03')).toBe(0);
});
});
import { getAlbumDateRange, getShortDateRange } from './date-time';
describe('getShortDateRange', () => {
beforeEach(() => {
+1 -13
View File
@@ -1,20 +1,8 @@
import { DateTime, Duration } from 'luxon';
import { DateTime } from 'luxon';
import { get } from 'svelte/store';
import { dateFormats } from '$lib/constants';
import { locale } from '$lib/stores/preferences.store';
/**
* Convert time like `01:02:03.456` to seconds.
*/
export function timeToSeconds(time: string) {
if (!time || time === '0') {
return 0;
}
const seconds = Duration.fromISOTime(time).as('seconds');
return Number.isNaN(seconds) ? 0 : seconds;
}
export function parseUtcDate(date: string) {
return DateTime.fromISO(date, { zone: 'UTC' }).toUTC();
}
@@ -95,18 +95,10 @@
};
const setProgressDuration = (asset: TimelineAsset) => {
if (asset.isVideo) {
const timeParts = asset.duration!.split(':').map(Number);
const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
progressBarController = new Tween<number>(0, {
duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0),
});
} else {
progressBarController = new Tween<number>(0, {
duration: (from: number, to: number) =>
to ? authManager.preferences.memories.duration * 1000 * (to - from) : 0,
});
}
progressBarController = new Tween<number>(0, {
duration: (from: number, to: number) =>
to ? (asset.isVideo ? asset.duration! : authManager.preferences.memories.duration * 1000) * (to - from) : 0,
});
};
const handleNextAsset = () => handleNavigate(current?.next?.asset);