17 KiB
Deploying GlitchPlayer Into Another React + Vite App
This guide explains how to include GlitchPlayer inside another user interface that is also built with React and Vite.
The target mental model is:
- your host app owns routing, fetching, auth, persistence, and editor UI
GlitchPlayerowns presentation playback, responsive layout, slide rendering, gesture handling, pause/resume behavior, and voting UI
In other words, GlitchPlayer should behave like an embeddable runtime surface inside a larger product such as GnommoEditor.
For editor integrations, this package also exposes:
GlitchSlideRendererGlitchSlideThumbnail
For template pickers, prefer the normalized availability exports:
slideTemplateAvailabilityListslideTemplateAvailabilityByDisplayMode
These expand templates by display mode. If a template supports both square and fullscreen, it appears once for each supported format.
1. What The Host App Must Provide
To render GlitchPlayer, the host app needs to provide:
- a
presentationobject - a
slideRegistry - an optional player mode
- an optional target slide id
- an optional vote handler
- an optional player event handler
- an optional session id
Today, the public player surface is exported from:
The primary component is:
GlitchPlayerGlitchSlideRendererGlitchSlideThumbnail
The key types are:
PresentationDefinitionSlideRegistryVoteRequestClientEvent
For editor-driven slide forms and template pickers, this package also exports:
slideTemplateDefinitionsslideTemplateDefinitionListslideTemplateAvailabilityListslideTemplateAvailabilityByDisplayModeslideTemplateDraftsslideTemplateBudgets
For hosted interactive Glitch components, the player also supports a base URL convention:
VITE_GLITCH_COMPONENT_BASE_URL
If a GlitchComponentFrame slide omits componentUrl, the player resolves the iframe source as:
${VITE_GLITCH_COMPONENT_BASE_URL || "/glitch"}/glitch_${componentId.replace(/-/g, "_")}/index.html
That matches the pattern where built Glitch component assets are copied into public/glitch/....
2. Public API
Current host usage looks like this:
import {
GlitchPlayer,
GlitchSlideThumbnail,
createSessionId,
defaultSlideRegistry,
type PresentationDefinition,
type Slide,
type VoteRequest,
type ClientEvent
} from "gnommoplayer";
import "gnommoplayer/player/styles.css";
interface PlayerHostProps {
presentation: PresentationDefinition;
}
export function PlayerHost({ presentation }: PlayerHostProps) {
async function handleVoteCommit(vote: VoteRequest) {
await fetch(`/api/presentations/${presentation.id}/votes`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(vote)
});
}
function handlePlayerEvent(event: ClientEvent) {
console.log("[glitch-player-event]", event);
}
return (
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
mode="playback"
initialSessionId={createSessionId()}
onVoteCommit={handleVoteCommit}
onEvent={handlePlayerEvent}
/>
);
}
2.1 Rendering Slide Thumbnails In An Editor
Use GlitchSlideThumbnail for slide lists, cards, and inspector previews. It renders the actual slide component without video playback or player chrome.
function SlideCard({
presentationId,
slide,
}: {
presentationId: string;
slide: Slide;
}) {
return (
<GlitchSlideThumbnail
presentationId={presentationId}
slide={slide}
slideRegistry={defaultSlideRegistry}
viewportMode="desktop"
/>
);
}
Use GlitchSlideRenderer when you want a larger slide-only render surface without the surrounding player.
2.1 Previewing A Specific Slide
For editor workflows, GlitchPlayer can start in a slide-targeted preview mode.
Use:
mode="slide-preview"targetSlideId="some-slide-id"
Example:
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
mode="slide-preview"
targetSlideId="slide-05-chart-single"
initialSessionId={createSessionId()}
/>
That mode is useful when:
- an editor wants to preview one slide in context
- a template author wants to inspect a specific candidate
- GnommoEditor wants to jump directly to the currently selected slide in the timeline
3. Installation Strategy
There are two practical ways to use GlitchPlayer inside another Vite app.
Option A: Local workspace dependency
Use this when the host app and gnommoplayer live side-by-side during active development.
Example:
{
"dependencies": {
"gnommoplayer": "file:../gnommoplayer"
}
}
Then install as usual:
npm install
This is the easiest path while GlitchPlayer is still evolving rapidly.
Option B: Internal package
Use this when the player should be versioned and consumed more formally.
That usually means:
- adding a proper package export map
- producing a distributable build
- publishing to a private npm registry or workspace package feed
This repo is not fully packaged for that yet, but the code is now organized in a way that makes that a straightforward next step.
Option C: Built Files Only
Use this when another agent or deployment process is only allowed to fetch compiled assets from this repo.
Build the vendor assets with:
npm run build:lib
Or simply run:
npm run build
which now builds both the demo app and the vendor assets in one pass.
That produces these stable files:
dist/vendor/gnommoplayer.jsdist/vendor/gnommoplayer.css
This is the best path for GnommoEditor if the integration process is only allowed to pull built artifacts rather than source files.
3.1 Local Deployment Commands
This repo includes a deployment helper that copies the latest built vendor bundle into sibling apps.
Available commands:
npm run deploy:editor
npm run deploy:web
npm run deploy:all
./deploy.sh editor
./deploy.sh web
./deploy.sh all
The shell wrapper is useful when you just want a short deploy command from the repo root:
./deploy.sh
That defaults to:
npm run deploy:all
The npm commands are still available too:
npm run deploy:editor
npm run deploy:web
npm run deploy:all
These commands:
- run
npm run build:lib - ensure the vendor artifacts exist
- copy the latest bundle into the target app
Current target paths:
../gnommoeditor/src/vendor/gnommoplayer../gnommoweb/src/vendor/gnommoplayer
The underlying script lives at:
You can also call it directly:
node scripts/deploy-vendor.mjs editor
node scripts/deploy-vendor.mjs web
node scripts/deploy-vendor.mjs all
node scripts/deploy-vendor.mjs all --skip-build
Use --skip-build only when you know dist/vendor/ already contains the bundle you want to publish.
4. Styling Requirements
Import the player stylesheet once in the host app:
import "gnommoplayer/player/styles.css";
If the host app is consuming built files directly instead of the source package, import the compiled vendor assets instead:
import "../vendor/gnommoplayer/gnommoplayer.css";
import {
GlitchPlayer,
GlitchSlideThumbnail,
defaultSlideRegistry
} from "../vendor/gnommoplayer/gnommoplayer.js";
Inside this repo, that stylesheet entry currently lives at:
Important notes:
- The player expects to own its own visual surface.
- The host should avoid wrapping it in containers that impose additional padding, border radius, or clipping unless that is intentional.
- The player uses viewport-aware layout logic and expects enough room to center its stage within the available host surface.
Recommended host wrapper:
export function PlayerScreen({ children }: { children: React.ReactNode }) {
return (
<div
style={{
width: "100%",
minHeight: "100vh",
background: "#6c6c6c"
}}
>
{children}
</div>
);
}
If the host app has its own page chrome, toolbars, or inspector panels, mount the player inside a dedicated content region rather than inside an arbitrary card component.
4.1 Built-File Vendor Workflow
For a sibling React + Vite app that is only allowed to fetch built files, the cleanest integration flow is:
- Run
npm run build:libingnommoplayer - Copy:
dist/vendor/gnommoplayer.jsdist/vendor/gnommoplayer.css
- Place them in a vendor folder inside the host app, for example:
src/vendor/gnommoplayer/gnommoplayer.jssrc/vendor/gnommoplayer/gnommoplayer.css
- Import the CSS once near the app root
- Import the JS module where the host renders the player or thumbnails
The built JS bundle already inlines the dynamic slide imports, so the host does not need to manage extra chunk files for the standard slide set.
5. Presentation Data Contract
GlitchPlayer expects a PresentationDefinition.
That type is currently defined in:
At the moment, PresentationDefinition is an alias for the existing internal LectureDefinition shape.
The important fields are:
interface PresentationDefinition {
id: string;
title: string;
version: string;
durationSec: number;
segments: VideoSegment[];
slides: Slide[];
}
Each presentation needs:
- ordered video segments
- timed slides
- one or more
glitchSlidesper logical slide - valid
componentKeyvalues that exist in the registry supplied by the host
6. Loading Presentations In The Host App
The host app should fetch presentation data before rendering GlitchPlayer.
Example host loader:
import { useEffect, useState } from "react";
import { GlitchPlayer, type PresentationDefinition } from "gnommoplayer";
export function PresentationRoute({
presentationId
}: {
presentationId: string;
}) {
const [presentation, setPresentation] =
useState<PresentationDefinition | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/presentations/${presentationId}`)
.then(async (response) => {
if (!response.ok) {
throw new Error(`Failed to load presentation ${presentationId}`);
}
return (await response.json()) as PresentationDefinition;
})
.then((data) => {
if (!cancelled) {
setPresentation(data);
}
})
.catch((caughtError: unknown) => {
if (!cancelled) {
setError(
caughtError instanceof Error
? caughtError.message
: "Could not load presentation."
);
}
});
return () => {
cancelled = true;
};
}, [presentationId]);
if (error) {
return <div>{error}</div>;
}
if (!presentation) {
return <div>Loading…</div>;
}
return (
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
/>
);
}
7. Slide Registry Integration
The registry maps componentKey strings from presentation data to lazily-loaded React modules.
It looks like this:
type SlideRegistry = Record<
string,
() => Promise<{ default: React.ComponentType<SlideRuntimeProps> }>
>;
In this repo, the built-in demo registry lives at:
Example host registry:
import type { SlideRegistry } from "gnommoplayer";
export const gnommoEditorSlideRegistry: SlideRegistry = {
SquareYellow: () => import("./slides/SquareYellow"),
FullscreenSplit: () => import("./slides/FullscreenSplit"),
RevenueChart: () => import("./slides/RevenueChart"),
OrbitalModel3D: () => import("./slides/OrbitalModel3D")
};
Rules to keep in mind:
- Every
componentKeyreferenced by the presentation must exist in the registry. - Components should be deterministic and reasonably lightweight.
- Prefer
componentKey + propsover arbitrary runtime code from a database. - If the host supports custom submitted code, that code should be reviewed or sandboxed before entering the public registry.
8. Vote Handling
The player can emit vote requests through onVoteCommit.
That callback receives a VoteRequest:
interface VoteRequest {
lectureId: string;
slideId: string;
previousGlitchSlideId: string;
selectedGlitchSlideId: string;
voteDirection?: "up" | "down";
sessionId: string;
timestamp: string;
deviceContext: DeviceContext;
}
Even though the field is still named lectureId internally, treat it as the presentation id for integration purposes.
Recommended host behavior:
- send the vote request to your own API
- include auth/session context on the server side
- do not block the UI longer than necessary
- log failures, but keep playback usable
Example:
async function handleVoteCommit(vote: VoteRequest) {
const response = await fetch(`/api/presentations/${vote.lectureId}/votes`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(vote)
});
if (!response.ok) {
throw new Error("Vote request failed.");
}
}
9. Player Events
The host can also listen to runtime events via onEvent.
Current events include things like:
lecture_loadedsegment_changedslide_activatedsoft_pause_startedsoft_pause_endedglitch_slide_changedvote_committed
This is useful for:
- analytics
- debugging
- editor preview tooling
- QA capture
Example:
function handlePlayerEvent(event: ClientEvent) {
if (event.type === "slide_activated") {
console.log("Active slide:", event.slideId, event.glitchSlideId);
}
}
10. Recommended Host Layout
For a host app like GnommoEditor, a strong integration pattern is:
- app shell owns toolbar, navigation, inspector, and editor panels
- central preview region owns the player mount
- the player fills that preview region
Example:
export function EditorPreviewPane({
presentation
}: {
presentation: PresentationDefinition;
}) {
return (
<section
style={{
flex: 1,
minWidth: 0,
minHeight: 0,
background: "#5d5d5d"
}}
>
<GlitchPlayer
presentation={presentation}
slideRegistry={defaultSlideRegistry}
/>
</section>
);
}
Important:
- make sure the containing region allows height to collapse properly with
min-height: 0when used inside flex or grid shells - avoid nesting inside containers with accidental
overflow: hiddenunless intended - avoid wrapping the player in padded cards, because the player already frames its own stage
11. Vite Considerations
If the host app is also using Vite:
- dynamic
import()for slide components will work well - CSS import from the player should be straightforward
- local workspace dependency is usually the easiest during development
Potential issues to watch:
- duplicated React versions between host and player
- CSS ordering conflicts if the host imports many global styles after the player stylesheet
- path alias assumptions if you later move this from repo-local import to packaged dependency
Recommended safeguards:
- keep React and ReactDOM deduped in the workspace
- import player styles at the app shell level
- keep slide modules in the host app, not hidden inside the player package, if GnommoEditor should own template evolution
12. Minimal GnommoEditor Integration Plan
If the target is GnommoEditor, the simplest inclusion path is:
- Add
gnommoplayeras a workspace or file dependency. - Import
GlitchPlayerandplayer/styles.css. - Fetch the current presentation from GnommoEditor’s backend.
- Build a host-owned slide registry matching the templates GnommoEditor supports.
- Pass vote commits into a GnommoEditor API endpoint.
- Optionally use
onEventto power preview analytics or editor debugging.
That gives GnommoEditor responsibility for:
- data
- registry
- persistence
- product shell
And leaves GlitchPlayer responsible for:
- playback
- timeline
- responsive stage layout
- slide rendering
- drag browsing
- voting interaction UI
13. Suggested Next Steps Before Production Embedding
Before using this as a stable shared dependency, I recommend:
- Rename remaining internal
lecture*event/type fields topresentation*where possible. - Add a package export map for
src/player/index.ts. - Add a small integration example app or Storybook-like preview host.
- Add a few host-level visual regression tests for portrait and landscape framing.
- Decide whether the player package or the host app owns built-in default slide templates.
14. Current Reality Check
Right now, GlitchPlayer is reusable enough to embed in another React + Vite app, especially in a workspace-style setup.
It is not yet a polished published library, but it already has the right architectural boundary:
- host app provides presentation + registry + persistence
- GlitchPlayer provides runtime playback and interaction
That is the correct seam for moving this into GnommoEditor.