JavaScript video editor, encoder, switcher - version 5.1.1

Client Developer

The @moviemasher/client-react package builds upon the core @moviemasher/moviemasher.js package to provide a suite of ReactJS components capable of displaying a video editing user interface, as well as managing file imports and metadata extraction.

The codebase mostly follows the Compound Components React pattern, relying heavily on context to share state within a hierarchy of nested components. It deviates from the pattern by allowing arbitrary sub component naming rather than forcing dot notation. For instance, a sub component might be referenced as ComponentSub rather than Component.Sub. The drawback of this approach is that each component must be included individually, but ultimately this makes the bundler's job much easier.

Masher Component

The Masher top-tier component wraps the entire interface within a MasherContext that provides a pointer to the core Editor instance, theme elements, as well as state related to Drag and Drop operations. One or more second-tier components typically utilize this context to support particular editing functionality:

Player Browser Composer Inspector Timeline Activity

These components also create their own contexts, providing them to lower-tier components that facilitate the user interactions that actually constitute editing. Components can access all the contexts above them in the hierarchy. For instance, the PlayerContent component first checks the disabled property of the PlayerContext above it before adding an onDrop listener. When the user actually drops a file on the component the drop method of the MasherContext is called in order to import the file and ultimately display it.

ESM Client Example

The source code is available in ESM (ECMAScript Module) format for those wanting just a subset of functionality and an optimized build. Any of the popular bundling tools available should be able to efficiently remove (tree-shake) unused code from the build and effectively break up (code-split) the rest into easily downloadable chunks. Movie Masher has been tested extensively with rollup.js.

Installation

The following shell command installs the client and core libraries to your NPM project, saving the former to the dependencies array in your package.json file.

npm install @moviemasher/client-react --save

Inclusion

From our HTML file we pull in both the compiled JavaScript and CSS files. To support the widest variety of workflows and tooling, the Cascading Style Sheets required to layout the client user interface are kept separate from JavaScript code:

esm.html
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<script src='index.js' defer></script>
<link href='index.css' rel='stylesheet'>
<style>
body { margin: 0px; padding: 0px; font-family: sans-serif; }
body, #root { width: 100vw; height: 100vh; display: flex; }
.moviemasher .editor { --preview-width: 480px; --preview-height: 270px; }
</style>
<title>Movie Masher</title>
</head>
<body>
<div id='root' class='moviemasher'></div>
</body>
</html>

Since most of the interface elements scroll and stretch both horizontally and vertically, we are rendering into a node that is styled to fill the whole window. We also apply the moviemasher class to the node, so the additional styles in the CSS file are engaged.

We also use this opportunity to set the dimensions of the video preview in the editor through CSS variables - to their default values, in this case. There are a few ways to override these dimensions, but doing so in the CSS is best practice. Learn more about coloring and sizing the user interface using CSS in the Styling Overview.

masher.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { ApiClient, Masher, MasherDefaultProps } from "@moviemasher/client-react"

const element = document.getElementById('root')!
const options = { previewSize: { width: 480, height: 270 } }
const props = MasherDefaultProps(options)
const masher = <Masher {...props} />
const editor = <ApiClient>{masher}</ApiClient>
ReactDOM.createRoot(element).render(editor)

In this example we're using the MasherDefaultProps function to populate the Masher component with preconfigured children. Alternatively, child components like Player, Browser, Timeline, and Inspector can be selectively provided, and manually configured with a selection of available child controls.

We are also setting the preview dimensions here, to their defaults for demonstration purposes. As mentioned above, overriding the defaults from JavaScript is sub-optimal - a visible resizing will occur as the CSS variables are updated. But perhaps helpful if supplying custom CSS is impractical.

Server Interaction

The client package is optimized to work with the @moviemasher/server-express package, but this is not a requirement. Server interactions are encapsulated by the ApiClient component which is typically used as the root component wrapping the entire application.

When the Masher component is nested under an ApiClient component, it will make a DataDefaultRequest to retrieve a DataDefaultResponse to load into the editor. When it's not, the application is still capable of importing and arranging media though interface elements like the save and render buttons aren't displayed. This magical behavior arises because these elements are within the ApiEnabled component which utilizes an ApiContext to control visibility of its children. This context contains an enabled boolean that is false by default and only enabled by the ApiClient component. When false, ApiEnabled component simply does not display its children.

Other components use ApiContext to control visibility or augment behavior. In addition to looking at the enabled flag, components may inspect the context's servers object for a particular ServerType property that relates to their functionality. For instance, the Masher component will make a request for recent data once the ServerType.Data key is populated.

All requests made by components are channeled through the context as well, by calling its endpointPromise method. This returns a fetch-based promise for a specific endpoint with support for different request methods and formats. Some components may trigger a chain of promises, depending on current state.