fastforward.library Application Developer Guide
Movie API, Graph API, BOOPSI gadgets, and application integration
This guide walks application developers through the practical use of fastforward.library V1. It accompanies the function reference (SDK/Doc/fastforward.doc), the Plugin Developer’s Guide, and the architectural design notes (Docs/ARCHITECTURE.md).
If you are new to the engine, the high-level conceptual model is:
A Movie is a high-level handle to one media stream (a file, a
URL, a memory buffer, an application-supplied byte stream). Movies are the player-app surface — you Open one, Play it, Wait for events on its signal mask, and Close it. Created with OpenFFMovieA() and disposed with CloseFFMovie().
A Track is one substream inside a movie (audio track 0, video
track 0, subtitle stream, MIDI track, ...). Tracks are obtained via GetFFTrack(); per-track mute, volume, language and custom sinks are set via SetFFTrackAttrsA().
A Graph is the low-level filter-graph surface for editors,
transcoders and sequencers. A graph is a set of Elements linked by their typed Pads. Created with CreateFFGraphA() and disposed with DeleteFFGraph().
An Element is one stage in the graph - a source, a demuxer, a
codec, a filter, a mixer, a sink. Elements are instances of registered Plugins.
A Pad is a typed endpoint on an Element: src for outputs,
sink for inputs; demuxers add per-track pads named audio, video, subtitle, data. LinkFFPads() wires them.
A Clock is the timing reference shared by every Element in a
Graph. Backends: FFCK_TIMER (timer.device, default), FFCK_REALTIME (realtime.library Conductor for sequencers / NLE), FFCK_AUDIO (slave to the audio sink's sample clock) and FFCK_EXTERNAL (driven by the application).
A Plugin is a LoadSeg module that implements one pipeline stage (source, demux, codec, filter, sink). Applications select plugins indirectly through sniffing; plugin authors see the Plugin Developer’s Guide.
The library coexists with datatypes.library and mediatypes.library. Still images (PNG, ILBM, JPEG, GIF, etc.) belong on datatypes / mediatypes; FastForward is the engine for timed media (audio, video, subtitles, MIDI) and the streaming / async / pipeline plumbing those need.
Table of contents
1. Getting started
2. Movie API: a player in one screen
3. Tape-deck transport
4. Track introspection and per-track control
5. Graph API: editors, sequencers, transcoders
6. Custom sinks: rendering inside your app
7. Render targets and video output
8. Clock and synchronisation
9. Events
10. Metadata
11. Media probe (file managers, browsers)
12. Thumbnails and still images
13. Errors and cancellation
14. Per-element tasks and Conductor synchronisation
15. player.gadget and playback.gadget
16. mediatypes.library integration
17. Reference: enums, error codes, transport rates
Appendices:
A. Quick recipes
B. Integration cheatsheet
1. Getting started
1.1 Opening the library
#include <libraries/fastforward.h>
#include <proto/fastforward.h>
struct Library *FastForwardBase = NULL;
FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
if (FastForwardBase == NULL) {
/* fall back to datatypes.library, or fail */
}
LoadFFPlugins(NULL); /* uses the resolved default plugin directory */
...
UnloadFFPlugins();
FF_CloseLibrary(FastForwardBase); |
FF_OpenLibrary() is a convenience macro defined in <libraries/fastforward.h> that expands to OpenLibrary("fastforward.library", v). The library base is implicit at every LVO trap (same model as exec.library, intuition.library, datatypes.library); the #pragma libcall lines in <pragmas/fastforward_pragmas.h> load the global FastForwardBase into A6 before each call.
A version that does not satisfy FF_API_VERSION means an older library or none installed.
1.2 Build / link
Headers
Source/include/libraries/fastforward.h - types, tags, opaque
handles
Source/include/libraries/ffplugin.h - plugin ABI, buffer
types, events
Source/include/fastforward/ff_c2p.h - chunky-to-planar helper
Source/include/fastforward/ff_string.h - inline counted-buffer
helpers
Prototypes / pragmas
Source/include/clib/fastforward_protos.h
Source/include/pragmas/fastforward_pragmas.h
Source/include/proto/fastforward.h (pulls in both)
No amiga.lib varargs glue is required - the public API uses
*A-suffix tag-list entry points, and SAS/C 6.x gets the matching #pragma tagcall varargs forms; gcc / vbcc users get compound-literal macros in <clib/fastforward_protos.h>.
1.3 Library layout on disk
LIBS:fastforward.library
LIBS:FastForward/Plugins/file.source
LIBS:FastForward/Plugins/pcm.demux
LIBS:FastForward/Plugins/ahi.sink
LIBS:FastForward/Plugins/...
ENV:FastForward/Plugins (override default plugin dir)
FastForward: (logical assign; optional) |
LoadFFPlugins(NULL) resolves the default plugin directory once at library-open time using:
1. ENV variable FastForward/Plugins
2. logical assign FastForward: (plugins live in
FastForward:Plugins/)
3. fallback LIBS:FastForward/Plugins
No path is compiled into the library. Packagers and end users move plugins wherever they like by editing the assign or the ENV variable; applications never need to know.
1.4 House style
FastForward is written exclusively against the Amiga ROM / NDK API surface - no libc, no POSIX types:
<string.h> is replaced by inline helpers in
<fastforward/ff_string.h> that wrap CopyMem plus utility.library Stricmp for case-insensitive matches.
struct TimeVal and struct TimeRequest from <devices/timer.h>
(with __USE_NEW_TIMEVAL__) - never POSIX timeval / timerequest.
Tag lists are walked through utility.library (GetTagData,
FindTagItem, NextTagItem, FilterTagItems).
Signals not callbacks. Apps Wait() on the movie's signal mask
exactly the way they Wait() on an Intuition IDCMP port.
2. Movie API: a player in one screen
The Movie API is the recommended surface for player applications. It hides demuxer / codec selection, sink plumbing, and pipeline construction behind a single tag-list-driven open call.
struct FFMovie *m;
ULONG mask, signals;
struct FFEvent ev;
struct TagItem mtags[] = {
{ FFMT_Path, (ULONG)"work:song.wav" },
{ FFMT_AutoPlay, TRUE },
{ FFMT_Volume, 0x10000 },
{ TAG_DONE, 0 }
};
m = OpenFFMovieA(mtags);
if (m == NULL) { /* GetFFLastError() */ exit(20); }
mask = GetFFMovieSignalMask(m);
while (GetFFMovieState(m) == FFMS_PLAYING ||
GetFFMovieState(m) == FFMS_PREROLL) {
signals = Wait(mask | SIGBREAKF_CTRL_C);
if (signals & SIGBREAKF_CTRL_C) {
StopFFMovie(m);
break;
}
while (PollFFMovieEvent(m, &ev) > 0) {
if (ev.fe_Kind == FFEV_EOS) goto done;
}
}
done:
CloseFFMovie(m); |
2.1 Source selection
OpenFFMovieA() accepts one source tag from each of these mutually exclusive groups:
| Tag(s) |
Source |
| FFMT_Path |
DOS file path |
| FFMT_FileHandle |
Already-open BPTR; engine takes ownership |
| FFMT_URL |
Network location (http/rtsp/hls) — needs network source plugin |
| FFMT_MemData + FFMT_MemSize |
In-memory buffer via built-in mem.source |
| FFMT_IOCallbacks + FFMT_IOUserData |
App-supplied read/seek/close (browser cache, RTSP shim) |
For network sources FFMT_Timeout, FFMT_UserAgent, FFMT_ProxyServer and FFMT_ProxyPort are forwarded to the underlying source plugin.
2.2 Synchronous vs asynchronous open
OpenFFMovieA() blocks by default - it returns once the source has been opened, format identified and tracks enumerated. For network or slow sources, set FFMT_AsyncOpen = TRUE and FFMT_OpenSignal = sigbit to receive a notification when the open completes; the call returns immediately with the movie in FFMS_OPENED (or FFMS_ERROR).
CancelFFMovie() aborts a hung or in-flight open and posts an FFEV_ERROR event with FFERR_CANCELLED.
2.3 Disposal
CloseFFMovie() tears down the playback Process, flushes all pipeline buffers, closes any sinks the engine created, and frees the movie record itself. Application-supplied sinks (FFMT_AudioSink / FFMT_VideoSink) are detached but not disposed - the caller retains ownership. EjectFFMovie() is a macro alias.
API supplement (from API.md)
FastForward exposes two layers on top of fastforward.library. Both share the same plugin registry, clock subsystem, host services and event queue.
| Layer |
When to use |
| Movie API |
Player apps. Opens a file, demuxes, decodes, builds a default audio/video graph, optionally autoplays, exposes tracks, signals progress over an exec signal mask. |
| Graph API |
Editors, transcoders, music sequencers, live mixers, anything that needs to wire arbitrary FFElements by their typed FFPads, drive a master FFClock, or replace pieces of a default movie graph. |
1. Naming and call conventions
The public API follows AmigaOS conventions, not POSIX/C-library style:
Verb-then-object: OpenFFMovieA, CloseFFMovie, PlayFFMovie, CreateFFGraphA, LinkFFPads. The library tag is baked into the object name (FFMovie, FFGraph, FFElement, FFPad, FFClock) so every public symbol is unambiguous without a blanket FF_ prefix on the verb.
Implicit library base. The base is not an explicit parameter. <proto/fastforward.h> provides #pragma libcall directives that load the global FastForwardBase into A6 before each LVO trap, exactly like exec.library, intuition.library or datatypes.library calls.
A suffix on tag-list entries. OpenFFMovieA(struct TagItem *), GetFFMovieAttrsA(...), CreateFFGraphA(...), etc. SAS/C 6.x also gets the matching varargs convenience form (#pragma tagcall) so OpenFFMovie(FFMT_Path, "...", FFMT_AutoPlay, TRUE, TAG_DONE) Just Works. gcc / vbcc users have equivalent compound-literal macros in <libraries/fastforward.h>.
Internals are explicit. Engine helpers (FFMovieOpen, FFGraphCreate, FFClockCreate, ...) take an explicit struct FastForwardBase *base argument because they are called both from the public LVO wrappers and from each other; they live in <fastforward/ff_internal.h> and are not part of the public ABI.
| Public (LVO) |
Internal helper |
| OpenFFMovieA |
FFMovieOpen(base, tags) |
| CloseFFMovie |
FFMovieClose(base, m) |
| CreateFFGraphA |
FFGraphCreate(base, tags) |
| LinkFFPads |
FFPadLink(base, src, dst) |
| CreateFFClockA |
FFClockCreate(base, tags) |
11. Extended Movie open tags
Beyond FFMT_Path, OpenFFMovieA() accepts:
| Tag |
Purpose |
| FFMT_URL |
Network location (http/rtsp/hls). Until http.source lands, treated as path string for sniffing. |
| FFMT_MemData / FFMT_MemSize |
Play from memory via built-in mem.source. |
| FFMT_IOCallbacks / FFMT_IOUserData |
App-supplied read/seek/close (browser cache, RTSP shim). |
| FFMT_Timeout / FFMT_UserAgent / FFMT_ProxyServer |
Forwarded to future network source plugins. |
| FFMT_AsyncOpen / FFMT_OpenSignal |
Non-blocking open notification (reserved). |
Read-only attrs via GetFFMovieAttrsA(): FFMT_HasAudio, FFMT_HasVideo, FFMT_HasSubtitle, FFMT_Seekable, FFMT_Width, FFMT_Height, FFMT_SampleRate, FFMT_Channels, FFMT_URL.
13. Pad and element introspection
Graph apps inspect negotiated formats before rewiring:
ULONG caps, linked;
struct FFAudioFormat *af;
GetFFPadAttrsA(pad,
FFPA_Caps, (ULONG)&caps,
FFPA_Linked, (ULONG)&linked,
FFPA_AudioFormat, (ULONG)&af,
TAG_DONE);
GetFFElementAttrsA(element,
FFE_Kind, (ULONG)&kind,
FFE_PadCount, (ULONG)&padcount,
FFE_Caps, (ULONG)&caps,
TAG_DONE); |
3. Tape-deck transport
FastForward is named for the tape metaphor and the API leans into it: a movie is a tape, the engine is the deck, the application drives it with the same controls a real-world deck has.
PlayFFMovie(m); /* the Play button */
PauseFFMovie(m); /* the Pause button */
ResumeFFMovie(m); /* alias of Play */
StopFFMovie(m); /* the Stop button */
RewindFFMovie(m); /* Stop + seek to head ("rewind to 0") */
EjectFFMovie(m); /* alias of CloseFFMovie */
SeekFFMovie(m, time, 0); /* scrub the timeline */
FastForwardFFMovie(m); /* play forward at 2x */
ReverseFFMovie(m); /* play backward at 1x */
ScanFFMovie(m, FF_RATE_FF_4X); /* arbitrary signed Q16.16 rate */
ScanFFMovie(m, FF_RATE_REWIND_8X);
ScanFFMovie(m, FF_RATE_SLOW_HALF); |
FFMT_PlayRate is the underlying tag — signed Q16.16 ticks per clock tick. 0x10000 is normal forward, -0x10000 is normal reverse, 0 is paused. The convenience macros expand to SetFFMovieAttrsA() calls so any plugin that honours FFMT_PlayRate participates automatically; codecs that only do 1x silently stay at 1x and the engine posts FFEV_QOS so the application can grey out the affordances that aren't supported.
Loop modes are exposed through FFMT_LoopMode:
| Value |
Behaviour |
| FFLM_OFF |
Play once, stop at end. Default. |
| FFLM_LOOP |
Return to start at end. |
| FFLM_PINGPONG |
Alternate forward / reverse at boundaries. |
| FFLM_REGION |
Loop between FFMT_LoopStart and FFMT_LoopEnd. |
Region mode is what a music sequencer drops onto a clip: set FFMT_LoopStart and FFMT_LoopEnd (in clock units), set FFMT_LoopMode = FFLM_REGION, and the engine seeks back to start whenever playback crosses end.
4. Track introspection and per-track control
Track ordering and metadata follow QuickTime's Movie/Track model: the demuxer enumerates independent substreams (audio, video, subtitle, MIDI, data) and exposes them as FFTrack handles.
ULONG count = GetFFTrackCount(m);
ULONG i;
for (i = 0; i < count; i++) {
struct FFTrack *t = GetFFTrack(m, i);
ULONG kind = 0;
STRPTR desc = NULL, lang = NULL, codec = NULL;
struct TagItem qtags[] = {
{ FFTA_Kind, (ULONG)&kind },
{ FFTA_Description, (ULONG)&desc },
{ FFTA_Language, (ULONG)&lang },
{ FFTA_CodecName, (ULONG)&codec },
{ TAG_DONE, 0 }
};
GetFFTrackAttrsA(t, qtags);
/* kind == FFTK_AUDIO / FFTK_VIDEO / FFTK_DATA / FFTK_MIDI / FFTK_SUBTITLE */
} |
Per-track mute / solo / volume / custom sink:
struct TagItem stags[] = {
{ FFTA_Mute, TRUE },
{ FFTA_Volume, 0x8000 }, /* 50% */
{ FFTA_Sink, (ULONG)mySink }, /* FFElement * */
{ TAG_DONE, 0 }
};
SetFFTrackAttrsA(t, stags); |
A "solo" UI button is implemented by muting every track except the selected one.
5. Graph API: editors, sequencers, transcoders
The Graph API is the low-level surface for code that wants to wire arbitrary elements together: non-linear editors, transcoders, music sequencers, live mixers, anything that needs more than the default linear source -> demux -> codec -> sink chain.
5.1 Building a graph by hand
struct FFGraph *g = CreateFFGraphA(NULL);
/* Add elements by name, identifier, kind, or explicit FFPlugin pointer. */
struct TagItem srcTags[] = {
{ FFE_Name, (ULONG)"FastForward file source" },
{ TAG_DONE, 0 }
};
struct FFElement *src = NewFFElementA(g, srcTags);
struct TagItem demuxTags[] = {
{ FFE_Kind, FFK_DEMUX },
{ FFE_Mime, (ULONG)"audio/wav" },
{ TAG_DONE, 0 }
};
struct FFElement *demux = NewFFElementA(g, demuxTags);
/* Wire pads. Each element exposes "src" (output) and/or "sink" (input). */
LinkFFPads(GetFFElementPad(src, FFPAD_SRC),
GetFFElementPad(demux, FFPAD_SINK)); |
Configure individual elements with SetFFElementAttrsA() (file path, AHI unit, volume, encoder bitrate, ...) and start the graph with RunFFGraph(). StopFFGraph() is safe to call from a signal handler or another task.
5.2 Auto-build a default pipeline
BuildFFGraphFromFileA() is a shortcut: returns a populated graph (source -> demux -> codec -> sink) for a path. Useful when you want the auto-detected chain but plan to swap the sink for a callback or transcoder element.
struct FFGraph *g = BuildFFGraphFromFileA("work:foo.wav", NULL);
struct FFElement *defaultSink = /* find the FFK_SINK element */;
DisposeFFElement(defaultSink);
struct FFElement *myMux = NewFFElementA(g, /* FFK_MUX tags */);
LinkFFPads(/* codec src */, GetFFElementPad(myMux, FFPAD_SINK));
RunFFGraph(g); |
5.3 Graph layout for an editor or mixer
track A: file_source -> demux -> codec -+
+-> mixer -> ahi_sink
track B: file_source -> demux -> codec -+ |
Mixer is itself an FFElement with two sink pads and one src pad. Editors free elements with DisposeFFElement() and rewire pads with UnlinkFFPad() / LinkFFPads(). The graph's master clock keeps every element timestamp-synchronised.
5.4 Disposal
DeleteFFGraph() calls StopFFGraph() if needed, disposes every element it owns, and tears down all inter-element links.
6. Custom sinks: rendering inside your app
A non-linear editor wants every decoded video frame in a RastPort. A waveform widget wants every PCM block. A music sequencer wants samples available for offline mixing. Use the built-in callback sink.
LONG OnAudio(APTR user, APTR data, ULONG size,
struct FFAudioFormat *af, ULONG ts) {
/* hand 'data' (size bytes, format 'af', timestamp 'ts') to your widget */
return 0;
}
LONG OnVideo(APTR user, APTR data, ULONG size,
struct FFVideoFormat *vf, ULONG ts) {
/* WritePixelArray() into your window's RastPort */
return 0;
}
struct FFCallbacks cb;
cb.cb_OnFormat = NULL;
cb.cb_OnAudio = OnAudio;
cb.cb_OnVideo = OnVideo;
cb.cb_OnSubtitle = NULL;
cb.cb_OnEvent = NULL;
cb.cb_UserData = my_widget;
struct FFElement *sink = NewFFCallbackSinkA(g, &cb, NULL); |
Wire the callback sink as the audio or video sink of a Movie:
struct TagItem mtags[] = {
{ FFMT_Path, (ULONG)path },
{ FFMT_AudioSink, (ULONG)sink },
{ TAG_DONE, 0 }
};
movie = OpenFFMovieA(mtags); |
The callback runs on the player Process. Heavy work (Intuition, WritePixelArray into a deep mode, blocking DOS calls) should be deferred via a message port back to your main task.
7. Render targets and video output
Apps that want video drawn into one of their own Intuition surfaces hand it over via render-target hint tags on OpenFFMovieA() or SetFFMovieAttrsA(). FastForward routes the right pointer to whichever video sink ends up bound to the movie.
| Tag |
Type |
Use |
| FFMT_Window |
struct Window * |
Render into the window's RastPort and re-use its IDCMP for resize events. |
| FFMT_Screen |
struct Screen * |
Render full-screen on a custom screen. |
| FFMT_RastPort |
struct RastPort * |
Explicit RastPort target. |
| FFMT_BitMap |
struct BitMap * |
Pre-allocated planar BitMap. Hand-rolled C2P sinks bind to this directly. |
| FFMT_DestX/Y |
LONG |
Destination top-left in the RastPort's coordinate space. Defaults to the window's inner top-left when FFMT_Window is set. |
| FFMT_DestWidth/Height |
ULONG |
Maximum destination rectangle. Zero means use the frame size. |
The hints are advisory - if no video sink ends up plumbed for the stream (audio-only file, video-track disabled, ...) the hint is silently ignored.
For native AGA / OCS / ECS sinks the chunky-to-planar helper is documented in the Plugin Developer’s Guide.
8. Clock and synchronisation
Every Graph carries a master FFClock. Four backends:
| Kind |
Backend |
Use |
| FFCK_TIMER |
timer.device UNIT_MICROHZ (ReadEClock) |
Default. ~1us resolution. |
| FFCK_REALTIME |
realtime.library Conductor |
Sequencers / NLE timelines that share tempo with CAMD, OctaMED, Bars&Pipes. |
| FFCK_AUDIO |
Slaves to the audio sink's sample clock |
A/V sync. |
| FFCK_EXTERNAL |
App calls SetFFClockTime() itself |
Mixer authoring tools. |
Tags on CreateFFClockA():
| Tag |
Use |
| FFCT_Kind |
FFCK_* |
| FFCT_TimeScale |
Ticks per second (default FF_TIMESCALE_USEC). |
| FFCT_Conductor |
realtime.library Conductor for FFCK_REALTIME. |
| FFCT_ConductorName |
Look up an existing named Conductor (CAMD, OctaMED). |
| FFCT_TickRate |
Tick rate hint (PPQN). |
| FFCT_Tempo |
BPM * 1000. |
| FFCT_ExternalSync |
TRUE: this clock's Player becomes the external sync source. |
| FFCT_PlayerHandle |
(struct Player **), output: receives the realtime.library Player. |
8.1 Driving a sequencer manually
struct TagItem ctags[] = {
{ FFCT_Kind, FFCK_REALTIME },
{ FFCT_TickRate, 192 }, /* PPQN */
{ FFCT_Tempo, 120000 }, /* 120.000 BPM */
{ TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);
StartFFClock(clock);
while (running) {
ULONG now = GetFFClockTime(clock);
/* schedule next note at 'now + 1 tick' */
WaitFFClock(clock, now + 1, SIGBREAKF_CTRL_C);
} |
WaitFFClock() returns the bitmask of received signals, so Wait()-style multiplexing on Ctrl-C, IDCMP, MIDI input, et cetera works as usual.
8.2 External sync sources
realtime.library allows exactly one Player per Conductor to be the external sync source. That Player calls ExternalSync(player, minTime, maxTime) to constrain conductor time, and every other Player (including FastForward's) follows. On Amiga the natural sources are:
SMPTE timecode from a hardware genlock or video card (NLE deck control)
MIDI clock from external sequencer hardware via CAMD
AHI sample clock when audio is the timing master
LTC / wordclock from a pro audio interface card
Network sync (NTP, RTSP timestamps) for cross-machine playback
Two integration patterns are supported.
Pattern A — FastForward is the timing master. The application asks FastForward to become the external sync source. Our internal Player is created with PLAYER_ExtSync = TRUE, the Player handle is returned via FFCT_PlayerHandle, and the application calls ExternalSync() on it from its own time source:
struct Player *player = NULL;
struct TagItem ctags[] = {
{ FFCT_Kind, FFCK_REALTIME },
{ FFCT_ConductorName, (ULONG)"FastForward.Conductor" },
{ FFCT_ExternalSync, TRUE },
{ FFCT_PlayerHandle, (ULONG)&player }, /* out */
{ FFCT_TickRate, 192 }, /* PPQN */
{ TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);
/* In the audio interrupt or SMPTE reader: */
LONG hw_now = ReadHardwareTime();
ExternalSync(player, hw_now, hw_now); /* clamp conductor to hw clock */ |
Pattern B — A third party owns the Conductor. Bars&Pipes, OctaMED, CAMD's metronome, or a custom genlock driver has already created a named Conductor and is calling ExternalSync() on its own Player. FastForward attaches to that Conductor by name and slaves to it automatically:
struct TagItem ctags[] = {
{ FFCT_Kind, FFCK_REALTIME },
{ FFCT_ConductorName, (ULONG)"BarsAndPipes" }, /* the master */
{ TAG_DONE, 0 }
};
struct FFClock *clock = CreateFFClockA(ctags);
/* No FFCT_ExternalSync: we are a follower, not the master. */
/* Every FFElement that uses FFE_SyncToConductor will now wake up */
/* in lock-step with whatever drives BarsAndPipes' conductor. */ |
This is the same model every realtime.library client uses. CAMD's MIDI clock follows it, OctaMED's transport follows it, and now FastForward graphs do too.
9. Events
Plugins post FFEvent records (format change, seek complete, end-of-stream, decoded metadata tags, QoS feedback, structured errors) onto the owning Movie. The application drains them on its own task by waiting on the Movie's signal mask:
ULONG mask = GetFFMovieSignalMask(movie);
ULONG signals;
struct FFEvent ev;
signals = Wait(mask | mywindowmask | SIGBREAKF_CTRL_C);
while (PollFFMovieEvent(movie, &ev) > 0) {
switch (ev.fe_Kind) {
case FFEV_EOS: /* loop or open next */ break;
case FFEV_FORMAT: /* renegotiate; re-query track attrs */ break;
case FFEV_SEEK: /* seek completed */ break;
case FFEV_FLUSH: /* upstream flushed */ break;
case FFEV_TAGS: /* ev.fe_Tags: ID3 / Vorbis comment */ break;
case FFEV_QOS: /* dropped frames; widen ring buffers */ break;
case FFEV_PROGRESS:/* buffering / transcode progress */ break;
case FFEV_NETWORK: /* source went online / offline */ break;
case FFEV_ERROR: /* structured error report */ break;
}
} |
Plugins call host->fh_PostEvent(element, &event) to push events; the engine attributes them to the Movie that owns the graph.
10. Metadata
Movie metadata is queried with GetFFMovieAttrsA() using a request / response pair on FFMETA_Key plus FFMETA_String (for text) or FFMETA_Long (for numeric).
STRPTR artist = NULL;
GetFFMovieAttrsA(movie,
FFMETA_Key, FFMETA_Artist,
FFMETA_String, (ULONG)&artist,
TAG_DONE); |
Keys: FFMETA_Title, FFMETA_Artist, FFMETA_Album, FFMETA_Genre, FFMETA_Comment, FFMETA_Copyright, FFMETA_Date, FFMETA_Language, FFMETA_CodecName, FFMETA_BitRate, FFMETA_Mime.
Demuxers post richer tag dumps via FFEV_TAGS events during playback; applications drain them through PollFFMovieEvent().
For canonical 4CC metadata interoperable with photo / catalogue tools, build the movie's mediatypes object instead and use the full metadataclass.h API. FastForward exposes the player-relevant subset; mediatypes owns the long tail (cover art, GPS, ID3 round-trip, sidecars, etc.).
11. Media probe (file managers, browsers)
Probe without building a playback graph:
struct FFMediaInfo *info;
STRPTR mime;
ULONG duration, tracks;
info = ProbeFFMediaA(
FFPG_Path, "Work:Tunes/song.wav",
FFPG_Recognition, FFREC_LIGHT,
TAG_DONE);
GetFFMediaInfoAttrsA(info,
FFPG_Mime, (ULONG)&mime,
FFPG_Duration, (ULONG)&duration,
FFPG_TrackCount, (ULONG)&tracks,
TAG_DONE);
FreeFFMediaInfo(info); |
Sources accepted by ProbeFFMediaA():
| Tag |
Use |
| FFPG_Path |
Local file |
| FFPG_FileHandle |
Already-open BPTR |
| FFPG_MemData + FFPG_MemSize |
Buffer already in RAM |
| FFPG_URL |
Extension / MIME guess before a network source connects |
FFREC_LIGHT sniffs headers only. FFREC_HEAVY opens the demuxer briefly to learn duration when p_QueryDuration exists.
Probe attributes available via GetFFMediaInfoAttrsA():
FFPG_Mime, FFPG_Score (0..100), FFPG_Duration, FFPG_TrackCount, FFPG_HasAudio, FFPG_HasVideo, FFPG_HasSubtitle, FFPG_Seekable, FFPG_Width, FFPG_Height, FFPG_SampleRate, FFPG_Channels, FFPG_Title, FFPG_DemuxerName.
12. Thumbnails and still images
DecodeFFThumbnailA() decodes one video frame when a video codec plugin is available:
struct FFFrameResult *frame = DecodeFFThumbnailA(
FFTH_Path, path,
FFTH_Time, durationUsec / 2, /* halfway through */
FFTH_Width, 320,
FFTH_Height, 240,
FFTH_PixelFormat, FFVF_RGB24,
TAG_DONE);
if (frame != NULL) {
/* frame->fr_Data is fr_Width x fr_Height pixels, stride fr_Stride */
WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
rp, x, y, frame->fr_Width, frame->fr_Height,
RECTFMT_RGB);
FreeFFFrameResult(frame);
} |
Still bitmaps (PNG, ILBM, JPEG, GIF) should use datatypes.library (ReadDTObjectA) for Workbench and file-manager previews until png.codec / ilbm.codec plugins land. DecodeFFThumbnailA() returns NULL with FFERR_NOTSUPPORTED and a message directing apps to datatypes for those MIME types.
Animated GIFs are timed media: future gif.demux exposes them as a video track with frame delays in FFImageFormat. Until then, gifanim.datatype covers icon/preview use.
Subtitle files (.srt, .ssa) are FFTK_SUBTITLE tracks decoded by srt.codec / ssa.codec plugins producing FFBT_SUBTITLE buffers with FFSubtitleFormat timing. Plain .txt viewing remains a datatypes concern.
13. Errors and cancellation
struct FFErrorInfo err;
if (OpenFFMovieA(...) == NULL) {
GetFFLastError(&err);
/* err.ei_Code, err.ei_Message, err.ei_Component */
}
CancelFFMovie(movie); /* abort streaming / hung open */ |
GetFFLastError() returns a per-task structured error record - same model as IoErr() on dos.library. The component / function names in the record name the originating engine subsystem or plugin so the application can include them in error dialogues without having to maintain its own translation table.
Error codes:
| Code |
Meaning |
| FFERR_NONE (0) |
Success |
| FFERR_NOMEM (-1) |
Out of memory |
| FFERR_NODEV (-2) |
Device unavailable |
| FFERR_FORMAT (-3) |
Unrecognised container |
| FFERR_CODEC (-4) |
Codec failure |
| FFERR_STREAM (-5) |
Stream error |
| FFERR_SYNC (-6) |
Synchronisation failure |
| FFERR_BUFFER (-7) |
Buffer underrun / overflow |
| FFERR_NOTOPEN (-8) |
Object not open |
| FFERR_NOWINDOW (-9) |
Render target missing |
| FFERR_HARDWARE (-10) |
Hardware error |
| FFERR_DECODE (-11) |
Decoder rejected data |
| FFERR_NETWORK (-12) |
Network failure |
| FFERR_TIMEOUT (-13) |
Operation timed out |
| FFERR_NOTSEEKABLE (-14) |
Source cannot seek |
| FFERR_CANCELLED (-15) |
Cancelled by caller |
| FFERR_NOTFOUND (-16) |
Resource not found |
| FFERR_NOTSUPPORTED (-17) |
Feature unsupported |
14. Per-element tasks and Conductor synchronisation
FastForward maps the BeOS Media Kit's node-per-thread topology onto Exec primitives. Every FFElement defaults to FFER_INLINE so existing linear graphs keep working unchanged; heavy elements opt into a dedicated Task by passing tags at creation time:
struct TagItem etags[] = {
{ FFE_Plugin, (ULONG)plugin },
{ FFE_RunMode, FFER_TASK }, /* Exec Task, no DOS calls */
{ FFE_TaskPriority, 5 }, /* nice and quick */
{ FFE_StackSize, 16384 },
{ FFE_SyncToConductor, TRUE }, /* slave to graph's master clock */
{ FFE_PoolDepth, 4 }, /* 4 buffers in flight per link */
{ TAG_DONE, 0 }
};
struct FFElement *e = NewFFElementA(graph, etags); |
| Tag |
Meaning |
| FFE_RunMode = FFER_INLINE |
Run on caller's task. Default. Zero overhead, single-thread linear graphs. |
| FFE_RunMode = FFER_TASK |
Dedicated Exec Task. Use for codecs, filters, mixers - anything CPU-bound that should not stall its neighbours. |
| FFE_RunMode = FFER_PROCESS |
Dedicated DOS Process via CreateNewProc. Use for sources and sinks that call Lock, Read, OpenDevice etc. |
| FFE_TaskPriority |
BYTE priority (-128..+127). Default 0. Audio sinks usually +5; demuxers 0; speculative decoders -5. |
| FFE_StackSize |
Bytes; default 8192. Increase for stack-heavy codecs. |
| FFE_SyncToConductor |
BOOL. When set, FastForward attaches a realtime.library Player to the graph's master Conductor and signals the element's task on PLAYER_AlarmSigBit at frame boundaries. The BeOS BTimeSource pattern done with the Amiga primitive that already exists. Use it on producers (sources, decoders) so they wake at the right time instead of as fast as they can push. |
| FFE_PoolDepth |
Number of buffers in the per-link ring. 2 is the minimum (double-buffer); 4 is the default; deeper rings absorb more jitter at the cost of more RAM. |
When the graph runs, RunFFGraph() walks the element list, starts a Task for every element whose FFE_RunMode is non-INLINE, allocates per-element message ports and per-link buffer pools, and - for any element that asked for FFE_SyncToConductor - creates a Player attached to the graph's Conductor. StopFFGraph() signals every running task to exit and tears the plumbing down.
14.1 When to use task mode versus inline
| Use case |
Recommended layout |
| Simple WAV / AIFF player |
All FFER_INLINE. The MVP audio path is single-thread by design — task overhead would be pure waste. |
| MP3 + AHI on a 68040 |
Codec FFER_TASK, AHI sink FFER_PROCESS. Decoder runs ahead of the sink; sink pulls from the buffer pool when DMA needs more samples. |
| NLE timeline |
Each track as its own FFER_TASK, all attached to the same Conductor with FFE_SyncToConductor. Track heads stay sample-accurate even when one track's decoder briefly stalls. |
| Music sequencer |
Producers (synth, sampler) FFER_TASK with FFE_SyncToConductor, mixer FFER_TASK, AHI sink FFER_PROCESS. The Conductor is shared with OctaMED / CAMD so external transport drives the lot. |
| Network stream |
Source FFER_PROCESS (DOS-side socket I/O), demux FFER_TASK (parse ahead, absorb jitter via deeper FFE_PoolDepth), codec FFER_TASK, sink FFER_PROCESS. |
14.2 Topology granularity rule of thumb
Exec context switching is fast on classic Amiga hardware but it is not free. Every FFER_TASK / FFER_PROCESS boundary costs roughly one Signal and one Wait per buffer plus the cache disturbance of a context flush. On a stock 68030 a five-stage fine-grained pipeline (source → demux → filter A → filter B → codec → sink, each on its own task) can spend more cycles in Signal / WaitPort than in actual decode work; the asynchronicity win is overwhelmed by the per-frame switching cost.
Recommended pattern on classic 68k:
Default everything to FFER_INLINE. The graph builder honours
that; a single caller-task slice runs Pull → Decode → Filter sequentially with no port traffic at all.
**Cross task boundaries only where you actually want the
asynchrony**: disk / network I/O on the source, the AHI / video sink (so it can run at high priority), or one CPU-heavy codec that wants to pace ahead of its consumer.
Treat fine-grained per-filter tasks as a 68060 / RTG luxury.
They are the right answer when the cost of a context switch is dwarfed by the cost of the work between switches; they are the wrong answer when the work between switches is itself short.
The AHI sink is the canonical example of where the boundary
pays off: it is FFER_PROCESS (because OpenDevice wants DOS context), it bumps its own task priority once playback starts so an Intuition window-drag cannot starve it, and it pulls from the pool whenever AHI's mixer hook fires. Everything upstream can stay FFER_INLINE if the codec is light enough.
The framework gives you the asynchronous primitives; the graph builder does not silently fold elements together to "save context switches". Pick the layout that suits your hardware and your workload, and audit it on the slowest CPU you intend to support.
15. player.gadget and playback.gadget
player.gadget and playback.gadget are BOOPSI gadget classes for embedding FastForward playback inside Intuition windows. They wrap the same Movie API that Play uses, so application authors get a viewport and tape-deck transport without building custom UI or managing the player process directly.
15.1 Roles
| Gadget |
Responsibility |
| player.gadget |
Hosts the movie viewport, opens the FFMovie, and renders decoded video into its gadget box (or forwards audio to AHI). Handles resize and refresh. |
| playback.gadget |
Supplies tape-deck controls (play, pause, stop, seek, scan) that drive the active movie attached to a player.gadget. |
15.2 Install layout
Gadget class descriptors ship with FastForward alongside the library and plugins:
LIBS:fastforward.library
Classes/Gadgets/player.gadget
Classes/Gadgets/playback.gadget |
Link your application against fastforward.library and open the gadget classes with OpenLibrary() the same way you would for any BOOPSI gadget. The gadgets themselves call into the Movie API internally.
15.3 Typical window layout
Place a player.gadget in the main content area and a playback.gadget along the bottom edge. The playback gadget must reference the player gadget that owns the movie:
struct Gadget *player_gad, *transport_gad;
player_gad = (struct Gadget *)NewObject(NULL, "player.gadget",
GA_Left, 10,
GA_Top, 10,
GA_Width, win->Width - 20,
GA_Height, win->Height - 60,
GA_RelVerify, TRUE,
PLAYERGAD_Path, (ULONG)path,
TAG_END);
transport_gad = (struct Gadget *)NewObject(NULL, "playback.gadget",
GA_Left, 10,
GA_Top, win->Height - 44,
GA_Width, win->Width - 20,
GA_Height, 32,
PLAYBACKGAD_Player, (ULONG)player_gad,
TAG_END); |
PLAYERGAD_Path is the primary open tag (local file path today; URL tags follow the same Movie API rules as FFMT_URL). PLAYBACKGAD_Player wires transport buttons to the movie owned by that player gadget instance.
15.4 Common player.gadget tags
| Tag |
Type |
Purpose |
| PLAYERGAD_Path |
STRPTR |
Media file to open when the gadget is instantiated. |
| PLAYERGAD_URL |
STRPTR |
Network source (when a streaming source plugin is available). |
| PLAYERGAD_AutoPlay |
BOOL |
Start playback immediately after open (mirrors FFMT_AutoPlay). |
| PLAYERGAD_Loop |
BOOL |
Loop at end-of-stream (mirrors FFMT_Loop). |
| PLAYERGAD_Movie |
struct FFMovie * |
Attach an application-created movie instead of opening by path. The gadget renders into its box but does not own the CloseFFMovie() call unless you set PLAYERGAD_OwnMovie. |
15.5 Event loop
The gadget owns the underlying FFMovie process. Your application still waits on the window IDCMP port for gadget input, and may also merge the movie signal mask when you need end-of-stream or error notifications at the application level:
ULONG sigs, mask = (1UL << win->UserPort->mp_SigBit);
/* optional: OR in GetFFMovieSignalMask(movie) when you opened the movie yourself */
while (running) {
sigs = Wait(mask);
if (sigs & (1UL << win->UserPort->mp_SigBit)) {
struct IntuiMessage *imsg;
while ((imsg = (struct IntuiMessage *)GT_GetIMsg(win->UserPort))) {
switch (imsg->Class) {
case IDCMP_GADGETUP:
/* layout gadgets handle playback; app handles the rest */
break;
case IDCMP_CLOSEWINDOW:
running = FALSE;
break;
}
GT_ReplyIMsg(imsg);
}
}
} |
15.6 Gadgets vs raw Movie API
Use the gadgets when you want a standard player UI inside an Intuition window. Use the raw Movie API (sections 2–7) when you need a custom render path (FFMT_VideoSink callback), per-track control in a non-standard layout, or graph-based editing. You can combine both: open a movie yourself, pass it with PLAYERGAD_Movie, and keep using SetFFTrackAttrsA() on the underlying handle retrieved via GetAttr(PLAYERGAD_Movie, player_obj, ...).
16. mediatypes.library integration
mediatypes.library owns media-type identification (MIME / format kind) and the BOOPSI object surface. FastForward owns the multimedia pipeline runtime. The two libraries are designed to be used together:
For format / MIME identification, applications hand bytes to
SniffMediaTypeA() / ExamineMT() (mediatypes) rather than reinventing detection in FastForward.
For full multimedia playback / transcoding / mixing, a mediatypes
object identifies the format and exposes substreams via containerclass.h; the object then builds a FastForward Graph and hands execution off — fastforward.library provides typed FIFO endpoints, demuxers, decoders, renderers, sinks.
Conversely, FastForward defers to mediatypes for format
recognition. Until mediatypes.library ships, the engine falls back to a small built-in sniffer.
In practice a player application can use either surface:
Use FastForward directly (OpenFFMovieA) when you only need to
play a single file, you don't care about generic BOOPSI dispatch, and you don't need cover-art / sidecar / Workbench integration.
Use mediatypes (NewMTObjectA) when you want a uniform handle to
a piece of media, with full metadata, sidecar persistence, catalogue-tool support, optional datatypes.library interoperability, and an optional hand-off to FastForward when real playback is requested.
17. Reference: enums, error codes, transport rates
17.1 Movie state (GetFFMovieState)
| State |
Value |
Meaning |
| FFMS_STOPPED |
0 |
Opened but not yet started |
| FFMS_OPENED |
1 |
Synchronous open completed |
| FFMS_PREROLL |
2 |
Filling buffers before play |
| FFMS_PLAYING |
3 |
Actively decoding and presenting |
| FFMS_PAUSED |
4 |
Decoding suspended at position |
| FFMS_FINISHED |
5 |
Reached EOS without error |
| FFMS_ERROR |
6 |
Playback aborted; see GetFFLastError() |
17.2 Track kinds (FFTA_Kind)
| Kind |
Value |
| FFTK_AUDIO |
1 |
| FFTK_VIDEO |
2 |
| FFTK_DATA |
3 |
| FFTK_MIDI |
4 |
| FFTK_SUBTITLE |
5 |
17.3 Source kinds (FFSO_Kind)
| Kind |
Value |
Use |
| FFSK_FILE |
1 |
DOS file |
| FFSK_URL |
2 |
Network location |
| FFSK_MEMORY |
3 |
In-memory buffer |
| FFSK_CALLBACK |
4 |
FFIOCallbacks byte stream |
17.4 Clock kinds (FFCT_Kind)
| Kind |
Value |
Backend |
| FFCK_TIMER |
1 |
timer.device EUNIT_MICROHZ |
| FFCK_REALTIME |
2 |
realtime.library Conductor |
| FFCK_AUDIO |
3 |
Audio sink sample clock |
| FFCK_EXTERNAL |
4 |
App-driven |
17.5 Seek / control flags (SeekFFMovie)
| Flag |
Meaning |
| FFCF_FLUSH |
Discard pending buffers before seeking |
| FFCF_KEYFRAME |
Snap to the nearest keyframe |
| FFCF_BACKWARD |
Prefer seeking backwards over forwards |
| FFCF_ACCURATE |
Decode forward from keyframe to land on the exact frame |
17.6 Transport rate presets (FFMT_PlayRate, signed Q16.16)
| Constant |
Hex |
Meaning |
| FF_RATE_STOP |
0x00000000 |
Stop / pause |
| FF_RATE_PAUSE |
0x00000000 |
Alias for STOP |
| FF_RATE_PLAY |
0x00010000 |
Normal forward (1.0) |
| FF_RATE_FF_2X |
0x00020000 |
Fast-forward 2x |
| FF_RATE_FF_4X |
0x00040000 |
Fast-forward 4x |
| FF_RATE_FF_8X |
0x00080000 |
Fast-forward 8x |
| FF_RATE_SLOW_HALF |
0x00008000 |
Slow motion (0.5) |
| FF_RATE_REVERSE |
-0x00010000 |
Normal reverse |
| FF_RATE_REWIND_2X |
-0x00020000 |
Rewind 2x |
| FF_RATE_REWIND_4X |
-0x00040000 |
Rewind 4x |
| FF_RATE_REWIND_8X |
-0x00080000 |
Rewind 8x |
17.7 Loop modes (FFMT_LoopMode)
| Mode |
Value |
Behaviour |
| FFLM_OFF |
0 |
Play once, stop at end |
| FFLM_LOOP |
1 |
Return to start at end |
| FFLM_PINGPONG |
2 |
Alternate forward / reverse at boundaries |
| FFLM_REGION |
3 |
Loop between FFMT_LoopStart and FFMT_LoopEnd |
17.8 Event kinds (FFEvent.fe_Kind)
| Kind |
Value |
Carried payload |
| FFEV_FORMAT |
1 |
Format change negotiation |
| FFEV_SEEK |
2 |
Downstream seek completed |
| FFEV_FLUSH |
3 |
Upstream flushed pending data |
| FFEV_EOS |
4 |
End of stream |
| FFEV_TAGS |
5 |
Metadata tag list available |
| FFEV_QOS |
6 |
Quality-of-service feedback |
| FFEV_PROGRESS |
7 |
Buffering / transcode progress |
| FFEV_NETWORK |
8 |
Network state change |
| FFEV_ERROR |
9 |
Structured error posted to movie |
17.9 Probe recognition (FFPG_Recognition)
| Mode |
Value |
Behaviour |
| FFREC_LIGHT |
0 |
Header sniff only; fast |
| FFREC_HEAVY |
1 |
Open demuxer briefly for duration / tracks |
17.10 Buffer types (FFBuffer.fb_Type)
| Type |
Value |
Use |
| FFBT_UNKNOWN |
0 |
Untyped / placeholder |
| FFBT_PACKET |
1 |
Opaque container packet |
| FFBT_PCM |
2 |
Decoded PCM audio |
| FFBT_VIDEO |
3 |
Decoded video frame |
| FFBT_DATA |
4 |
Generic data |
| FFBT_EVENT |
5 |
Control event, no payload |
| FFBT_SUBTITLE |
6 |
Timed text cue (see FFSubtitleFormat) |
| FFBT_IMAGE |
7 |
Still or animated frame (see FFImageFormat) |
| FFBT_TEXT |
8 |
Plain untimed text chunk |
17.11 Log levels (SetFFLogLevel)
| Level |
Value |
Behaviour |
| FFLOG_NONE |
0 |
Silence |
| FFLOG_ERROR |
1 |
Errors only |
| FFLOG_WARN |
2 |
Warnings and errors (default) |
| FFLOG_INFO |
3 |
Informational notices |
| FFLOG_DEBUG |
4 |
Verbose internal traces |
Appendix A: Quick recipes
A.1 Play an audio file in 30 lines of C
#include <libraries/fastforward.h>
#include <proto/fastforward.h>
#include <proto/exec.h>
#include <dos/dos.h>
struct Library *FastForwardBase;
int main(int argc, char **argv)
{
struct FFMovie *m;
ULONG mask, signals;
struct FFEvent ev;
if (argc < 2) return 5;
FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
if (!FastForwardBase) return 20;
LoadFFPlugins(NULL);
m = OpenFFMovie(FFMT_Path, (ULONG)argv[1],
FFMT_AutoPlay, TRUE);
if (!m) goto out;
mask = GetFFMovieSignalMask(m);
while (GetFFMovieState(m) == FFMS_PLAYING ||
GetFFMovieState(m) == FFMS_PREROLL) {
signals = Wait(mask | SIGBREAKF_CTRL_C);
if (signals & SIGBREAKF_CTRL_C) { StopFFMovie(m); break; }
while (PollFFMovieEvent(m, &ev) > 0)
if (ev.fe_Kind == FFEV_EOS) goto done;
}
done:
CloseFFMovie(m);
out:
UnloadFFPlugins();
FF_CloseLibrary(FastForwardBase);
return 0;
} |
A.2 Background music for a game (looped)
movie = OpenFFMovie(
FFMT_Path, (ULONG)"music:level1.mod",
FFMT_AutoPlay, TRUE,
FFMT_Loop, TRUE,
FFMT_Volume, 0x8000); /* 50% so SFX bury it cleanly */ |
A.3 Show a poster frame for a video file
struct FFFrameResult *frame = DecodeFFThumbnail(
FFTH_Path, clipPath,
FFTH_Time, FF_TIMESCALE_USEC * 5, /* 5 seconds in */
FFTH_Width, 160,
FFTH_Height, 120,
FFTH_PixelFormat, FFVF_RGB24);
if (frame) {
WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
rp, x, y, frame->fr_Width, frame->fr_Height,
RECTFMT_RGB);
FreeFFFrameResult(frame);
} |
A.4 Probe a folder and populate a file-manager listing
struct ExAllControl *ctrl = AllocDosObject(DOS_EXALLCONTROL, NULL);
/* ... ExAll loop omitted ... */
for (each entry e) {
struct FFMediaInfo *info = ProbeFFMedia(
FFPG_Path, e->ed_Name,
FFPG_Recognition, FFREC_LIGHT);
if (info) {
STRPTR mime = NULL;
ULONG dur = 0;
GetFFMediaInfoAttrs(info,
FFPG_Mime, (ULONG)&mime,
FFPG_Duration, (ULONG)&dur);
AddRow(e->ed_Name, mime, dur);
FreeFFMediaInfo(info);
}
} |
A.5 Cancel a stuck network open
movie = OpenFFMovie(FFMT_URL, (ULONG)"http://example.com/foo.mp4",
FFMT_AsyncOpen, TRUE,
FFMT_OpenSignal, openSig);
/* user clicks Cancel before the OpenSignal fires */
CancelFFMovie(movie);
CloseFFMovie(movie); |
A.6 Render decoded audio into your own widget
LONG OnAudio(APTR user, APTR data, ULONG size,
struct FFAudioFormat *af, ULONG ts) {
Waveform_Append((Waveform *)user, data, size, af->af_SampleRate);
return 0;
}
struct FFCallbacks cb = { NULL, OnAudio, NULL, NULL, NULL, my_widget };
struct FFGraph *g = BuildFFGraphFromFile(path, TAG_DONE);
struct FFElement *sink = NewFFCallbackSink(g, &cb, TAG_DONE);
/* find and replace the default audio sink with `sink`, then run */
RunFFGraph(g); |
A.7 Transcode a file in five lines
struct FFGraph *g = BuildFFGraphFromFile("in.wav", TAG_DONE);
/* find the default sink and dispose it; install a file mux instead */
ReplaceWithFileMux(g, "out.aiff");
RunFFGraph(g);
WaitForGraphCompletion(g);
DeleteFFGraph(g); |
A.8 Tempo-quantised sequencer step
struct FFClock *clk = CreateFFClock(
FFCT_Kind, FFCK_REALTIME,
FFCT_TickRate, 192, /* PPQN */
FFCT_Tempo, 140000); /* 140 BPM */
StartFFClock(clk);
while (running) {
ULONG now = GetFFClockTime(clk);
ULONG sigs = WaitFFClock(clk, now + 1, midiInSig | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C) break;
if (sigs & midiInSig) Drain_MidiInput();
Trigger_NextStep(now + 1);
} |
Appendix B: Integration cheatsheet
| Goal |
Recipe |
| Play a file |
Open Movie with FFMT_Path and FFMT_AutoPlay. Wait on signal mask. |
| Background music for a game |
Same, plus FFMT_Loop. |
| Custom video output |
NewFFCallbackSinkA(), pass via FFMT_VideoSink. |
| Waveform display |
FFMT_AudioSink = callback sink. Hand samples to a widget. |
| Transcoder |
BuildFFGraphFromFileA(), replace sink with a file mux element, run graph. |
| NLE timeline |
CreateFFGraphA(), multiple sources via mixer / compositor element, master FFClock FFCK_REALTIME. |
| Sequencer |
CreateFFClockA(FFCK_REALTIME), dispatch events with WaitFFClock(). |
| File manager preview |
ProbeFFMediaA() for metadata; still images via datatypes.library; timed media via DecodeFFThumbnailA() when a video decoder exists. |
| Browser / streaming |
OpenFFMovieA(FFMT_URL, url, ...) today; http.source / hls.demux plugins supply bytes later. |
| Cancel network open |
CancelFFMovie() signals the player process and posts FFEV_ERROR. |
| Tape-deck rewind |
RewindFFMovie(m) (composes StopFFMovie + SeekFFMovie(m, 0, 0)). |
| Fast-forward 4x |
ScanFFMovie(m, FF_RATE_FF_4X). |
| Embed player in a window |
player.gadget + playback.gadget (section 15). |
Appendix A: Quick recipes
A.1 Play an audio file in 30 lines of C
#include <libraries/fastforward.h>
#include <proto/fastforward.h>
#include <proto/exec.h>
#include <dos/dos.h>
struct Library *FastForwardBase;
int main(int argc, char **argv)
{
struct FFMovie *m;
ULONG mask, signals;
struct FFEvent ev;
if (argc < 2) return 5;
FastForwardBase = FF_OpenLibrary(FF_API_VERSION);
if (!FastForwardBase) return 20;
LoadFFPlugins(NULL);
m = OpenFFMovie(FFMT_Path, (ULONG)argv[1],
FFMT_AutoPlay, TRUE);
if (!m) goto out;
mask = GetFFMovieSignalMask(m);
while (GetFFMovieState(m) == FFMS_PLAYING ||
GetFFMovieState(m) == FFMS_PREROLL) {
signals = Wait(mask | SIGBREAKF_CTRL_C);
if (signals & SIGBREAKF_CTRL_C) { StopFFMovie(m); break; }
while (PollFFMovieEvent(m, &ev) > 0)
if (ev.fe_Kind == FFEV_EOS) goto done;
}
done:
CloseFFMovie(m);
out:
UnloadFFPlugins();
FF_CloseLibrary(FastForwardBase);
return 0;
} |
A.2 Background music for a game (looped)
movie = OpenFFMovie(
FFMT_Path, (ULONG)"music:level1.mod",
FFMT_AutoPlay, TRUE,
FFMT_Loop, TRUE,
FFMT_Volume, 0x8000); /* 50% so SFX bury it cleanly */ |
A.3 Show a poster frame for a video file
struct FFFrameResult *frame = DecodeFFThumbnail(
FFTH_Path, clipPath,
FFTH_Time, FF_TIMESCALE_USEC * 5, /* 5 seconds in */
FFTH_Width, 160,
FFTH_Height, 120,
FFTH_PixelFormat, FFVF_RGB24);
if (frame) {
WritePixelArray(frame->fr_Data, 0, 0, frame->fr_Stride,
rp, x, y, frame->fr_Width, frame->fr_Height,
RECTFMT_RGB);
FreeFFFrameResult(frame);
} |
A.4 Probe a folder and populate a file-manager listing
struct ExAllControl *ctrl = AllocDosObject(DOS_EXALLCONTROL, NULL);
/* ... ExAll loop omitted ... */
for (each entry e) {
struct FFMediaInfo *info = ProbeFFMedia(
FFPG_Path, e->ed_Name,
FFPG_Recognition, FFREC_LIGHT);
if (info) {
STRPTR mime = NULL;
ULONG dur = 0;
GetFFMediaInfoAttrs(info,
FFPG_Mime, (ULONG)&mime,
FFPG_Duration, (ULONG)&dur);
AddRow(e->ed_Name, mime, dur);
FreeFFMediaInfo(info);
}
} |
A.5 Cancel a stuck network open
movie = OpenFFMovie(FFMT_URL, (ULONG)"http://example.com/foo.mp4",
FFMT_AsyncOpen, TRUE,
FFMT_OpenSignal, openSig);
/* user clicks Cancel before the OpenSignal fires */
CancelFFMovie(movie);
CloseFFMovie(movie); |
A.6 Render decoded audio into your own widget
LONG OnAudio(APTR user, APTR data, ULONG size,
struct FFAudioFormat *af, ULONG ts) {
Waveform_Append((Waveform *)user, data, size, af->af_SampleRate);
return 0;
}
struct FFCallbacks cb = { NULL, OnAudio, NULL, NULL, NULL, my_widget };
struct FFGraph *g = BuildFFGraphFromFile(path, TAG_DONE);
struct FFElement *sink = NewFFCallbackSink(g, &cb, TAG_DONE);
/* find and replace the default audio sink with `sink`, then run */
RunFFGraph(g); |
A.7 Transcode a file in five lines
struct FFGraph *g = BuildFFGraphFromFile("in.wav", TAG_DONE);
/* find the default sink and dispose it; install a file mux instead */
ReplaceWithFileMux(g, "out.aiff");
RunFFGraph(g);
WaitForGraphCompletion(g);
DeleteFFGraph(g); |
A.8 Tempo-quantised sequencer step
struct FFClock *clk = CreateFFClock(
FFCT_Kind, FFCK_REALTIME,
FFCT_TickRate, 192, /* PPQN */
FFCT_Tempo, 140000); /* 140 BPM */
StartFFClock(clk);
while (running) {
ULONG now = GetFFClockTime(clk);
ULONG sigs = WaitFFClock(clk, now + 1, midiInSig | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C) break;
if (sigs & midiInSig) Drain_MidiInput();
Trigger_NextStep(now + 1);
} |
Appendix B: Integration cheatsheet
| Goal |
Recipe |
| Play a file |
Open Movie with FFMT_Path and FFMT_AutoPlay. Wait on signal mask. |
| Background music for a game |
Same, plus FFMT_Loop. |
| Custom video output |
NewFFCallbackSinkA(), pass via FFMT_VideoSink. |
| Waveform display |
FFMT_AudioSink = callback sink. Hand samples to a widget. |
| Transcoder |
BuildFFGraphFromFileA(), replace sink with a file mux element, run graph. |
| NLE timeline |
CreateFFGraphA(), multiple sources via mixer / compositor element, master FFClock FFCK_REALTIME. |
| Sequencer |
CreateFFClockA(FFCK_REALTIME), dispatch events with WaitFFClock(). |
| File manager preview |
ProbeFFMediaA() for metadata; still images via datatypes.library; timed media via DecodeFFThumbnailA() when a video decoder exists. |
| Browser / streaming |
OpenFFMovieA(FFMT_URL, url, ...) today; http.source / hls.demux plugins supply bytes later. |
| Cancel network open |
CancelFFMovie() signals the player process and posts FFEV_ERROR. |
| Tape-deck rewind |
RewindFFMovie(m) (composes StopFFMovie + SeekFFMovie(m, 0, 0)). |
| Fast-forward 4x |
ScanFFMovie(m, FF_RATE_FF_4X). |
| Embed player in a window |
player.gadget + playback.gadget (section 15). |
Return to the FastForward Overview.
|
Back to top
|
|