Jump to content

Movie Player

From OpenBOR
Revision as of 19:17, 12 August 2026 by Dcurrent (talk | contribs) (Created page with "OpenBOR provides a reusable source, playback, and channel system for movie media. The API uses the generic <code>movie_*</code> namespace so additional container or codec support can be added later. The current backend plays WebM files containing VP8 video and optional Vorbis audio. The movie system has three main parts: * '''Source''' - A streamed path or cached WebM byte buffer identified by a source ID. * '''Playback''' - One active use of a source, represented by a...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

OpenBOR provides a reusable source, playback, and channel system for movie media. The API uses the generic movie_* namespace so additional container or codec support can be added later. The current backend plays WebM files containing VP8 video and optional Vorbis audio.

The movie system has three main parts:

  • Source - A streamed path or cached WebM byte buffer identified by a source ID.
  • Playback - One active use of a source, represented by a stable playback object pointer.
  • Movie channel - One of 64 fixed slots that owns playback, timing, decoding, composition, and sound-routing state.

Keeping these concepts separate is important. One source may be played several times, each playback has independent state, and movie channels are separate from the sound channels used by embedded audio.

Formats

Component Current support Notes
Container WebM The supplied path is used exactly. Movie loading does not append an extension.
Video VP8 Video track required. Stereoscopic WebM modes are not supported.
Audio Optional Vorbis, mono or stereo Embedded audio is decoded to 16-bit PCM and routed through an ordinary OpenBOR sound channel. Sample rates from 11.025 through 48 kHz are recommended.
Composition 32-bit RGB Frames may fill the main screen or be copied into a creator-owned 32-bit subscreen.

Source video dimensions must be at least 2 by 2, even on both axes, and small enough for the 32-bit screen renderer to represent safely. Compatibility is checked when movie_play() opens the decoder. Successful movie_load() calls only prove that the source path could be opened or cached.

Movie functions remain visible on builds compiled without WebM support. Such builds report that movie playback is unavailable and return an empty value instead of opening media.

Streamed and cached sources

movie_load() accepts a loading-mode constant.

Mode Constant Load behavior Playback behavior Best use
Streamed MOVIE_LOADING_STREAM Copies the path and confirms that the file can be opened. Each playback opens an independent packfile position and decoder. Long movies, one-time scenes, and memory-sensitive projects.
Cached MOVIE_LOADING_CACHE Reads the complete compressed WebM file into memory once. Each playback reads the shared read-only buffer but still owns an independent decoder and frame queues. Short clips, repeated playback, and seek-heavy controls when sufficient RAM is available.

Cached mode is a compressed source cache, not a decoded frame cache. It removes repeated storage reads but does not remove VP8 or Vorbis decoding cost.

Source storage grows dynamically from an initial capacity of 64 records. Source IDs are assigned in ascending order and are not reused during the session. Loading the same path more than once creates another source ID; OpenBOR does not merge duplicate movie-load requests automatically.

One source can back several simultaneous playbacks. Cached playbacks share the source bytes, while every playback retains independent position, speed, repeat, screen, movie channel, and sound channel state.

Movie channels

OpenBOR exposes one bank of 64 movie channels numbered 0 through 63.

  • Automatic playback selects the lowest inactive movie channel.
  • Automatic playback fails when all 64 channels are active.
  • Explicit channel selection forces playback onto that slot and stops its previous occupant.
  • Stopped or completed playback immediately releases its source reference and marks the channel inactive.
  • The 64-bit active mask allows OpenBOR and scripts to skip inactive channels.
  • Channel objects remain structurally stable until movie shutdown, even while their playback state is replaced or reset.

Movie channels have no priority system. Explicit selection is a forced replacement. The public constant MOVIE_CHANNEL_COUNT reports the current capacity of 64.

Practical limits

The 64-channel capacity provides consistency and creative headroom, not a realistic expectation of 64 simultaneous decoders. Every active movie may own demux, video-decoder, audio-decoder, packet-queue, frame-queue, conversion, scaling, and sound-stream resources. Available CPU time, memory bandwidth, storage speed, resolution, frame rate, and target hardware establish the practical limit.

Most projects should expect one or two simultaneous movies to be a substantial workload. The fixed channel bank prevents an arbitrary API ceiling from becoming the first limitation.

Playback defaults

Every successful movie_play() begins with the following state:

Setting Default Behavior
Movie channel Automatic Uses the lowest inactive movie channel unless a channel is supplied.
Screen NULL() Renders over the main screen at the presentation stage.
Width and height 0, 0 Each zero dimension adopts the corresponding destination-screen dimension.
X and Y offset 0, 0 Places the prepared frame at the destination origin.
Speed 1.0 Normal forward playback.
Paused 0 Playback clock and decoding advance normally.
Repeat 0 Stops when the movie reaches its terminal frame.
Interrupt 1 Normal movie-skip input stops playback.
Black filter 0 Exact RGB black remains unchanged.
Sound channel 0 Embedded Vorbis audio uses conventional music and media space.

Script playback

movie_load()

Loads a reusable movie source and returns its integer source ID.

   int source_id = movie_load(
       string path,
       int loading
   );
Argument Required Default Description
path Yes None Exact module path to a WebM source.
loading No MOVIE_LOADING_STREAM MOVIE_LOADING_STREAM or MOVIE_LOADING_CACHE.

Invalid paths, loading modes, allocation failures, or empty cached files fail with a script error.

movie_unload()

Releases a loaded source.

   movie_unload(int source_id);

The source must exist and have no active playback references. Stop every playback using the source before unloading it. Unloaded IDs remain invalid and are not reassigned later in the session.

movie_play()

Starts a source and returns its stable movie playback object.

   void playback = movie_play(
       int source_id,
       int movie_channel
   );
Argument Required Default Description
source_id Yes None Source returned by movie_load().
movie_channel No MOVIE_CHANNEL_AUTO Omit the argument or pass NULL() for automatic allocation. Values from 0 through 63 force that channel.

Forced selection stops the current channel occupant before opening the new playback. Automatic selection never replaces an active movie and fails when no channel is free.

The returned pointer identifies the channel object, not a permanent unique playback instance. Later forced playback on the same channel reuses the same pointer with new state.

movie_stop()

Stops playback, closes its decoder, releases its source reference, and recycles its movie channel.

   movie_stop(void playback);

Calling movie_stop() with a valid but already inactive channel object safely leaves it inactive.

Basic example

This example streams a movie asynchronously to the main screen. Normal engine updates and scripts continue while it plays.

   int source_id = movie_load(
       "data/scenes/intro.webm",
       openborconstant("MOVIE_LOADING_STREAM")
   );
   void playback = movie_play(source_id);

The source can be unloaded after playback stops:

   int active = movie_get_property(
       playback,
       openborconstant("MOVIE_PROPERTY_ACTIVE")
   );
   if(!active)
   {
       movie_unload(source_id);
   }

Screen composition

Main-screen fallback

Playback with no assigned screen renders to the main 32-bit screen. Default width and height are zero, so the movie fills the destination. This preserves the visual behavior expected from traditional fullscreen movie playback while allowing the new API to remain asynchronous.

Unbound movies are composed after the ordinary sprite queue at the main-screen presentation stage. When several unbound movies are active, OpenBOR renders them in ascending movie-channel order. Higher channels therefore overwrite lower channels where their destination rectangles overlap.

Use a subscreen when ordinary script drawing or sprite composition must appear over the movie.

movie_set_screen()

Assigns a creator-owned 32-bit subscreen to a playback.

   movie_set_screen(
       void playback,
       void screen
   );

The creator must allocate the screen first. The movie system does not create, resize, draw, or free creator subscreens.

  • allocscreen(width, height) creates a compatible 32-bit screen.
  • Invalid, freed, non-screen, or non-32-bit pointers are rejected.
  • Passing NULL() detaches the subscreen and restores main-screen rendering.
  • Freeing an assigned screen automatically stops every movie playback still targeting it. This prevents a decoder from retaining a dangling screen pointer.
  • Assigning a screen does not automatically place that screen on the main display. Use drawscreen() or another normal composition path.

The screen property is read-only through movie_set_property(). Use movie_set_screen() whenever the target changes.

Drawing order

Bound movie frames are copied into their subscreens near the beginning of each engine update, before creator update and key scripts execute. This gives a bound movie the same downstream composition behavior as other subscreen content:

  1. OpenBOR advances the decoder and writes the newest movie frame into the assigned subscreen.
  2. Creator scripts may call drawstringtoscreen(), drawspritetoscreen(), drawboxtoscreen(), or other screen functions afterward.
  3. The script submits the completed screen with drawscreen().
  4. Existing drawmethod features may rotate, scale, flip, blend, or otherwise compose that screen.

Anything drawn to the same subscreen after the movie stage appears over the movie. Clearing the subscreen afterward also erases the copied movie frame, so avoid clearscreen() over the movie region unless that is intentional.

When several movie channels target the same subscreen, lower channel numbers render first and higher channel numbers render afterward. Creator drawing still occurs after all bound movies.

Width, height, offsets, and clipping

MOVIE_PROPERTY_WIDTH and MOVIE_PROPERTY_HEIGHT independently select the prepared frame dimensions.

  • Zero width uses the destination screen width.
  • Zero height uses the destination screen height.
  • Positive values scale the decoded frame to the requested dimension.
  • Width and height may be set independently, so scripts are responsible for preserving aspect ratio when desired.
  • Negative width or height is rejected.

MOVIE_PROPERTY_OFFSET_X and MOVIE_PROPERTY_OFFSET_Y place the scaled frame inside the destination. Offsets are signed. Pixels outside the destination are clipped safely.

Scaling a movie larger than its destination and applying negative offsets provides a controllable zoom-and-crop effect. Scaling it smaller provides picture-in-picture placement. The destination's normal drawscreen() drawmethod remains available for later whole-screen rotation or scaling.

Zoom-and-crop example

This example prepares a 320 by 180 movie image and clips its central 160 by 90 region into a subscreen.

   void screen = allocscreen(160, 90);
   int source_id = movie_load("data/scenes/camera.webm");
   void playback = movie_play(source_id);
   movie_set_screen(playback, screen);
   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_WIDTH"),
       320
   );
   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_HEIGHT"),
       180
   );
   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_OFFSET_X"),
       -80
   );
   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_OFFSET_Y"),
       -45
   );

Subscreen overlay example

Run the overlay and drawscreen() calls from a normal update script after the engine has copied the current movie frame.

   drawstringtoscreen(screen, 4, 4, 0, "LIVE");
   drawscreen(screen, 80, 30, 5000);

The text becomes part of the subscreen after the movie frame and therefore appears over it. Any configured drawmethod applied by drawscreen() transforms the completed movie-and-overlay composition together.

Exact-black protection

Some subscreen composition paths treat RGB 0, 0, 0 as a transparency mask. Enable MOVIE_PROPERTY_BLACK_FILTER to protect exact-black movie pixels:

   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_BLACK_FILTER"),
       1
   );

When enabled, OpenBOR changes only exact RGB 0, 0, 0 movie pixels to RGB 1, 1, 1 after YUV conversion. Other near-black shades remain unchanged. The filter affects the prepared movie frame before scaling and composition.

The filter is disabled by default because ordinary main-screen playback does not need transparency-mask protection.

Playback control

Movie control is provided through the property API. Timing properties use milliseconds.

Position and seeking

MOVIE_PROPERTY_POSITION reports the current playback position as an unsigned 64-bit millisecond value. Writing it seeks by reopening the decoder at the requested position.

MOVIE_PROPERTY_DURATION reports the source duration in milliseconds and is read-only. Seeking at or beyond a known duration clamps the request to the final available millisecond.

   int position_id = openborconstant("MOVIE_PROPERTY_POSITION");
   movie_set_property(playback, position_id, 30000); // 30 seconds

Seeking preserves the last prepared frame until a replacement frame is ready. Streamed seek latency depends on storage performance and WebM keyframe placement. Cached mode can reduce storage latency but still performs decoder work.

Pause

MOVIE_PROPERTY_PAUSED accepts zero or nonzero values. Pausing freezes the movie clock, preserves the current frame, and pauses embedded audio. Resuming continues from the stored position.

Speed, fast-forward, and reverse

MOVIE_PROPERTY_SPEED accepts a decimal from -16.0 through 16.0.

Speed Video behavior Embedded-audio behavior
1.0 Normal forward playback. Normal speed and pitch.
Greater than 1.0 Fast-forward. Speed and pitch increase with the positive rate.
Between 0.0 and 1.0 Slow forward playback. Speed and pitch decrease with the positive rate.
0.0 Holds the current position and frame. Paused.
Less than 0.0 Reverse playback. Paused. OpenBOR does not reverse embedded audio.

Reverse playback periodically seeks backward and decodes forward to recover the requested frame. It is therefore more expensive than ordinary forward playback and is sensitive to storage speed and keyframe spacing.

   int speed_id = openborconstant("MOVIE_PROPERTY_SPEED");
   movie_set_property(playback, speed_id, 2.0);  // Fast-forward
   movie_set_property(playback, speed_id, -1.0); // Reverse
   movie_set_property(playback, speed_id, 1.0);  // Normal

Repeat

MOVIE_PROPERTY_REPEAT accepts zero or nonzero values. Forward playback reopens at the beginning after its terminal frame. Reverse playback wraps from position zero to the final available millisecond when duration is known.

Movie playback has no separate repeat offset. Scripts can implement custom loop regions by observing position and writing a new position.

Input interruption

MOVIE_PROPERTY_INTERRUPT defaults to 1. New Escape or normal movie-skip button presses stop playback during the engine update.

Set the property to 0 when playback must continue until natural completion or an explicit movie_stop() call:

   movie_set_property(
       playback,
       openborconstant("MOVIE_PROPERTY_INTERRUPT"),
       0
   );

Embedded audio

Movie channels and sound channels are independent address spaces.

  • Movie channels range from 0 through 63 and own video playback objects.
  • Sound channels range from 0 through 4095 and mix audio.
  • Every new movie playback initially routes embedded audio to sound channel 0.
  • Modern movie_play() playback replaces only its selected sound channel. It does not stop unrelated sound channels.
  • WebM sources without an audio track leave existing sound playback unchanged.
  • Failure to open an otherwise optional audio track leaves the video playing silently.

Channel 0 is OpenBOR's soft-reserved music and media channel. Starting a movie with embedded audio therefore replaces the current occupant of sound channel 0 unless the previous movie has already been routed elsewhere.

movie_set_sound_channel()

Moves a playback's embedded audio to an explicit sound channel.

   movie_set_sound_channel(
       void playback,
       int sound_channel
   );

Valid sound channels are 0 through 4095. Changing the sound channel reopens the decoder at the current movie position so video and audio remain synchronized. The selected sound channel is forced: an existing occupant is replaced without a priority check.

The sound-channel property is read-only through movie_set_property(). Use movie_set_sound_channel() to change routing.

Route each movie to its intended sound channel immediately after starting it and before starting the next audible movie:

   int source_a = movie_load("data/scenes/feed_a.webm");
   int source_b = movie_load("data/scenes/feed_b.webm");
   void movie_a = movie_play(source_a, 4);
   movie_set_sound_channel(movie_a, 10);
   void movie_b = movie_play(source_b, 5);
   movie_set_sound_channel(movie_b, 11);

The embedded streams are ordinary producer-fed sound objects. Scripts may inspect their volume, pause state, play ID, and other applicable common properties through the sound object API. Sample-backed seeking properties do not apply to producer-fed movie audio; seek through MOVIE_PROPERTY_POSITION instead.

Movie object script API

Advanced scripts may inspect the fixed movie channel bank, obtain stable channel objects, and read or change playback properties.

Use named constants from openborconstant(). Raw numeric property and mode IDs are implementation details.

movie_get_channel_mask()

   mixed active_mask = movie_get_channel_mask();

Returns an unsigned 64-bit mask. Bit 0 represents movie channel 0, bit 1 represents channel 1, and so on. Set bits indicate active playback.

movie_get_channel_object()

   void playback = movie_get_channel_object(int movie_channel);

Returns the stable movie object for a channel from 0 through 63. The fixed bank exists after movie-system initialization, so inactive channels still return objects. Test MOVIE_PROPERTY_ACTIVE or the active mask before treating the object as current playback.

movie_get_channel_index()

   int movie_channel = movie_get_channel_index(void playback);

Validates a movie object pointer and returns its channel index.

Property access

   mixed value = movie_get_property(
       void playback,
       int property
   );
   movie_set_property(
       void playback,
       int property,
       mixed value
   );

Property reads use a synchronized public-state snapshot. Playback-sensitive writes route through lifecycle-aware helpers so clocks, decoders, frame scaling, audio, and screen composition remain coherent.

Property constant Type Access Default Description
MOVIE_PROPERTY_ACTIVE Integer Read 0 before play 1 while the channel owns active playback; otherwise 0.
MOVIE_PROPERTY_BLACK_FILTER Integer Read/write 0 Converts exact RGB black movie pixels to RGB 1, 1, 1.
MOVIE_PROPERTY_CHANNEL Integer Read Channel index Stable movie channel from 0 through 63.
MOVIE_PROPERTY_DURATION Unsigned 64-bit integer Read Source value Duration in milliseconds.
MOVIE_PROPERTY_HEIGHT Integer Read/write 0 Prepared frame height. Zero uses the destination height.
MOVIE_PROPERTY_INTERRUPT Integer Read/write 1 Nonzero allows normal movie-skip input to stop playback.
MOVIE_PROPERTY_OFFSET_X Integer Read/write 0 Signed horizontal destination offset. Overflow is clipped.
MOVIE_PROPERTY_OFFSET_Y Integer Read/write 0 Signed vertical destination offset. Overflow is clipped.
MOVIE_PROPERTY_PAUSED Integer Read/write 0 Pause state. Writes require active playback.
MOVIE_PROPERTY_POSITION Unsigned 64-bit integer Read/write 0 Current millisecond position. Writing seeks active playback.
MOVIE_PROPERTY_REPEAT Integer Read/write 0 Nonzero repeats at the source boundary.
MOVIE_PROPERTY_SCREEN Pointer Read NULL() Assigned 32-bit subscreen. Change with movie_set_screen().
MOVIE_PROPERTY_SOUND_CHANNEL Integer Read 0 Generic sound channel used by embedded audio. Change with movie_set_sound_channel().
MOVIE_PROPERTY_SOURCE Integer Read -1 while inactive Source ID owned by the current playback.
MOVIE_PROPERTY_SPEED Decimal Read/write 1.0 Signed playback speed from -16.0 through 16.0.
MOVIE_PROPERTY_WIDTH Integer Read/write 0 Prepared frame width. Zero uses the destination width.

Property example

This example pauses an active playback, seeks to 12.5 seconds, disables skip input, and resumes at half speed.

   if(movie_get_property(
       playback,
       openborconstant("MOVIE_PROPERTY_ACTIVE")
   ))
   {
       movie_set_property(
           playback,
           openborconstant("MOVIE_PROPERTY_PAUSED"),
           1
       );
       movie_set_property(
           playback,
           openborconstant("MOVIE_PROPERTY_POSITION"),
           12500
       );
       movie_set_property(
           playback,
           openborconstant("MOVIE_PROPERTY_INTERRUPT"),
           0
       );
       movie_set_property(
           playback,
           openborconstant("MOVIE_PROPERTY_SPEED"),
           0.5
       );
       movie_set_property(
           playback,
           openborconstant("MOVIE_PROPERTY_PAUSED"),
           0
       );
   }

Legacy playback

Traditional WebM entry points remain available as wrappers around the movie system. Their defaults intentionally reproduce historical blocking fullscreen behavior.

playwebm()

   int result = playwebm(
       string path,
       int noskip
   );
Argument Required Default Description
path Yes None Exact WebM source path.
noskip No 0 Zero permits normal skip input. Nonzero forces playback to continue until completion or error.

Return values:

Result Meaning
1 Playback completed normally.
0 Loading, decoding, rendering, or cleanup failed.
-1 Creator-enabled input interrupted playback.

The wrapper performs the following operations:

  • Stops every active movie playback.
  • Loads the requested source in streamed mode.
  • Uses automatic movie-channel selection.
  • Leaves the screen unbound, with zero width and height, so the movie fills the main screen.
  • Uses normal speed, no repeat, no black filter, and sound channel 0.
  • Blocks the calling scene or script until completion, interruption, or error.
  • Clears and presents the main screen directly during playback.
  • Unloads its temporary source before returning.
  • Preserves the historical replace-all sound behavior when the WebM contains audio.

Legacy WebM files with embedded audio stop current music and active sample playback before opening a producer-fed stream on sound channel 0. Legacy WebM files without audio leave existing channel 0 playback running.

Scene video command

Scene files retain their existing command:

   video {path} {skipone} {noskip}

noskip uses the same interruption rule as playwebm(). When interruption is enabled, skipone controls whether an interrupted video allows the scene parser to continue instead of closing the remaining scene.

Projects requiring asynchronous playback, composition, seeking, speed control, repeat, multiple channels, caching, or separate sound routing should use the movie_* API.

Practical guidance

  • Stream long or one-time movies that would waste RAM as complete cached files.
  • Cache short, frequently reused, or seek-heavy clips when storage latency matters and sufficient memory is available.
  • Remember that cached sources still require full VP8 and Vorbis decoding for every playback.
  • Bind a creator-owned subscreen whenever sprites, text, effects, or other screen drawing must appear over the movie.
  • Leave the screen unset only when main-screen fallback and late fullscreen composition are desired.
  • Treat zero width and height as destination-fill behavior. Set explicit dimensions when preserving aspect ratio or building picture-in-picture layouts.
  • Use signed offsets with oversized dimensions for controlled zoom and clipping.
  • Enable the black filter only when exact black must survive a subscreen transparency mask.
  • Route simultaneous movies to distinct sound channels immediately after each movie_play() call.
  • Expect reverse playback and frequent streamed seeks to cost substantially more than normal forward playback.
  • Stop every playback using a source before calling movie_unload().
  • Treat movie pointers as stable channel slots rather than permanent unique playback instances.
  • Use the 64-channel mask as a fast state summary, not as a target workload.
  • Prefer the modern movie API for new projects. Reserve playwebm() and the scene video command for compatibility or intentionally blocking presentation.