Feature: Video Message Recording
Relevant source files
The following files were used as context for generating this wiki page:
The Video Message Recording feature is the core functionality of the Heyme application. It enables users to create and schedule video messages for future delivery to themselves or others. The system handles video capture, file uploads, cloud storage, metadata management, and scheduled email dispatch.
The overall user journey involves a user recording or uploading a video, providing recipient details and a future delivery date. The video is stored in Cloudinary, and its metadata is saved in a MySQL database. A scheduled task later queries the database for due messages and dispatches them via email, which contains a unique link to a watch page where the recipient can view the video.
Sources: README.md, pages/recording.tsx, pages/api/dispatch.ts
System Architecture Overview
The feature integrates several technologies to function. The frontend, built with Next.js and React, utilizes browser-native WebRTC APIs for live recording. Video files are uploaded directly from the client to Cloudinary. Application data is stored in a MySQL database, and scheduled emails are dispatched using the Zeptomail service.
The following diagram illustrates the end-to-end flow of the video recording and delivery process.
This diagram shows the complete lifecycle of a video message, from creation to viewing.
Sources: README.md, pages/recording.tsx, pages/api/store.ts, pages/api/dispatch.ts, pages/watch/[id].tsx
Recording UI and Logic (pages/recording.tsx)
The primary user interface for this feature is the Recording component. It manages two distinct user flows: live video recording and video file upload. The component uses the detect-browser library to identify the user's OS, providing an upload-first experience for iOS users.
Sources: pages/recording.tsx:25, 52
Live Recording
For live recording, the application requests access to the user's camera and microphone using navigator.mediaDevices.getUserMedia.
The process is as follows:
- Permission & Device Access: The
accessUserDevicesfunction attempts to get the media stream. If the user denies permission, ahasPermissionIssuesstate is set, and an error message is displayed. If successful, available cameras and microphones are listed for user selection. - Recording Countdown: When the user clicks "Start recording", a 3-second countdown timer is initiated.
- Recording State: The
MediaRecorderAPI is used to capture the video stream. The recording is limited to 5 minutes (300 seconds), tracked by thetimeElapsedstate, which is decremented using theuseIntervalhook. The recording stops automatically when the timer reaches zero or if the user manually stops it. - Preview: Once stopped, the recorded video chunks are compiled into a
Blob, and a local URL is generated usingURL.createObjectURL()for the user to preview the video before uploading.
The following diagram illustrates the live recording flow within the component.
File Upload
As an alternative to live recording, users can upload a video file.
- A hidden
<input type="file">is triggered. - The selected file is validated to ensure its duration does not exceed 5 minutes (300 seconds). This is checked by creating a temporary
<video>element and listening for theloadedmetadataevent. - If valid, an object URL is created for preview. An error message is shown for videos that are too long.
Sources: pages/recording.tsx:492-507
Key State Variables
The Recording component relies on several React state variables to manage the UI and recording logic.
| State Variable | Description |
|---|---|
isRecording | boolean: Tracks if the MediaRecorder is currently active. |
isStopped | boolean: Becomes true after the recording is stopped, revealing the form. |
videoBlobUrl | `string |
uploadedVideo | `File |
hasPermissionIssues | boolean: Indicates if camera/microphone access was denied. |
timeElapsed | number: Countdown timer for the 5-minute recording limit. |
isUploading | boolean: Tracks the upload state to show a progress bar. |
complete | boolean: Becomes true after a successful upload, triggering the success view. |
isUsingFileUpload | boolean: Toggles the UI between live recording and file upload modes. |
email | string: Stores the recipient's email address. |
deliverIn | string: Stores the scheduled delivery date. |
Sources: pages/recording.tsx:28-58
Video Processing and Storage
After a video is recorded or selected, the next phase involves uploading it to a cloud service and storing its associated metadata in the database.
Upload to Cloudinary and API Storage
The upload process is orchestrated from the frontend and involves multiple steps, as shown in the sequence diagram below.
This diagram shows the client-side upload to Cloudinary, followed by a server-side call to store metadata.
Sources: pages/recording.tsx:78-106, pages/api/store.ts:8-26
API Endpoint: /api/store
This endpoint is responsible for persisting the video's metadata.
- Method:
POST - Handler:
handleStoreVideo - Description: It receives the video details after it has been uploaded to Cloudinary. It validates the incoming data and inserts a new record into the
Videostable in the MySQL database.
The expected request body is:
| Field | Type | Description | Required |
|---|---|---|---|
video_url | string | The secure URL of the video from Cloudinary. | Yes |
email | string | The recipient's email address. | Yes |
send_at | string | The scheduled delivery date (e.g., "YYYY-MM-DD"). | Yes |
send_to | string | "yourself" or "someone_else". | Yes |
created_at | string | The date the recording was created. | Yes |
Sources: pages/api/store.ts:8-20
Scheduled Dispatch (/api/dispatch)
This API endpoint is designed to be triggered by a scheduled task (e.g., a daily cron job). Its purpose is to find all videos scheduled for delivery on the current day and send them via email.
The logic flow is as follows:
This process ensures that each video is sent only once by updating its sent status in the database after dispatch. The email body is populated from a template file.
Sources: pages/api/dispatch.ts, public/mails/email.html
Viewing the Video (/watch/[id])
This page displays the video message to the recipient. It's a server-side rendered (SSR) page to ensure the video data is available on the initial load.
Data Fetching (getServerSideProps)
The getServerSideProps function is responsible for fetching the video data before the page is rendered.
- It extracts the video
idfrom the URL parameters. - It establishes a database connection.
- It calls
fetchVideoByIdto retrieve the video record from the database. - If the video is not found, it returns a
404status. - If found, it formats the
created_atdate and passes the video object (id,video_url,created_at) as props to the component.
// Source: pages/watch/[id].tsx:60-83
export async function getServerSideProps({ params }: GetServerSidePropsContext): Promise<GetServerSidePropsResult<{
video: {
id: number
video_url: string
created_at: string
}
}>> {
// establish database connection
const connection = createMysqlConnection()
// fetch video from database with the id from url
const video = (await fetchVideoById(connection, params['id'])) as WatchVideoProps['video']
// return video as props to component
if (!video) {
return {
notFound: true
}
}
return {
props: {
video: {
id: video.id,
video_url: video.video_url,
created_at: dayjs(video.created_at).format('MMM D, YYYY'),
}
}
}
}
Sources: pages/watch/[id].tsx:60-83, tests/api/watch.spec.tsx:40-55
Rendering
The WatchVideo component uses the react-html5video library to render the video player, using the video_url passed in props. It also displays the message "A message from [creation_date]". A special warning is displayed on iOS devices if the video format is .mkv, as it may not be playable.
Sources: pages/watch/[id].tsx:24-30
Post-Upload Experience
Upon a successful upload, the Recording component renders the Success component. This provides immediate positive feedback to the user. It displays a confirmation message, an animated GIF, and triggers a confetti effect using runFireworks. It also includes social sharing links for Facebook, LinkedIn, and Twitter to encourage users to share the application.
Sources: pages/recording.tsx:342-346, components/success.tsx