Custom Clip

A Custom Clip is a class that can be extended with your own custom implementation and injected into the SDK. Its purpose is to enable developers to draw custom elements on the canvas. The CustomClip class provides access to a series of lifecycle methods that you can override to manage your clip's behavior.

When creating a Custom Clip, you'll likely interact with some of the underlying packages that our SDK utilizes, specifically:

  1. Pixi.js (opens in a new tab) - For working with the WebGL canvas.
  2. Zod (opens in a new tab) - For creating type-safe schemas for serialization and deserialization.

Lifecycle

A Custom Clip goes through the following lifecycle stages:

  1. constructor - Triggered before the init function. Recommended place to initialize the sprites and graphics elements.
  2. init - Triggered when the clip is added to the timeline. This is the ideal place to initialize resources, fetch data, or perform any necessary computations.
  3. update - Triggered during each redraw. Use this method to process data and render it onto the canvas.
  4. destroy - Triggered when the clip is removed from the timeline. This is the time to clean up resources that aren’t automatically garbage collected, ensuring efficient memory management.
  5. serialize - Triggered when the engine needs to serialize the clip's state into JSON.
  6. deserialize - Triggered when the engine is initialized from a JSON object, transforming it back into the clip's state.

Example

To get started, extend the CustomClip class with your own implementation as shown below:

import { ClipOptions, ClipSchema, CustomClip, Zod, Pixi } from "@rendley/sdk";
 
// Define custom parameters for your class.
// Example: a reference to a clip ID, a URL, etc.
interface WaveformClipOptions extends ClipOptions {
  customProp?: string;
}
 
// Define the schema for serializing/deserializing the clip's state.
const WaveformClipSchema = ClipSchema.extend({
  customProp: Zod.string().optional(),
});
 
// Extend the CustomClip class to create your custom clip.
class WaveformClip extends CustomClip {
  private customProp?: string;
 
  // Pixi.Graphics is a component with a Canvas2D-like API
  // that allows you to draw on the composition.
  private graphics: Pixi.Graphics;
 
  constructor(options: WaveformClipOptions) {
    super(options);
 
    this.customProp = options.customProp;
 
    // Use the main sprite as a container for the Graphics component.
    this.sprite = new Pixi.Sprite();
 
    // Initialize the main Graphics component for drawing.
    this.graphics = new Pixi.Graphics();
 
    // Add the Graphics component to the main sprite.
    this.sprite.addChild(this.graphics);
  }
 
  // The init function initializes the clip.
  // It is triggered when the clip is added to the timeline.
  override async init(layerId: string) {
    // Perform initialization here.
    // Be sure to call `super.init(layerId)` if you override this function.
    await super.init(layerId);
  }
 
  // The update function is called during each redraw.
  // This is where you update the clip's state or draw on the canvas.
  override update(currentTime: number) {
    // Be sure to call `super.update(currentTime)` if you override this function.
    super.update(currentTime);
  }
 
  // Clean up any resources that are not automatically garbage collected.
  override destroy() {
    super.destroy();
    this.graphics.destroy(true);
  }
 
  // The clone function creates a new instance of the clip.
  // Each class must override this method for the engine to identify the class.
  override clone() {
    return WaveformClip.deserialize(this.serialize());
  }
 
  // The serialize method converts the clip's state into an object.
  // This method is mandatory.
  override serialize() {
    // Use the schema defined above to parse the state.
    return WaveformClipSchema.parse({
      ...super.serialize(),
      customProp: this.customProp,
    });
  }
 
  // The deserialize method converts an object back into the clip's state.
  // This method is mandatory.
  static override deserialize(payload: object) {
    const data = WaveformClipSchema.parse(payload);
    const clip = new WaveformClip(data);
 
    return clip;
  }
}
 
export { WaveformClip };

Once you've created your custom class, you need to inject it into the engine so it can recognize and use it. A good place to do this is before initializing the engine:

import { Engine } from "@rendley/sdk";
 
Engine.getInstance().registerCustomClip(WaveformClip, "waveform");

The first parameter of registerCustomClip is your class, and the second is a custom name (like a tag) associated with your clip. This tag can be used later to identify the clip in the UI.

Once the custom clip is registered, you can add it to the timeline:

const layer = Engine.getInstance().getTimeline().createLayer();
 
await layer.addClip({
  type: "waveform",
  customProp: "Hello World",
});

For a comprehensive list of all supported properties and methods, be sure to check out the API Reference.