OpenTUI 0.8.1
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_stateis 40 bytes instead of 32. Itsreservedfield at offset 28 is nowimage_uploads_pending, anddropped_framesanddropped_frame_bytesfollow 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:
VideoRenderableplays an MP4 file with H.264 video and AAC audio, withplay(),pause(),seek(),loop,volume,maxFps, and callbacks such asonReadyandonEnd. Withaudio: false, it opens no audio device and follows the wall clock. See Video. (#1800, #1884, #1883, #1885, #1887, #1889) - react:
<video>renders aVideoRenderable, andVideoPropstypes its props. See Video. (#1800, #1884) - solid:
<video>renders aVideoRenderable, andVideoPropstypes its props. See Video. (#1800, #1884) - core:
ImageRenderableandVideoRenderabletakecrop: { left, top, width, height }in source pixels, andfitapplies to that region. An invalid crop callsonErrorand keeps the previous crop. See Image and Video. (#1888) - core: The
frame:droppedevent (CliRenderEvents.FRAME_DROPPED) reports a frame that is too large for the output once for each content, with{ bytes, limit }. It replaces theconsole.errorcall on each frame. See Renderer. (#1890) - core: A
textdraw inNativeBufferDrawtakesmaxWidthandellipsis, 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 theot_video_*functions add a video node to a scene.ot_scene_set_cropsets the source region of a video or image node, and theOT_VIDEO_OPEN_NO_AUDIOflag ofot_video_openopens no audio device. See C. (#1800, #1884, #1888) - native:
ot_session_post_stdoutgives queued output to the stdout writer thread of the Session and reportsOT_STDOUT_IDLEorOT_STDOUT_IN_FLIGHT.ot_session_drop_outputdrops 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_widthfield and theOT_BUFFER_DRAW_FIT_ELLIPSISflag. 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=0to 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()returnsfalseuntil'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.dirfills 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 adestroylistener or a SolidonCleanupcan clear a record at exit. On a failed session, it returnsfalseinstead of throwing. See Program status. (#1871) - core:
TextTableRenderable,SelectRenderable, andTabSelectRenderabledraw into the frame unlessbufferedistrue, 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:
LineNumberRenderabledraws 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
TimeToFirstDrawRenderablelabel 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:
NativeImagedecode, crop, resize, and composite examine alpha 64 bytes at a time. In the crop benchmark,extracttakes 10.6 M instructions instead of 56.2 M. See NativeImage. (#1880) - keymap:
useBindingsin 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 nostateevent. See Solid keymap integration and React keymap integration. (#1785) - native:
OT_LAYOUT_PAINTof 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
selectiononly while it auto-scrolls, so rebuilds no longer leak listeners. See ScrollBox and Code. (#1863) - core: After a
syntaxStyleorfgchange,MarkdownRenderablerecolors 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 afterstreaming = false. See Markdown. (#1857) - core:
TabSelectRenderablecuts 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,
Uint8Arraywrites 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 Renderableconstructor VideoRenderable(ctx: RenderContext, options: VideoRenderableOptions)enum-member CliRenderEvents.FRAME_DROPPED = "frame:dropped"enum-member NativeStatus.VideoFailed = -60function checkCrop(crop: ExtractOptions, width?: number, height?: number): ExtractOptionsfunction sameCrop(a: ExtractOptions | undefined, b: ExtractOptions | undefined): booleaninterface CliRendererFrameDroppedEventinterface NativeVideoInfointerface NativeVideoStateinterface VideoMetadatainterface VideoRenderableOptions extends RenderableOptions<VideoRenderable>method FFIRenderLib.sceneSetCrop(context: NativeContextHandle, node: SceneNodeHandle, crop: ExtractOptions | undefined): voidmethod FFIRenderLib.sceneSetVideo(context: NativeContextHandle, node: SceneNodeHandle, fit: "fit" | "cover" | "fill", buffer: ContextBufferHandle | null): voidmethod FFIRenderLib.sessionDropOutput(context: NativeContextHandle, session: SessionHandle): voidmethod 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"): voidmethod FFIRenderLib.videoGetError(context: NativeContextHandle, node: SceneNodeHandle): stringmethod FFIRenderLib.videoGetState(context: NativeContextHandle, node: SceneNodeHandle): NativeVideoStatemethod FFIRenderLib.videoOpen(context: NativeContextHandle, node: SceneNodeHandle, path: string, audio: boolean): NativeVideoInfomethod FFIRenderLib.videoSeek(context: NativeContextHandle, node: SceneNodeHandle, timeUs: bigint): voidmethod FFIRenderLib.videoSetAvSyncOffset(context: NativeContextHandle, node: SceneNodeHandle, offsetUs: bigint): voidmethod FFIRenderLib.videoSetLoop(context: NativeContextHandle, node: SceneNodeHandle, loop: boolean): voidmethod FFIRenderLib.videoSetMaxFps(context: NativeContextHandle, node: SceneNodeHandle, fps: number): voidmethod FFIRenderLib.videoSetMuted(context: NativeContextHandle, node: SceneNodeHandle, muted: boolean): voidmethod FFIRenderLib.videoSetVolume(context: NativeContextHandle, node: SceneNodeHandle, volume: number): voidproperty ImageRenderable.crop: ExtractOptions | undefinedproperty ImageRenderableOptions.crop?: ExtractOptionsreadonly property NativeSession.open: booleanproperty NativeSessionRendererState.droppedFrameBytes: numberproperty NativeSessionRendererState.droppedFrames: numberproperty 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: VideoPropstype 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 = 16const OT_SCENE_VIDEO = 9const OT_STDOUT_IDLE = 0const OT_STDOUT_IN_FLIGHT = 1const OT_VIDEO_AUDIO = 8const OT_VIDEO_BUFFERING = 4const OT_VIDEO_ENDED = 2const OT_VIDEO_ERROR = 32const OT_VIDEO_FAILED = -60const OT_VIDEO_HAS_FRAME = 16const OT_VIDEO_OPEN_NO_AUDIO = 1const OT_VIDEO_PLAYING = 1field ot_buffer_draw_text_record.max_width: uint32_t, offset 44field ot_session_renderer_state.dropped_frame_bytes: uint32_t, offset 36field ot_session_renderer_state.dropped_frames: uint32_t, offset 32field ot_session_renderer_state.image_uploads_pending: uint32_t, offset 28function ot_scene_set_crop(ot_context *, const ot_handle *node, uint32_t left, uint32_t top, uint32_t width, uint32_t height): ot_statusfunction ot_scene_set_video(ot_context *, const ot_handle *node, uint32_t fit, const ot_handle *buffer): ot_statusfunction ot_session_drop_output(ot_context *context, const ot_handle *session): ot_statusfunction 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_statusfunction ot_video_get_error(ot_context *, const ot_handle *node, uint8_t *bytes, uint32_t capacity, uint32_t *out_count): ot_statusfunction ot_video_get_state(ot_context *, const ot_handle *node, ot_video_state *out_state): ot_statusfunction 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_statusfunction ot_video_pause(ot_context *, const ot_handle *node): ot_statusfunction ot_video_play(ot_context *, const ot_handle *node): ot_statusfunction ot_video_seek(ot_context *, const ot_handle *node, int64_t time_us): ot_statusfunction ot_video_set_av_sync_offset(ot_context *, const ot_handle *node, int64_t offset_us): ot_statusfunction ot_video_set_loop(ot_context *, const ot_handle *node, uint32_t loop): ot_statusfunction ot_video_set_max_fps(ot_context *, const ot_handle *node, uint32_t fps): ot_statusfunction ot_video_set_muted(ot_context *, const ot_handle *node, uint32_t muted): ot_statusfunction ot_video_set_volume(ot_context *, const ot_handle *node, float volume): ot_statusstruct ot_video_infostruct ot_video_state
Removed
field ot_session_renderer_state.reserved: uint32_t, offset 28