Skip to content
Merged
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
14 changes: 12 additions & 2 deletions packages/docs/docs/web-renderer/render-media-on-web.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,19 @@ React to render progress. The callback receives an object with the following pro
```tsx twoslash
import type {RenderMediaOnWebProgressCallback} from '@remotion/web-renderer';

const onProgress: RenderMediaOnWebProgressCallback = ({renderedFrames, encodedFrames}) => {
console.log(`Rendered ${renderedFrames} frames`);
const onProgress: RenderMediaOnWebProgressCallback = ({
encodedFrames,
renderEstimatedTime,
progress,
doneIn,
}) => {
console.log(`Encoded ${encodedFrames} frames`);
console.log(`Overall progress: ${Math.round(progress * 100)}%`);
console.log(`Estimated render time remaining: ${renderEstimatedTime}ms`);

if (doneIn !== null) {
console.log(`Finished rendering in ${doneIn}ms`);
}
};
```

Expand Down
22 changes: 20 additions & 2 deletions packages/docs/docs/web-renderer/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,26 @@ import type {RenderMediaOnWebProgress} from '@remotion/web-renderer';
// ^?
```

- `renderedFrames`: The number of frames that have been rendered
- `encodedFrames`: The number of frames that have been encoded
### `renderedFrames`

Deprecated and kept for backward compatibility. The number of frames that have been rendered.
Prefer `progress` for overall status updates.

### `encodedFrames`

The number of frames that have been encoded.

### `doneIn`

The total time in milliseconds from render start until all frames were encoded, or `null` while encoding is still in progress.

### `renderEstimatedTime`

Estimated time remaining in milliseconds until rendering is done.

### `progress`

Overall progress as a number between `0` and `1`.

## `RenderMediaOnWebProgressCallback`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const GitHubStars: React.FC = () => {
width="45px"
/>
<StatItemContent
content="38k"
content="39k"
width="80px"
fontSize="2.5rem"
fontWeight="bold"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,10 @@ export const RenderButton: React.FC<{
muted: typeof AudioEncoder === 'undefined',
scale: 1,
inputProps,
onProgress: ({renderedFrames}) => {
onProgress: ({progress}) => {
setState({
type: 'progress',
progress: renderedFrames / durationInFrames,
progress,
});
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const GithubButton: React.FC = () => {
<div className="flex flex-row items-center text-base">
<GithubIcon /> <span>GitHub</span>{' '}
<div className="text-xs inline-block ml-2 leading-none mt-[3px] self-center">
{'38k'}
{'39k'}
</div>
</div>
);
Expand Down
8 changes: 4 additions & 4 deletions packages/renderer/src/test/render-still.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test(
).toThrow(/not be NaN, but is NaN/);
},
{
timeout: 30000,
timeout: 90000,
},
);

Expand Down Expand Up @@ -66,7 +66,7 @@ test(
expect(contentType).toBe('image/png');
},
{
timeout: 30000,
timeout: 90000,
},
);

Expand Down Expand Up @@ -100,7 +100,7 @@ test(
/Cannot use frame 200: Duration of composition is 30, therefore the highest frame that can be rendered is 29/,
);
},
{timeout: 30000},
{timeout: 90000},
);

test(
Expand Down Expand Up @@ -133,6 +133,6 @@ test(
).toThrow(/Image format should be one of: "png", "jpeg", "pdf", "webp"/);
},
{
timeout: 30000,
timeout: 90000,
},
);
33 changes: 22 additions & 11 deletions packages/studio/src/components/RenderModal/ClientRenderProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import {LIGHT_TEXT} from '../../helpers/colors';
import {Spacing} from '../layout';
import {CircularProgress} from '../RenderQueue/CircularProgress';
import {getClientRenderProgressMessage} from '../RenderQueue/client-render-progress';
import type {ClientRenderJob} from '../RenderQueue/client-side-render-types';
import {SuccessIcon} from '../RenderQueue/SuccessIcon';

Expand All @@ -26,22 +27,28 @@ const right: React.CSSProperties = {
flex: 1,
};

const EncodingProgress: React.FC<{
const ProgressStatus: React.FC<{
readonly encodedFrames: number;
readonly totalFrames: number;
}> = ({encodedFrames, totalFrames}) => {
const done = encodedFrames === totalFrames;
const progress = totalFrames > 0 ? encodedFrames / totalFrames : 0;
readonly doneIn: number | null;
readonly renderEstimatedTime: number;
readonly progress: number;
}> = ({encodedFrames, totalFrames, doneIn, renderEstimatedTime, progress}) => {
const done = doneIn !== null;
const message = getClientRenderProgressMessage({
encodedFrames,
totalFrames,
doneIn,
renderEstimatedTime,
progress,
});

return (
<div style={progressItem}>
{done ? <SuccessIcon /> : <CircularProgress progress={progress} />}
<Spacing x={1} />
<div style={label}>
{done
? `Encoded ${totalFrames} frames`
: `Encoding ${encodedFrames} / ${totalFrames} frames`}
</div>
<div style={label}>{message}</div>
{doneIn !== null ? <div style={right}>{doneIn}ms</div> : null}
</div>
);
};
Expand Down Expand Up @@ -92,15 +99,19 @@ export const ClientRenderProgress: React.FC<{
);
}

const {encodedFrames, totalFrames} = job.progress;
const {encodedFrames, totalFrames, doneIn, renderEstimatedTime, progress} =
job.progress;

return (
<div>
<Spacing y={0.5} />
{job.type === 'client-video' && (
<EncodingProgress
<ProgressStatus
encodedFrames={encodedFrames}
totalFrames={totalFrames}
doneIn={doneIn}
renderEstimatedTime={renderEstimatedTime}
progress={progress}
/>
)}
<Spacing y={1} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ export const ClientRenderQueueProcessor: React.FC = () => {
onProgress(job.id, {
encodedFrames: progress.encodedFrames,
totalFrames,
doneIn: progress.doneIn,
renderEstimatedTime: progress.renderEstimatedTime,
progress: progress.progress,
});
},
outputTarget: 'web-fs',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ export const RenderQueueItemStatus: React.FC<{
if (job.status === 'running') {
let progressValue: number;
if (isClientJob) {
const {encodedFrames, totalFrames} = job.progress;
progressValue = totalFrames > 0 ? encodedFrames / totalFrames : 0;
progressValue = job.progress.progress;
} else {
progressValue = job.progress.value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {useCallback, useContext} from 'react';
import {ModalsContext} from '../../state/modals';
import {useZIndex} from '../../state/z-index';
import {getClientRenderProgressMessage} from './client-render-progress';
import type {AnyRenderJob} from './context';
import {isClientRenderJob} from './context';
import {renderQueueItemSubtitleStyle} from './item-style';
Expand Down Expand Up @@ -29,9 +30,7 @@ export const RenderQueueProgressMessage: React.FC<{
}, [job.id, setSelectedModal]);

const message = isClientJob
? job.progress.totalFrames === 0
? 'Getting composition'
: `Encoding frame ${job.progress.encodedFrames}/${job.progress.totalFrames}`
? getClientRenderProgressMessage(job.progress)
: job.progress.message;

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type {ClientRenderJobProgress} from './client-side-render-types';

export const formatEtaString = (timeRemainingInMilliseconds: number) => {
const remainingTime = timeRemainingInMilliseconds / 1000;
const remainingTimeHours = Math.floor(remainingTime / 3600);
const remainingTimeMinutes = Math.floor((remainingTime % 3600) / 60);
const remainingTimeSeconds = Math.floor(remainingTime % 60);

return [
remainingTimeHours ? `${remainingTimeHours}h` : null,
remainingTimeMinutes ? `${remainingTimeMinutes}m` : null,
`${remainingTimeSeconds}s`,
]
.filter((value): value is string => Boolean(value))
.join(' ');
};

export const getClientRenderProgressMessage = (
progress: ClientRenderJobProgress,
) => {
if (progress.totalFrames === 0) {
return 'Getting composition';
}

if (progress.doneIn !== null) {
return `Encoded ${progress.totalFrames}/${progress.totalFrames}`;
}

if (progress.renderEstimatedTime > 0) {
const etaString = `, time remaining: ${formatEtaString(progress.renderEstimatedTime)}`;

return `Rendering ${progress.encodedFrames}/${progress.totalFrames}${etaString}`;
}

return `Encoded ${progress.encodedFrames}/${progress.totalFrames}`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import type {LogLevel} from 'remotion';
export type ClientRenderJobProgress = {
encodedFrames: number;
totalFrames: number;
doneIn: number | null;
renderEstimatedTime: number;
progress: number;
};

export type GetBlobCallback = () => Promise<Blob>;
Expand Down
8 changes: 7 additions & 1 deletion packages/studio/src/components/RenderQueue/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@ export const RenderQueueContextProvider: React.FC<{
? ({
...job,
status: 'running',
progress: {encodedFrames: 0, totalFrames: 0},
progress: {
encodedFrames: 0,
totalFrames: 0,
doneIn: null,
renderEstimatedTime: 0,
progress: 0,
},
} as ClientRenderJob)
: job,
),
Expand Down
47 changes: 47 additions & 0 deletions packages/studio/src/test/client-render-progress.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {expect, test} from 'bun:test';
import {
formatEtaString,
getClientRenderProgressMessage,
} from '../components/RenderQueue/client-render-progress';

test('formats ETA strings like server-side renders', () => {
expect(formatEtaString(5_000)).toBe('5s');
expect(formatEtaString(65_000)).toBe('1m 5s');
expect(formatEtaString(3_725_000)).toBe('1h 2m 5s');
});

test('formats client render progress message while rendering', () => {
expect(
getClientRenderProgressMessage({
encodedFrames: 10,
totalFrames: 30,
doneIn: null,
renderEstimatedTime: 65_000,
progress: 0.55,
}),
).toBe('Rendering 10/30, time remaining: 1m 5s');
});

test('formats client render progress message while encoding', () => {
expect(
getClientRenderProgressMessage({
encodedFrames: 24,
totalFrames: 30,
doneIn: null,
renderEstimatedTime: 0,
progress: 0.94,
}),
).toBe('Encoded 24/30');
});

test('returns getting composition before frame totals are known', () => {
expect(
getClientRenderProgressMessage({
encodedFrames: 0,
totalFrames: 0,
doneIn: null,
renderEstimatedTime: 0,
progress: 0,
}),
).toBe('Getting composition');
});
Loading
Loading