Skip to content

Commit

Permalink
add progress updates
Browse files Browse the repository at this point in the history
  • Loading branch information
avibn committed Jul 22, 2024
1 parent aee2937 commit 78f8f73
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 53 deletions.
Binary file modified .github/images/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion electron/handlers/renderHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ ipcMain.handle(
async (event, inputProps: Record<string, unknown> = {}) => {
try {
log.info("Rendering media...");
await render(inputProps);
await render(inputProps, (progress) => {
event.sender.send("RENDER_PROGRESS", progress);
});
return true;
} catch (error) {
log.error("Failed to render media:", error);
Expand Down
94 changes: 53 additions & 41 deletions electron/preload/index.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,67 @@
import { ipcRenderer, contextBridge } from 'electron'
import { contextBridge, ipcRenderer } from "electron";

// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld('ipcRenderer', {
contextBridge.exposeInMainWorld("ipcRenderer", {
on(...args: Parameters<typeof ipcRenderer.on>) {
const [channel, listener] = args
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
const [channel, listener] = args;
return ipcRenderer.on(channel, (event, ...args) =>
listener(event, ...args)
);
},
off(...args: Parameters<typeof ipcRenderer.off>) {
const [channel, ...omit] = args
return ipcRenderer.off(channel, ...omit)
const [channel, ...omit] = args;
return ipcRenderer.off(channel, ...omit);
},
send(...args: Parameters<typeof ipcRenderer.send>) {
const [channel, ...omit] = args
return ipcRenderer.send(channel, ...omit)
const [channel, ...omit] = args;
return ipcRenderer.send(channel, ...omit);
},
invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
const [channel, ...omit] = args
return ipcRenderer.invoke(channel, ...omit)
const [channel, ...omit] = args;
return ipcRenderer.invoke(channel, ...omit);
},
removeAllListeners(
...args: Parameters<typeof ipcRenderer.removeAllListeners>
) {
return ipcRenderer.removeAllListeners(...args);
},
removeListener(...args: Parameters<typeof ipcRenderer.removeListener>) {
return ipcRenderer.removeListener(...args);
},

// You can expose other APTs you need here.
// ...
})
});

// --------- Preload scripts loading ---------
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
return new Promise(resolve => {
function domReady(
condition: DocumentReadyState[] = ["complete", "interactive"]
) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true)
resolve(true);
} else {
document.addEventListener('readystatechange', () => {
document.addEventListener("readystatechange", () => {
if (condition.includes(document.readyState)) {
resolve(true)
resolve(true);
}
})
});
}
})
});
}

const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find(e => e === child)) {
return parent.appendChild(child)
if (!Array.from(parent.children).find((e) => e === child)) {
return parent.appendChild(child);
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find(e => e === child)) {
return parent.removeChild(child)
if (Array.from(parent.children).find((e) => e === child)) {
return parent.removeChild(child);
}
},
}
};

/**
* https://tobiasahlin.com/spinkit
Expand All @@ -58,7 +70,7 @@ const safeDOM = {
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = `loaders-css__square-spin`
const className = `loaders-css__square-spin`;
const styleContent = `
@keyframes square-spin {
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
Expand All @@ -85,34 +97,34 @@ function useLoading() {
background: #282c34;
z-index: 9;
}
`
const oStyle = document.createElement('style')
const oDiv = document.createElement('div')
`;
const oStyle = document.createElement("style");
const oDiv = document.createElement("div");

oStyle.id = 'app-loading-style'
oStyle.innerHTML = styleContent
oDiv.className = 'app-loading-wrap'
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
oStyle.id = "app-loading-style";
oStyle.innerHTML = styleContent;
oDiv.className = "app-loading-wrap";
oDiv.innerHTML = `<div class="${className}"><div></div></div>`;

return {
appendLoading() {
safeDOM.append(document.head, oStyle)
safeDOM.append(document.body, oDiv)
safeDOM.append(document.head, oStyle);
safeDOM.append(document.body, oDiv);
},
removeLoading() {
safeDOM.remove(document.head, oStyle)
safeDOM.remove(document.body, oDiv)
safeDOM.remove(document.head, oStyle);
safeDOM.remove(document.body, oDiv);
},
}
};
}

// ----------------------------------------------------------------------

const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
const { appendLoading, removeLoading } = useLoading();
domReady().then(appendLoading);

window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading()
}
ev.data.payload === "removeLoading" && removeLoading();
};

setTimeout(removeLoading, 4999)
setTimeout(removeLoading, 4999);
12 changes: 10 additions & 2 deletions electron/remotion/render.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { renderMedia, selectComposition } from "@remotion/renderer";
import {
renderMedia,
RenderMediaOnProgress,
selectComposition,
} from "@remotion/renderer";
import { app, shell } from "electron";
import log from "electron-log/main";
import os from "os";
Expand Down Expand Up @@ -118,7 +122,10 @@ if (app.isPackaged) {
* @param inputProps - The input props for the composition.
* @returns A Promise that resolves when the video rendering is complete.
*/
export default async function render(inputProps: Record<string, unknown>) {
export default async function render(
inputProps: Record<string, unknown>,
onProgress?: RenderMediaOnProgress
) {
const compositionId = "HelloWorld";

const bundleLocation = path.join(app.getAppPath(), "out/remotion-bundle");
Expand Down Expand Up @@ -150,6 +157,7 @@ export default async function render(inputProps: Record<string, unknown>) {
outputLocation: downloadsFolderPath,
inputProps,
binariesDirectory,
onProgress,
});
log.info(`Video rendered: ${compositionId}.mp4`);

Expand Down
5 changes: 0 additions & 5 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

Expand Down Expand Up @@ -37,10 +36,6 @@
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
Expand Down
78 changes: 76 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import "./App.css";

import { Player } from "@remotion/player";
import { useState } from "react";
import { StitchingState } from "@remotion/renderer";
import { useEffect, useState } from "react";
import { z } from "zod";
import { HelloWorld, myCompSchema } from "./remotion/video/HelloWorld";

interface RenderProgress {
renderedFrames: number;
encodedFrames: number;
encodedDoneIn: number | null;
renderedDoneIn: number | null;
renderEstimatedTime: number;
progress: number;
stitchStage: StitchingState;
}

function App() {
const [isRendering, setIsRendering] = useState(false);
const [inputProps, setInputProps] = useState<z.infer<typeof myCompSchema>>({
Expand All @@ -19,8 +30,33 @@ function App() {
fps: 30,
},
});
const [renderProgress, setRenderProgress] = useState<RenderProgress | null>(
null
);

// Listen for render progress updates
useEffect(() => {
window.ipcRenderer.on(
"RENDER_PROGRESS",
(_event, renderProgress: RenderProgress) => {
setRenderProgress(renderProgress);
}
);
return () => {
window.ipcRenderer.removeAllListeners("RENDER_PROGRESS");
};
}, []);

const renderVideo = async () => {
setRenderProgress({
renderedFrames: 0,
encodedFrames: 0,
encodedDoneIn: null,
renderedDoneIn: null,
renderEstimatedTime: 0,
progress: 0,
stitchStage: "encoding",
});
setIsRendering(true);
console.log("Rendering video...");
const response: boolean = await window.ipcRenderer.invoke(
Expand All @@ -40,7 +76,7 @@ function App() {
return (
<div className="App">
<h1>Electron + Vite + React + Remotion</h1>
<div className="card flex flex-col items-center gap-4">
<div className="flex flex-col items-center gap-4">
<Player
component={HelloWorld}
inputProps={inputProps}
Expand All @@ -54,6 +90,44 @@ function App() {
<button onClick={renderVideo} disabled={isRendering}>
Render Video
</button>
{renderProgress && (
<>
<div className="flex flex-row gap-2 items-center justify-center text-sm">
{isRendering ? (
<>
<progress value={renderProgress.progress} max={1}></progress>
<p>{Math.round(renderProgress.progress * 100)}%</p>
<p className="text-gray-500">
({Math.round(renderProgress.renderEstimatedTime / 1000)}s
left)
</p>
</>
) : (
<p>Download Complete 🎉</p>
)}
</div>

<div className="flex flex-row gap-4 text-xs">
{isRendering ? (
<>
<p>
Status:{" "}
{renderProgress.stitchStage === "encoding"
? "Encoding"
: "Muxing Audio"}
</p>
<p>Rendered Frames: {renderProgress.renderedFrames}</p>
<p>Encoded Frames: {renderProgress.encodedFrames}</p>
</>
) : (
<>
<p>Encoding Duration: {renderProgress.encodedDoneIn}ms</p>
<p>Render Duration: {renderProgress.renderedDoneIn}ms</p>
</>
)}
</div>
</>
)}
</div>
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ body {
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}

h1 {
font-size: 3.2em;
line-height: 1.1;
}

p {
margin: 0 0;
}

button {
border-radius: 8px;
border: 1px solid transparent;
Expand Down

0 comments on commit 78f8f73

Please sign in to comment.