Skip to content

Add Firebase Cloud Function for screen recording conversion - #1432

Open
shresthalucky wants to merge 4 commits into
devfrom
screen-recording-firebase-function
Open

Add Firebase Cloud Function for screen recording conversion#1432
shresthalucky wants to merge 4 commits into
devfrom
screen-recording-firebase-function

Conversation

@shresthalucky

@shresthalucky shresthalucky commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a Firebase Cloud Function that converts uploaded screen recordings to webm server-side, fixing cross-browser playback compatibility
  • Skip conversion when already mp4/webm-compatible; save without extension plus converted metadata otherwise
  • Point FirebaseStorageEngine at the converted asset

Split out of #1174 (replay videos should load faster).

Test plan

  • Deploy function to a Firebase project and confirm screen recordings uploaded from Chrome/Safari/Firefox convert and play back cross-browser
  • Confirm non-convertible files are skipped and do not mark converted in metadata

@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

A preview of ccc07aa is uploaded and can be seen here:

https://revisit.dev/study/PR1432

Changes may take a few minutes to propagate.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2e86cb035

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread functions/src/index.ts
Comment on lines +50 to +52
const fileName = path.basename(filePath);
const tmpInput = path.join(os.tmpdir(), fileName);
const tmpOutput = path.join(os.tmpdir(), `${fileName}.tmp`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use invocation-unique temporary paths

When concurrent events have the same basename—for example, two studies both uploading p1_task1—they use identical input and output paths in the shared instance temp directory. A second download or cleanup can overwrite/delete files while the first invocation is processing them, and because each invocation uploads that shared output to its own event-specific destination, recordings can be corrupted or copied into the wrong study. Create a unique temporary directory or include a unique event/object identifier in both paths.

Useful? React with 👍 / 👎.

Comment thread functions/src/index.ts
Comment on lines +75 to +78
await bucket.upload(tmpOutput, {
destination: filePath,
metadata: { contentType: 'video/webm', metadata: { converted: 'true' } },
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve Firebase download tokens when replacing recordings

When conversion succeeds, this upload creates a replacement object generation while supplying only the new converted custom metadata. It therefore drops the original firebaseStorageDownloadTokens metadata created by the Firebase client upload; subsequent calls to getDownloadURL in FirebaseStorageEngine cannot construct a download URL for the converted object. Preserve the existing token metadata or explicitly issue a new token when overwriting the recording.

Useful? React with 👍 / 👎.

Comment thread functions/src/index.ts
Comment on lines +59 to +61
if (!await isWebmCopyCompatible(tmpInput)) {
logger.info(`Skipping: codecs not compatible with WebM stream copy: ${filePath}`);
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Transcode Safari codecs instead of skipping them

For recordings containing H.264 and/or AAC streams, this predicate returns false and the handler exits without producing WebM output. The client constructs MediaRecorder without selecting a MIME type, so browsers that default to an H.264/AAC MP4 recording—most notably Safari—naturally take this path and remain in the original format, defeating the stated cross-browser conversion. These streams need transcoding rather than being rejected from the conversion pipeline.

Useful? React with 👍 / 👎.

@JackWilb JackWilb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes on the exact current head e2e86cb.\n\nI verified the three existing Codex inline findings and agree they are blockers:\n- temporary-file collisions across overlapping invocations;\n- loss of Firebase download-token metadata when replacing the object;\n- H.264/AAC recordings being skipped instead of transcoded.\n\nAdditional verified blockers are included inline for missing ffprobe, stale-generation overwrites, the asynchronous source/final-readiness race, incorrect IAM setup guidance, and the stated converted-metadata contract.\n\nPlease address these cases and add function-specific coverage for probe failure, codec decisions, metadata/token preservation, invocation isolation, and stale-event handling before re-requesting review.

Comment thread functions/src/index.ts

admin.initializeApp();
setGlobalOptions({ maxInstances: 5 });
ffmpeg.setFfmpegPath(ffmpegInstaller.path);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Provision ffprobe separately\n\nThis configures the bundled ffmpeg executable, but fluent-ffmpeg.ffprobe() still resolves ffprobe through PATH. In a deployment without a system ffprobe, the callback converts the probe error to false at lines 24-27, and every recording then takes the silent skip path at lines 59-61. Bundle a platform-appropriate ffprobe binary and call setFfprobePath, then cover this in a deployment/emulator test.

Comment thread functions/src/index.ts
});

logger.info(`Uploading ${filePath}`);
await bucket.upload(tmpOutput, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Guard the replacement against stale generations\n\nThe handler ignores event.data.generation and overwrites the same object path without an ifGenerationMatch precondition. If an older finalization event is delayed or retried after a newer upload replaces that path, this invocation can upload its older conversion over the newer recording. Download the event generation explicitly and use a generation-match precondition; treat a failed precondition as a stale event.

Comment thread functions/src/index.ts
await bucket.file(filePath).download({ destination: tmpInput });

if (!await isWebmCopyCompatible(tmpInput)) {
logger.info(`Skipping: codecs not compatible with WebM stream copy: ${filePath}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the converted-metadata contract consistent\n\nThe PR test plan says non-convertible files are skipped and still marked converted, but this return leaves the original object and metadata untouched. That also makes a probe failure indistinguishable from an intentional terminal skip. Either persist an explicit terminal status while preserving the download metadata, or update the acceptance contract and add coverage for the chosen behavior.

): Promise<string | null> {
const storage = getStorage();

// Fetches webm converted by firebase function

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Do not expose the source while conversion is pending\n\nThe browser upload, asynchronous function output, and analysis read all use the same object path. A reader can therefore fetch the original recording after upload but before the function replaces it, so this path does not reliably return the converted asset. Use distinct source/final object identities or make the read path wait for terminal conversion metadata.

Comment thread functions/README.md

```bash
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--member="serviceAccount:service-PROJECT_NUMBER@gcp-sa-eventarc.iam.gserviceaccount.com" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Document the correct least-privilege IAM principals\n\nThis grants project-wide roles/storage.admin to the Eventarc service agent, although the function runtime is the identity that performs the Storage download/upload. The direct Cloud Storage Eventarc setup also requires the Cloud Storage service agent publisher role and the trigger/runtime identities appropriate roles. Please document the correct principals with least privilege, and make deployment project selection explicit instead of relying on an ignored .firebaserc and unqualified firebase deploy.

@shresthalucky
shresthalucky changed the base branch from main to dev August 24, 2026 14:18
@shresthalucky
shresthalucky force-pushed the screen-recording-firebase-function branch from 97731ee to ccc07aa Compare August 24, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants