HomeroomBook a demo

homeroom.video

School video, gated at the clip and captioned at the door.

Two refusals, not one player: nothing serves without permission, and nothing publishes without a captions track an author wrote.

On this page

Eleven sections. The one worth reading if you only read one is the honest state, at the bottom, which lists what is running in the route today, what is built but not folded in, and what does not exist.

01

What Homeroom Video is

Homeroom Video is the motion lane inside Homeroom: the stored clip, the join that records which students are in it, the gate that decides whether a stranger may see it, and the rule that blocks it from publishing without captions. It is one address for the part of the platform that handles video, on the same school record and the same permission machinery as the photographs, the records, and the money.

It is not a video host you point a district at instead of the platform you already run, and it is not a highlight-reel pitch. Every school-video vendor sells the player. The player is the easy part and it has been the easy part since browsers learned the video element. The hard part is the two refusals -- the one that stops a non-consented child reaching a stranger, and the one that stops an uncaptioned clip reaching the public web -- because those are the two that turn into a complaint with a case number.

So this page is organised around the two walls. Everything else on it exists to say what the walls are made of, where they are enforced, and precisely where they are not.

02

What a clip actually is here

A clip is a row, not a file in a folder. That distinction is the whole reason the rest of the page can make promises, so here is the row as a school reads it.

The stored clip, field by field.
FieldWhat it means for a school
school_idThe clip belongs to one school and the database enforces that, not the application. Row-level security means a query from another school returns nothing rather than being trusted to filter.
statuspending, approved, or rejected -- and the default is pending. A newly uploaded clip is not servable because nobody decided it was; it is unservable because nobody has decided anything yet.
storage_key / poster_storage_keyWhere the bytes and the poster frame live. The public route never returns either one; it returns the clip through a gate or it returns a 404.
duration_seconds / content_typeWhat the file is and how long it runs, recorded on the row rather than sniffed at serve time.
alt_textThe author's short description of the clip. It is a separate field from the captions track, because a description of a video and a transcript of its speech are two different accommodations.
deleted_at / deleted_run_idThe records-retention tombstone, added by the retention sweep wiring. A clip removed by a retention run is marked by the run that removed it, so a school can answer which policy removed which clip.

Alongside it sits a second, smaller row type: one record per student a clip depicts, carrying the school identifier a second time so the database can enforce isolation directly on it. That is the same shape the photo lane uses to record who is in a pose, and it is deliberately the same shape -- a school should not have to learn two different permission models for two different media.

Because the depiction is a record rather than an inference, the gate never has to guess. There is no face detection deciding who is in the frame and no model asserting an identity. A person said who is in this clip, and the gate reads what the person said.

Source: packages/db/prisma/schema.prisma -- MediaAsset, MediaAssetKind, MediaAssetStatus, MediaAssetDepiction.

03

The two walls sit at different moments and answer different questions. The first runs on every public request for a byte. The second runs once, when someone tries to publish. Both fail closed, which means the failure mode of a bug in either one is that a school sees less than it expected, never that a stranger sees a child.

Serve time: nothing leaves without permission

Fail-closed. A clip with an unconsented depicted student returns 404.

A clip is joined to the students it depicts by one row per student -- the same shape the photo lane uses for a pose. That join is what makes the gate decidable: the server does not ask “is this video public”, it asks “is every child in this frame permitted to be seen by this viewer”, and if it cannot answer yes it does not serve.

The refusal is a uniform 404. Not a 403, which would confirm the clip exists; not an error body naming the storage key. A stranger probing identifiers learns the same thing from a real clip and from a fabricated one: nothing.

The captions file rides the same gate. A transcript is speech, and speech identifies a child as surely as a face does, so the caption endpoint runs the identical consent decision rather than a looser one.

publicVideoUrlIfConsented (six consumers, three anonymous serving paths -- apps/api/src/domain/photo-serving-gate.ts:138); GET /v1/public/videos/:id (apps/api/src/routes/public-videos.ts) with two dedicated pending-leak security suites; GET /v1/public/videos/:id/captions on the same gate (apps/api/src/app.ts:3512-3515).

Publish time: nothing goes out uncaptioned

Blocking, at the same severity as a missing image alt.

A video without an author-written captions track is a blocking accessibility finding under WCAG 1.2.2, and the publish wall treats it exactly the way it treats an image with no alt text under 1.1.1. It is not a warning, not a score, not a badge on a dashboard. The publish does not happen.

A subtitles-only track does not clear the bar. Subtitles carry dialogue for a hearing viewer; captions carry the sound -- the whistle, the applause, the name called from off-camera. The shared predicate that decides this is the same function on every surface, so one publish path cannot quietly become more permissive than another.

There is no speech recognition in this lane. Captions are authored content. The wall's own message tells the author to supply a WebVTT file and says, in the product's words, that captions “are not generated for you”.

videoNeedsCaptions (apps/api/src/domain/video-captions-predicate.ts, the one surface-agnostic decision); captionsTrackSatisfies122 (packages/shared/src/captions-track.ts:84); enforcement folded into the real checkPageA11y loop, which the website publish wall calls at apps/api/src/routes/website-surface.ts:176/:225/:262.

Why the captions decision lives in one function

There are two publish paths in this platform that can place a video: the website builder, and the online edition that a student publication runs. A rule written twice is a rule that drifts, and the drift is always in the permissive direction because nobody notices a gate that stopped stopping things. So the decision is one function that both paths call, and neither path is allowed its own copy of the answer.

That design choice is also what makes the disclosure in the honest-state section below legible. The two paths share the decision; they do not currently share the enforcement. One of them calls the rule inside the check its publish wall already runs. The other holds the predicate and awaits the fold at its own chokepoint. Both facts are true, and a page that printed only the first would be the kind of confident half-sentence this platform is built to refuse.

04

Coach film breakdown, with its limits stated first

Coaches mark film by play and time-code, and then want a cut-up of the marked plays. Both halves exist here: a tagging pass that validates and folds an append-only tag log into a play index, and a playlist builder that turns a selected, ordered set of tags into a sequence of clip windows the browser plays back to back.

Here is what that route does not do, taken from its own header rather than paraphrased into something flattering. It stores no video byte. It performs no server-side transcode and no concatenation -- the cut-up is assembled client-side from windows into clips that already exist. It adds no migration and builds no new engine; it is glue over a pure, separately tested module. And the tag log has no durable server-side store, so the caller supplies the working log on every call and receives the folded result back.

That last one is the important limit and it is why this sits in the built-but-limited column rather than the finished one. A coaching staff can run a working session; a coaching staff closes the laptop and the session is gone, because nothing on the server is holding it. The sibling live-scoreboard surface takes the identical posture and says so in the same words, which is at least consistent rather than accidental.

Every byte a coach watches through this still routes through the consent-gated public video path or its authenticated twin. The film surface does not get its own looser door.

Source: apps/api/src/routes/athletics-film-tags.ts over packages/shared/src/cbuild-athletics-film-tags-data-model.ts.

05

Who this is for

Four people in a district touch school video, and they are usually not the same person and usually do not talk to each other until something goes wrong. Each of them is holding a different problem.

The communications director

What you are holding. A folder of assembly footage, a board meeting recording, and a superintendent who wants it on the website by Friday.

What this gives you. A publish path that refuses the uncaptioned upload before the district owns the problem, and a serve path that will not put a non-consented child in front of a stranger.

The yearbook adviser

What you are holding. Student journalists producing motion pieces for the online edition alongside the printed book, on the same story blocks.

What this gives you. Video as a first-class block in the same story document as text and photographs, with the same consent posture the photo lane already has -- not a separate embed pasted in from somewhere else.

The athletic director

What you are holding. Game film, a coaching staff that marks plays by time-code, and a request for a highlight reel that does not involve anyone downloading raw footage.

What this gives you. Clip tagging by play and time-code and a cut-up playlist assembled from the tags, over a consent-gated byte lane -- described below with its real limits, which are substantial.

The technology director

What you are holding. An accessibility remediation plan, a records-retention schedule, and an auditor who will ask which control enforced which rule.

What this gives you. Two controls that live in the database and the publish route rather than in a policy document: a per-student depiction join, and a blocking rule in the accessibility check the publish wall already calls.

What they have in common is that the thing they are afraid of is not the upload failing. It is the upload succeeding when it should not have.

06

What it costs to not have this

The cost is not a subscription you are not paying. It is a set of tasks that get done by a person remembering, which is another way of saying they get done until the week they do not.

Permission becomes a memory. Without a per-student depiction record, deciding whether a clip may be public means someone watching it and recalling which families opted out this year. That is a real person doing a real review under deadline, and the failure mode is not a rejected upload -- it is a published one that nobody caught.

Withdrawal becomes an archaeology project. When a family changes their mind, a district without a serve-time gate has to find every page, embed, and social post carrying that clip. With the gate, the next request simply gets the new answer, and nobody has to be certain they found all of them.

Accessibility becomes a report you read after publishing. An audit that tells you which of your videos are uncaptioned is a list of things already public. A blocking rule at publish time is the same information delivered at the only moment it is cheap to act on. The difference between the two is the remediation window, and the remediation window is where the complaint lives.

Captions become an afterthought priced as an emergency. Authoring a track while the piece is being edited costs the editor's attention. Authoring it after a complaint costs a vendor's rush rate across a back catalogue, and it costs it for every clip at once.

None of these are hypothetical failures of an imagined district and none of them are presented here with a number attached, because we do not have a number and inventing one would undo the point of the page.

07

Objections, answered

These are the six that come up, written as they are actually asked rather than as they would be convenient to answer.

We already have a video platform. Why would we move?

Most likely you should not move the hosting, and this page is not asking you to. What a general-purpose video platform does not have is your student list, your permission records, or your retention schedule, so its access control is a link setting and its accessibility posture is a report you read afterwards.

The difference is where the decision lives. Here the refusal happens in the serving route and the publish route, against the same permission records that govern a photograph of the same child. A link setting on an outside platform is a control you have to remember; a fail-closed gate is a control you have to deliberately remove.

Will the captions wall stop our staff from publishing anything at all?

It will stop them from publishing a video with no captions track, which is the intended behaviour and is the same behaviour they already experience for an image with no alt text. Everything else publishes normally.

We are not going to soften this into a warning. A warning on an accessibility finding is a finding that ships. If the wall is inconvenient the honest fix is authoring time for captions, not a quieter alarm -- and relaxing a fail-closed guard to stop it firing loses the same bytes while removing the alarm.

Can the system write the captions for us?

No. There is no speech recognition, no transcription, and no machine translation in this lane, and the source says so where the decision is made rather than only in marketing copy.

That is a deliberate position, not a missing feature we are being coy about. A machine transcript of a classroom is a recording of children being interpreted by a model, and it is wrong by exactly the amount that matters -- names, and the words a district would be held to. When captions exist here, a person wrote them.

What happens to a clip when a family withdraws permission?

The gate is evaluated at serve time, not baked into a published URL, so the next request for that clip re-asks the question and gets the new answer. A withdrawal does not require anyone to hunt down a page and unpublish it.

Separately, the clip row carries a retention tombstone written by the sweep that removed it, so “which policy removed this and when” is answerable from the record rather than from memory.

Is any of this AI?

No part of this lane is. Nothing is generated, transcribed, summarized, ranked, or graded by a model. Across this platform, AI is off and stays off until it is turned on deliberately and said out loud.

Is homeroom.video a separate product we buy?

It is this address, for this part of the platform. The video lane is inside Homeroom, on the same school record, the same student list, and the same permission machinery as everything else. This page exists so the part of the platform that handles motion has a door with its own name on it, and so the two walls are written down somewhere a district can read them before signing anything.

08

How this fits the family

Homeroom is one platform with several front doors, and the doors exist because the people who walk through them are different people. A parent should not have to read a procurement page to find out what permission means; an athletic director should not have to read the parent page to find out what happens to game film.

The nearest addresses in the Homeroom family.
AddressWhat it is, and how it differs from this page
homeroom.softwareThe flagship: the whole K-12 operating system -- permission, the student list, the money, and privacy -- that the video lane described here is a part of.
homeroom.galleryThe still-image library. The photo lane and the video lane share a consent posture and a school record, and are different stored objects with different accessibility law over them.
homeroom.familyThe address written for parents and guardians rather than for an institution -- including what permission actually means and how it is withdrawn.

This page shares the house shell with them deliberately -- one wordmark, one navigation, one footer -- and carries its own accent and its own film-strip rule so it is recognisably this door and not a copy of the one next to it. Family similarity is the point within a family; it stops being the point at the family boundary.

09

The honest state of the product

Three states, and they are not averaged into a percentage. Either a control runs in the route that serves your request, or it is built and awaiting a fold, or it does not exist.

  • In the running route The serve-time consent gate. One gate function, six callers, three of them anonymous serving paths. Two dedicated security suites pin the pending-clip and pending-original cases specifically, because those are the two ways a clip leaks before anyone approves it.
  • In the running route The captions wall on the website publish path. The 1.2.2 rule is folded into the accessibility check the publish wall already calls, at three call sites in the website surface route. That is enforcement in the route that runs, not a parallel checker nobody invokes.
  • In the running route The clip record and its per-student depiction join. Both are in the schema with school-scoped row-level security, unique constraints on (school, storage key) and (clip, student), and indexes on the three lookup paths the gate uses.
  • Built, not folded in The captions wall on the online-edition publish path. The predicate and the finding builder exist and reuse the identical decision, so the two surfaces cannot drift. The fold into the publish route itself is not landed: the durable chokepoint is the publish-state handler in the publications route, and that handler has not been rewritten. Said here rather than smoothed over, because a page claiming both paths enforce today would be false in the direction that matters.
  • Built, not folded in Coach film tagging and the cut-up playlist. The engine is a pure, tested module and the route is deliberate glue over it. What it does not do is listed in its own header and repeated in this page's film section: no durable tag store, no server-side transcode, no concatenation, and not one video byte stored by that route.
  • Not built Live captioning. Real-time captioning of a stream is a named gap in this platform, tracked as a gap rather than described as a feature. Nothing on this page or in this lane provides it.
  • Not built Automatic caption generation. There is no speech recognition here. This is a position rather than a backlog item, and it is stated in the source where the caption decision is made.
  • Not built Anything involving money on this address. No price, no plan, no checkout, and no payment claim of any kind. The next step from this page is a conversation.

Read the middle band carefully, because it is the one a brochure would have quietly merged into the first. A predicate that exists and a predicate that runs at the chokepoint are two different facts, and the gap between them is exactly the kind of thing a district discovers during an incident rather than during a demo.

Nothing on this page claims a capability the serving path does not hold. Where a wall runs, it is named with the route that calls it. Where it does not, that is written above in the present tense rather than deferred into a promise.

10

The next concrete step

There is nothing to buy on this page and nothing to sign up for. The useful next step is a conversation with three things in front of it: the kinds of video your district actually publishes, who currently decides whether a clip may be public, and what your accessibility remediation plan already commits you to.

Bring the awkward case rather than the easy one. The assembly recording with a child whose family opted out. The game film a booster club wants on a public page. The board meeting that runs three hours and has never been captioned. Those are the cases the two walls were built for, and they are the cases worth testing the answer against.

What this page does not claim

No school is named, no customer is quoted, and no adoption figure appears anywhere above. There are none to report, and inventing one would be the cheapest possible way to lose the argument this page is making.

Nothing here is AI. No captions are generated, no speech is transcribed, no clip is summarised or ranked by a model.

No money moves on this address. There is no price, no plan, and no checkout.

Student faces, names, and voices are consent-gated and never public. The mechanism is named above so you can check it rather than take our word for it.

Where a control is built but not folded into the route that runs, the status list above says so in that many words.