These docs describe the main branch, including unreleased changes. Read the 0.8.1 docs.

OpenTUI 0.8.1

npmGitHub release

OpenTUI 0.8.1 can play video. VideoRenderable, and <video> in React and Solid, plays an MP4 file with H.264 video and AAC audio. Native code does the decoding, the media clock, the audio, and the drawing. Frames draw like an ImageRenderable, with Kitty, Sixel, or Unicode blocks:

const video = new VideoRenderable(renderer, {
  source: "./clip.mp4",
  width: 60,
  height: 20,
  autoplay: true,
  loop: true,
  onError: console.error,
})
renderer.root.add(video)

To show only part of a clip, you could make a node the size of the whole board and let its parent clip it. That gets expensive quickly. Nine such tiles of one 1080p clip used 2.3 cores and sent 58 MB of Kitty data per frame, which is far over the output limit. The new crop option selects a region in source pixels, and the decoder scales and copies only that region. The same nine tiles use 0.93 cores and 5.1 MB per frame. There is a second cost: each player of a clip with audio opens its own audio device, even when it is muted. Set audio: false on every tile except the one that plays sound:

// The top-left quarter of a 1920x1080 clip, with no audio device.
const tile = new VideoRenderable(renderer, {
  source: "./clip.mp4",
  crop: { left: 0, top: 0, width: 960, height: 540 },
  audio: false,
  width: 30,
  height: 10,
})

ImageRenderable takes the same crop. To decode video, the native library now statically links a decode-only LGPL build of FFmpeg 9.0.2. This makes each stripped library 2.6 to 3.5 MB larger. On linux-x64 it grows from 5.20 MB to 8.40 MB.

Over a slow link such as ssh, the terminal reads frames more slowly than OpenTUI writes them. Once the pty and the link filled up, the JavaScript thread waited inside write(2), and keys and timers waited behind it. On a 48 KB/s link it spent 79% of its time there, and the median key wait was 123.7 ms. In 0.8.1, on Linux and macOS when stdout is a terminal, a native thread owned by the Session does the write. On the same link, the JavaScript thread spends no time in write(2), and the median key wait is 0.1 ms. The screen still lags by the size of the link’s buffers. The app also uses more CPU per run, because it now does the updates that it used to skip while it waited. Set OTUI_STDOUT_THREAD=0 to write on the JavaScript thread as before.

A slow link also made exit unreliable. In 0.8.0, the close gave up after one second and cancelled the session before it wrote the restore sequence. A 40-row quit at 16 KB/s left the terminal in the alternate screen with mouse reporting on. Now the close waits as long as output keeps moving. If nothing is written for one second, or if the close has run for ten seconds, the renderer stops waiting. It lets the write in progress finish, drops the queued output, writes the restore, and rejects closed as late.

The Session writes at most 8 MiB in one frame. In 0.8.0, a larger frame failed, and the repaint after it was just as large. The screen froze, but input still worked. Three 1000x700 Kitty images were enough to cause it. Now a frame sends its cells and the images that fit, and later frames send the rest. If one image is too large for a frame on its own, OpenTUI sends a copy at the size it is shown. A 1272x2015 photo shown at 880x1400 pixels sends 6.6 MB instead of 13.7 MB. The image draws as blocks only if that copy is too large as well. If a frame’s cells alone are too large, the new frame:dropped event reports it once for each content. In 0.8.0, console.error reported it on every frame:

renderer.on(CliRenderEvents.FRAME_DROPPED, ({ bytes, limit }) => {
  console.warn(`dropped a frame of ${bytes} bytes (limit ${limit})`)
})

One layout change can show in existing apps. When Yoga gives a node zero width or height, OpenTUI used to paint and hit-test it as one cell, on top of its siblings. A common case is a one-row header above a ScrollBox with flexGrow. Once the list is taller than the screen, Yoga shrinks the header to zero rows. The header and the first list row then mixed into one line. Now the header is not drawn at all. To keep it, give the header flexShrink: 0, or give the ScrollBox flexBasis: 0:

const column = new BoxRenderable(renderer, { flexDirection: "column", height: "100%" })
column.add(new TextRenderable(renderer, { content: "filter: (type)", flexShrink: 0 }))
column.add(new ScrollBoxRenderable(renderer, { flexGrow: 1, minHeight: 0 }))

Breaking changes

  • native: ot_session_renderer_state is 40 bytes instead of 32. Its reserved field at offset 28 is now image_uploads_pending, and dropped_frames and dropped_frame_bytes follow it, so allocate 40 bytes. See Host I/O and time. (#1890)
  • native: The native library and the Zig module need Zig 0.17.0. A Zig 0.16 build cache or FFmpeg prefix is rebuilt or rejected. See Zig. (#1586)

Added

  • core: VideoRenderable plays an MP4 file with H.264 video and AAC audio, with play(), pause(), seek(), loop, volume, maxFps, and callbacks such as onReady and onEnd. With audio: false, it opens no audio device and follows the wall clock. See Video. (#1800, #1884, #1883, #1885, #1887, #1889)
  • react: <video> renders a VideoRenderable, and VideoProps types its props. See Video. (#1800, #1884)
  • solid: <video> renders a VideoRenderable, and VideoProps types its props. See Video. (#1800, #1884)
  • core: ImageRenderable and VideoRenderable take crop: { left, top, width, height } in source pixels, and fit applies to that region. An invalid crop calls onError and keeps the previous crop. See Image and Video. (#1888)
  • core: The frame:dropped event (CliRenderEvents.FRAME_DROPPED) reports a frame that is too large for the output once for each content, with { bytes, limit }. It replaces the console.error call on each frame. See Renderer. (#1890)
  • core: A text draw in NativeBufferDraw takes maxWidth and ellipsis, which fit the text to a cell width and end wider text with “…”. See Buffer API. (#1828)
  • native: OT_SCENE_VIDEO, ot_scene_set_video, and the ot_video_* functions add a video node to a scene. ot_scene_set_crop sets the source region of a video or image node, and the OT_VIDEO_OPEN_NO_AUDIO flag of ot_video_open opens no audio device. See C. (#1800, #1884, #1888)
  • native: ot_session_post_stdout gives queued output to the stdout writer thread of the Session and reports OT_STDOUT_IDLE or OT_STDOUT_IN_FLIGHT. ot_session_drop_output drops queued output but keeps the copy in flight and the restore. See Host I/O and time. (#1858, #1893)
  • native: The text draw record has a max_width field and the OT_BUFFER_DRAW_FIT_ELLIPSIS flag. Records of 44 bytes from older bindings still draw without a limit. See C. (#1828)

Changed

  • core: On Linux and macOS, when stdout is a terminal, a native thread writes the renderer output, so a slow link does not block keys and timers. Set OTUI_STDOUT_THREAD=0 to write on the JavaScript thread. See Environment variables. (#1858, #1881)
  • core: In split-footer mode, captured stdout has a limit of 64 MiB. From half the limit, stdout.write() returns false until 'drain', and a write past the limit replaces the oldest unwritten rows with one [N lines dropped] row. See Renderer. (#1856)
  • core: The console capture that console.dir fills keeps only its newest 1,000 writes. See Console overlay. (#1856)
  • core: A node with zero width or height from Yoga draws nothing and gets no mouse events, but its children still paint unless it clips them. To keep a header above a growing ScrollBox, give the header flexShrink: 0. See ScrollBox and Layout. (#1869)
  • core: setProgramStatus() accepts reports until the terminal close starts, so a destroy listener or a Solid onCleanup can clear a record at exit. On a failed session, it returns false instead of throwing. See Program status. (#1871)
  • core: TextTableRenderable, SelectRenderable, and TabSelectRenderable draw into the frame unless buffered is true, and they keep no offscreen buffer. Translucent colors blend once over the final background, so they can differ from 0.8.0. See TextTable, Select, and TabSelect. (#1834, #1831)
  • core: LineNumberRenderable draws its gutter into the frame, so a scroll frame makes no draw call. Translucent gutter colors under ancestor opacity blend for each primitive instead of as a group. See Line number gutter. (#1833)
  • core: The TimeToFirstDrawRenderable label draws with a transparent background and fades with the opacity of its ancestors. See TimeToFirstDraw. (#1828)
  • core: A z-index change or a reparent in an update, resize, or layout acknowledgement shows in the same frame, not one frame later. See Frames and output. (#1829)
  • core: A culling ScrollBox finds its visible children by binary search and prepares a culled subtree only when it shows. A one-row scroll of a 10,000-row list takes 1.28 M instructions per frame instead of 5.52 M. See ScrollBox. (#1837, #1867)
  • core: When a column that follows its content grows, text and editor nodes reuse their cached size. A grown or appended row costs one measure call instead of one call for each row. See Layout. (#1859)
  • core: NativeImage decode, crop, resize, and composite examine alpha 64 bytes at a time. In the crop benchmark, extract takes 10.6 M instructions instead of 56.2 M. See NativeImage. (#1880)
  • keymap: useBindings in the React and Solid adapters replaces its layer in one update, and the layer keeps its mount position instead of moving to the top. A plain-data layer that differs only in its functions stays registered and emits no state event. See Solid keymap integration and React keymap integration. (#1785)
  • native: OT_LAYOUT_PAINT of a node that the latest paint list did not paint returns its solved geometry with current translations and scroll offsets. Before, it returned the place where the node last painted. See Frames and output. (#1867)
  • native: The native library statically links decode-only LGPL FFmpeg 9.0.2 and zlib, so each stripped library is 2.6 to 3.5 MB larger. The packages ship the license, source, and relink notices. See Video. (#1800)

Fixed

  • core: A frame larger than the 8 MiB output limit no longer freezes the screen. The frame sends its cells and the images that fit, and an image too large for one frame goes at its placement size before it falls back to blocks. See Image. (#1890, #1896)
  • core: In split-footer mode, the renderer drops a scrollback commit that is larger than one frame and reports it through frame:dropped. A quit after such a commit restores the terminal. See Renderer. (#1890)
  • core: When an app quits over a slow link, the renderer restores the terminal instead of leaving the alternate screen and mouse reporting on. A late close drops the queued output and rejects closed. See Lifecycle and cleanup. (#1872, #1893)
  • core: A measured node with a margin, such as a text with margin: 1, wraps at the correct width after its parent grows by less than the margin. See Layout. (#1860)
  • core: A code block measures its text before the first highlight arrives, so a ScrollBox with Markdown that rebuilds each frame can scroll. A ScrollBox listens for selection only while it auto-scrolls, so rebuilds no longer leak listeners. See ScrollBox and Code. (#1863)
  • core: After a syntaxStyle or fg change, MarkdownRenderable recolors horizontal rules, keeps the blank lines under blocks, and shows a header-only table on every frame. In coalesced mode, code blocks in list items leave streaming mode after streaming = false. See Markdown. (#1857)
  • core: TabSelectRenderable cuts tab names and descriptions at a cell width, so a CJK name stays in its tab and a cut surrogate pair no longer draws U+FFFD. A TabSelect with a fixed width draws before it gets focus. See TabSelect. (#1828, #1831)
  • core: The line-number gutter measures signs with native glyph widths, so a number no longer covers part of a tab sign or a family emoji sign. See Line number gutter. (#1833)
  • core: In split-footer mode with captured stdout, Uint8Array writes decode as text, also when a multibyte character spans two writes. See Renderer. (#1735)
  • core: On WSLg, a clipboard read waits until its new helper has focus, so it no longer reads an offer without focus. See Clipboard. (#1862)

API changes#

@opentui/core#

Added

  • class VideoRenderable extends Renderable
  • constructor VideoRenderable(ctx: RenderContext, options: VideoRenderableOptions)
  • enum-member CliRenderEvents.FRAME_DROPPED = "frame:dropped"
  • enum-member NativeStatus.VideoFailed = -60
  • function checkCrop(crop: ExtractOptions, width?: number, height?: number): ExtractOptions
  • function sameCrop(a: ExtractOptions | undefined, b: ExtractOptions | undefined): boolean
  • interface CliRendererFrameDroppedEvent
  • interface NativeVideoInfo
  • interface NativeVideoState
  • interface VideoMetadata
  • interface VideoRenderableOptions extends RenderableOptions<VideoRenderable>
  • method FFIRenderLib.sceneSetCrop(context: NativeContextHandle, node: SceneNodeHandle, crop: ExtractOptions | undefined): void
  • method FFIRenderLib.sceneSetVideo(context: NativeContextHandle, node: SceneNodeHandle, fit: "fit" | "cover" | "fill", buffer: ContextBufferHandle | null): void
  • method FFIRenderLib.sessionDropOutput(context: NativeContextHandle, session: SessionHandle): void
  • method FFIRenderLib.sessionPostStdout(context: NativeContextHandle, session: SessionHandle, maxBytes: number, wait: boolean): { written: number; inFlight: boolean; }
  • method FFIRenderLib.videoCall(context: NativeContextHandle, node: SceneNodeHandle, operation: "ot_video_play" | "ot_video_pause"): void
  • method FFIRenderLib.videoGetError(context: NativeContextHandle, node: SceneNodeHandle): string
  • method FFIRenderLib.videoGetState(context: NativeContextHandle, node: SceneNodeHandle): NativeVideoState
  • method FFIRenderLib.videoOpen(context: NativeContextHandle, node: SceneNodeHandle, path: string, audio: boolean): NativeVideoInfo
  • method FFIRenderLib.videoSeek(context: NativeContextHandle, node: SceneNodeHandle, timeUs: bigint): void
  • method FFIRenderLib.videoSetAvSyncOffset(context: NativeContextHandle, node: SceneNodeHandle, offsetUs: bigint): void
  • method FFIRenderLib.videoSetLoop(context: NativeContextHandle, node: SceneNodeHandle, loop: boolean): void
  • method FFIRenderLib.videoSetMaxFps(context: NativeContextHandle, node: SceneNodeHandle, fps: number): void
  • method FFIRenderLib.videoSetMuted(context: NativeContextHandle, node: SceneNodeHandle, muted: boolean): void
  • method FFIRenderLib.videoSetVolume(context: NativeContextHandle, node: SceneNodeHandle, volume: number): void
  • property ImageRenderable.crop: ExtractOptions | undefined
  • property ImageRenderableOptions.crop?: ExtractOptions
  • readonly property NativeSession.open: boolean
  • property NativeSessionRendererState.droppedFrameBytes: number
  • property NativeSessionRendererState.droppedFrames: number
  • property NativeSessionRendererState.imageUploadsPending: number

Changed

  • type NativeBufferDraw = { operation: "clear"; background?: RGBA; } | (BufferDrawPosition & { operation: "fill"; width?: number; height?: number; background?: RGBA; }) | (BufferDrawPosition & BufferDrawColors & { operation: "text"; text?: string; attributes?: number; }) | (BufferDrawPosition & BufferDrawColors & { operation: "cell" | "cellBlend" | "char"; char?: number; attributes?: number; }) | (BufferDrawPosition & BufferDrawColors & { operation: "box"; width?: number; height?: number; titleColor?: RGBA; packedOptions?: number; borderChars?: Uint32Array; text?: string; bottomTitle?: string; }) | (BufferDrawPosition & { operation: "compose"; source: ContextBufferHandle; sourceX?: number; sourceY?: number; sourceWidth?: number; sourceHeight?: number; }) | { operation: "respectAlpha"; enabled: boolean; }type NativeBufferDraw = { operation: "clear"; background?: RGBA; } | (BufferDrawPosition & { operation: "fill"; width?: number; height?: number; background?: RGBA; }) | (BufferDrawPosition & BufferDrawColors & { operation: "text"; text?: string; attributes?: number; maxWidth?: number; ellipsis?: boolean; }) | (BufferDrawPosition & BufferDrawColors & { operation: "cell" | "cellBlend" | "char"; char?: number; attributes?: number; }) | (BufferDrawPosition & BufferDrawColors & { operation: "box"; width?: number; height?: number; titleColor?: RGBA; packedOptions?: number; borderChars?: Uint32Array; text?: string; bottomTitle?: string; }) | (BufferDrawPosition & { operation: "compose"; source: ContextBufferHandle; sourceX?: number; sourceY?: number; sourceWidth?: number; sourceHeight?: number; }) | { operation: "respectAlpha"; enabled: boolean; }

@opentui/keymap#

Added

  • property KeymapExtensionContext.layers: Pick<LayerService<TTarget, TEvent>, "createSlot" | "replaceLayer">

@opentui/react#

Added

  • type VideoProps = ComponentProps<VideoRenderableOptions, VideoRenderable>

Changed

  • const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; markdown: typeof MarkdownRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; scrollbox: typeof ScrollBoxRenderable; "ascii-font": typeof ASCIIFontRenderable; "tab-select": typeof TabSelectRenderable; "line-number": typeof LineNumberRenderable; image: typeof ImageRenderable; span: typeof SpanRenderable; br: typeof LineBreakRenderable; b: typeof BoldSpanRenderable; strong: typeof BoldSpanRenderable; i: typeof ItalicSpanRenderable; em: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; a: typeof LinkRenderable; }const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; markdown: typeof MarkdownRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; scrollbox: typeof ScrollBoxRenderable; "ascii-font": typeof ASCIIFontRenderable; "tab-select": typeof TabSelectRenderable; "line-number": typeof LineNumberRenderable; image: typeof ImageRenderable; video: typeof VideoRenderable; span: typeof SpanRenderable; br: typeof LineBreakRenderable; b: typeof BoldSpanRenderable; strong: typeof BoldSpanRenderable; i: typeof ItalicSpanRenderable; em: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; a: typeof LinkRenderable; }
  • type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : TConstructor extends RenderableConstructor<TextareaRenderable> ? NonStyledProps | "placeholder" | "initialValue" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" | "conceal" | "drawUnstyledText" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : NonStyledPropstype GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : TConstructor extends RenderableConstructor<TextareaRenderable> ? NonStyledProps | "placeholder" | "initialValue" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" | "conceal" | "drawUnstyledText" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : TConstructor extends RenderableConstructor<VideoRenderable> ? NonStyledProps | "source" | "autoplay" | "loop" | "audio" | "muted" | "volume" | "avSyncOffsetMs" | "maxFps" : NonStyledProps

@opentui/react/jsx-dev-runtime#

Added

  • property JSX.IntrinsicElements.video: VideoProps

@opentui/react/jsx-runtime#

Added

  • property JSX.IntrinsicElements.video: VideoProps

@opentui/solid#

Added

  • property JSX.IntrinsicElements.video: VideoProps
  • type VideoProps = ComponentProps<VideoRenderableOptions, VideoRenderable>

Changed

  • const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; ascii_font: typeof ASCIIFontRenderable; tab_select: typeof TabSelectRenderable; scrollbox: typeof ScrollBoxRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; line_number: typeof LineNumberRenderable; markdown: typeof MarkdownRenderable; image: typeof ImageRenderable; span: typeof SpanRenderable; strong: typeof BoldSpanRenderable; b: typeof BoldSpanRenderable; em: typeof ItalicSpanRenderable; i: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; br: typeof LineBreakRenderable; a: typeof LinkRenderable; }const baseComponents: { box: typeof BoxRenderable; text: typeof TextRenderable; input: typeof InputRenderable; select: typeof SelectRenderable; textarea: typeof TextareaRenderable; ascii_font: typeof ASCIIFontRenderable; tab_select: typeof TabSelectRenderable; scrollbox: typeof ScrollBoxRenderable; code: typeof CodeRenderable; diff: typeof DiffRenderable; line_number: typeof LineNumberRenderable; markdown: typeof MarkdownRenderable; image: typeof ImageRenderable; video: typeof VideoRenderable; span: typeof SpanRenderable; strong: typeof BoldSpanRenderable; b: typeof BoldSpanRenderable; em: typeof ItalicSpanRenderable; i: typeof ItalicSpanRenderable; u: typeof UnderlineSpanRenderable; br: typeof LineBreakRenderable; a: typeof LinkRenderable; }
  • type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "minLength" | "maxLength" | "placeholder" | "value" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : NonStyledPropstype GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" | "bottomTitle" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "minLength" | "maxLength" | "placeholder" | "value" : TConstructor extends RenderableConstructor<CodeRenderable> ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" : TConstructor extends RenderableConstructor<MarkdownRenderable> ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" : TConstructor extends RenderableConstructor<ImageRenderable> ? NonStyledProps | "source" : TConstructor extends RenderableConstructor<VideoRenderable> ? NonStyledProps | "source" | "autoplay" | "loop" | "audio" | "muted" | "volume" | "avSyncOffsetMs" | "maxFps" : NonStyledProps

@opentui/solid/jsx-dev-runtime#

Added

  • property JSX.IntrinsicElements.video: VideoProps

@opentui/solid/jsx-runtime#

Added

  • property JSX.IntrinsicElements.video: VideoProps

opentui.h#

Added

  • const OT_BUFFER_DRAW_FIT_ELLIPSIS = 16
  • const OT_SCENE_VIDEO = 9
  • const OT_STDOUT_IDLE = 0
  • const OT_STDOUT_IN_FLIGHT = 1
  • const OT_VIDEO_AUDIO = 8
  • const OT_VIDEO_BUFFERING = 4
  • const OT_VIDEO_ENDED = 2
  • const OT_VIDEO_ERROR = 32
  • const OT_VIDEO_FAILED = -60
  • const OT_VIDEO_HAS_FRAME = 16
  • const OT_VIDEO_OPEN_NO_AUDIO = 1
  • const OT_VIDEO_PLAYING = 1
  • field ot_buffer_draw_text_record.max_width: uint32_t, offset 44
  • field ot_session_renderer_state.dropped_frame_bytes: uint32_t, offset 36
  • field ot_session_renderer_state.dropped_frames: uint32_t, offset 32
  • field ot_session_renderer_state.image_uploads_pending: uint32_t, offset 28
  • function ot_scene_set_crop(ot_context *, const ot_handle *node, uint32_t left, uint32_t top, uint32_t width, uint32_t height): ot_status
  • function ot_scene_set_video(ot_context *, const ot_handle *node, uint32_t fit, const ot_handle *buffer): ot_status
  • function ot_session_drop_output(ot_context *context, const ot_handle *session): ot_status
  • function ot_session_post_stdout(ot_context *context, const ot_handle *session, uint32_t max_bytes, uint32_t wait, uint32_t *out_state, uint32_t *out_written): ot_status
  • function ot_video_get_error(ot_context *, const ot_handle *node, uint8_t *bytes, uint32_t capacity, uint32_t *out_count): ot_status
  • function ot_video_get_state(ot_context *, const ot_handle *node, ot_video_state *out_state): ot_status
  • function ot_video_open(ot_context *, const ot_handle *node, const uint8_t *path, uint32_t path_length, uint32_t flags, ot_video_info *out_info): ot_status
  • function ot_video_pause(ot_context *, const ot_handle *node): ot_status
  • function ot_video_play(ot_context *, const ot_handle *node): ot_status
  • function ot_video_seek(ot_context *, const ot_handle *node, int64_t time_us): ot_status
  • function ot_video_set_av_sync_offset(ot_context *, const ot_handle *node, int64_t offset_us): ot_status
  • function ot_video_set_loop(ot_context *, const ot_handle *node, uint32_t loop): ot_status
  • function ot_video_set_max_fps(ot_context *, const ot_handle *node, uint32_t fps): ot_status
  • function ot_video_set_muted(ot_context *, const ot_handle *node, uint32_t muted): ot_status
  • function ot_video_set_volume(ot_context *, const ot_handle *node, float volume): ot_status
  • struct ot_video_info
  • struct ot_video_state

Removed

  • field ot_session_renderer_state.reserved: uint32_t, offset 28