OpenPlayer plugins are currently package-based extensions. A user-facing
plugin ships as a .opplugin file, which is a zip archive with a root
manifest.json. The manifest can contribute themes, capability metadata,
validated settings, UI-slot controls, and an optional sandboxed JavaScript worker
runtime.
The manifest format is intentionally forward-compatible with future sandboxed
third-party code runtimes. webviewJs is supported through an isolated Web
Worker bridge. wasm remains reserved until a separate WASM sandbox is
implemented.
Official plugin source code and release packages live in the separate
openplayer-plugins repository.
This player repository owns the host API, manifest behavior, and security
model. Keep official plugin implementations in openplayer-plugins; keep only
small host test fixtures in this repository when integration tests need a
manifest.
For current SDK 1.6 runtime usage, examples, permissions, events, mpv controls,
custom views, and verification commands, read
sdk-1.6-developer-guide.md.
Development native extensions are documented in
Native modules. They run as separately confirmed, trusted
processes, not inside the JavaScript sandbox. The generic control protocol is
implemented. Development Windows x64 builds also provide a separately permissioned
native video adapters; this is not in released 1.6.3.
The development present-rgba-v1 CPU RGBA presenter has real-host harness evidence
for generation, paused OSD redraw, window transitions, restoration and exit.
See the verification scope: NR
composition, long-duration A/V, real React UI and user image-quality acceptance
remain unverified; refreshPaused and paused resize were not independently tested.
This is not a completed installable XeFG product.
- Install
.oppluginpackages from the Plugins settings page. - Drag
.oppluginpackages into the player window to install them. - Import a plugin folder or raw JSON manifest for local development.
- Uninstall plugins and remove their managed files, settings, themes, and enablement state.
- Enable or disable imported plugins.
- Persist plugin manifests and plugin setting values in redb.
- Render plugin settings in the central Plugins settings page.
- Let plugins create session-local host-managed task snapshots for long-running
transcription, translation, analysis, and batch work with
openplayer.tasks. - Let plugins write host-visible diagnostics into the plugin runtime log panel
with
openplayer.log.info,openplayer.log.warn, andopenplayer.log.error. - Render settings assigned to the subtitle/track panel in that panel.
- Render plugin actions in UI slots such as the control strip, context menu, and playlist actions.
- Apply whitelisted subtitle-related mpv properties from plugin settings:
sub-font,sub-font-size,sub-scale,sub-pos,sub-color,sub-spacing,sub-outline-size,sub-border-size, andsub-shadow-offset. - Let declarative actions call safe built-in capability APIs:
player.captureScreenshot,player.openStream, andplayer.openStreamDialog. - Let capture plugins start and stop lightweight mpv native stream recording through permissioned host commands.
- Let advanced plugins use permissioned, allowlisted mpv core controls for safe properties, commands, OSD text, script messages, and plugin-scoped video filters.
- Execute optional
webviewJsruntime scripts in a Web Worker sandbox with no DOM, Tauri API, local filesystem access, or direct host privileges. - Send playback and media lifecycle events to runtime plugins.
- Send playlist and recording lifecycle events to runtime and view plugins so queue browsers and capture panels can refresh without polling.
- Let runtime plugins participate in
media.openingand return safe mpvloadfileoptions such as HLS demuxer hints before playback starts. - Let runtime and view plugins ask the host for normalized current playback
windows with
openplayer.media.currentSegment, then export short managed WAV clips from those windows withopenplayer.audio.extractClip. - Let runtime and view plugins split loaded media into bounded overlapping
chunks with
openplayer.media.segmentTimelinefor batch transcription, analysis, or summarization. - Let runtime and view plugins export bounded audio or video segments with
openplayer.media.exportSegment; the host validates time ranges, output names, and formats, then writes into the user's Downloads/OpenPlayer/Exports folder without granting plugins arbitrary filesystem write access. - Let runtime and view plugins capture the current video frame as a managed
image artifact with
openplayer.capture.framefor OCR, visual understanding, scene tagging, or summary workflows. - Let runtime and view plugins upload current-plugin managed artifacts through
openplayer.network.request({ bodyFile })without exposing arbitrary local filesystem reads. - Let plugins list, inspect, remove, and clear their own managed audio clips
and frame captures with
openplayer.artifacts, so analysis and provider-backed plugins can clean up batch artifacts without raw filesystem access. - Let runtime and view plugins call JSON provider APIs with
openplayer.network.requestJson, a convenience wrapper over the same host-mediatednetwork.requestpermission and validation path. - Let cache-heavy plugins call
openplayer.storage.list({ prefix, limit })and inspectopenplayer.storage.info().totalBytes/maxValueBytes, while still keeping all plugin data private to the host-managed redb store. - Let runtime and view plugins create subtitle documents from structured
SubtitleCue[]input or standard subtitle text withopenplayer.subtitle.documents.create; the host writes results into a plugin-scoped managed directory and loads them as mpv subtitle tracks. - Let runtime and view plugins read only the selected subtitle track's currently
displayed cue with
openplayer.subtitle.currentCue, so translation and cleanup plugins can compose read, network, and generated subtitle write APIs without arbitrary subtitle file access. - Let runtime and view plugins update subtitle presentation with
openplayer.subtitle.setStylethrough the same safempv.subtitleStyleallowlist used by manifest-backed subtitle settings. - Let plugins manage only their own generated subtitle documents through
openplayer.subtitle.documents.create,list,read,replace,appendCues, andremove; the olderloadGenerated*and*Generated*helpers remain compatibility aliases over the same managed subtitle files. - Keep provider-backed media workflows plugin-composed: use media segments, audio extraction, frame capture, subtitle read/write, network requests, tasks, storage, and logs as reusable primitives instead of adding one-off core toggles for transcription, translation, OCR, or a specific provider.
OpenPlayer host APIs should expose durable media-player primitives, not provider-shaped feature APIs. A transcription, translation, subtitle cleanup, or OCR plugin should be an orchestration of these blocks:
- playback context:
openplayer.media.currentSegment,openplayer.media.segmentTimeline,openplayer.media.current, andopenplayer.media.snapshot; - media artifacts:
openplayer.audio.extractClip,openplayer.capture.frame,openplayer.media.exportSegment, andopenplayer.artifactslifecycle helpers; - provider I/O:
openplayer.network.requestandopenplayer.network.requestJson; - subtitle documents:
openplayer.subtitle.currentCue,openplayer.subtitle.documents.create,openplayer.subtitle.documents.appendCues,openplayer.subtitle.documents.replace,openplayer.subtitle.documents.read, andopenplayer.subtitle.documents.remove; - runtime state:
openplayer.tasks,openplayer.storage, andopenplayer.log.
Do not add ai.*, transcription.*, translation.*, or provider-specific host
permissions for one plugin. If a plugin cannot be built from the existing
blocks, add the missing generic block, such as a safer artifact lifecycle API,
subtitle document operation, audio extraction option, or event bridge.
Manifest capability kinds are broad UI/discovery categories such as
audioTool or subtitleTool; they are not permission gates and should not
become per-provider taxonomy.
.opplugin is a zip archive:
subtitle-styler.opplugin
├── manifest.json
└── assets/
└── ...
Rules:
manifest.jsonmust exist at the archive root.- All archive entries must be relative package paths.
- Path traversal and symlinks are rejected.
- Packages are extracted to OpenPlayer's managed app-data plugin directory.
- The current package limit is 1024 entries and 128 MiB uncompressed.
During development, a directory with the same layout can be imported directly from the Plugins settings page.
{
"id": "dev.openplayer.subtitle.styler",
"name": "Subtitle Styler",
"version": "1.0.0",
"apiVersion": "1",
"minHostVersion": "1.6.0",
"author": "OpenPlayer Team",
"updateUrl": "https://github.com/AreChen/openplayer-plugins/releases",
"description": "Subtitle typography controls for OpenPlayer.",
"entry": "manifest",
"runtime": {
"kind": "manifest"
},
"contributes": {
"capabilities": [
{
"id": "subtitle-style",
"name": "Subtitle Styling",
"kind": "subtitleStyle",
"description": "Controls allowed subtitle mpv properties.",
"permissions": ["mpv.subtitleStyle"]
}
],
"settings": [
{
"id": "font-size",
"label": "Font Size",
"description": "Subtitle font size in screen-scaled points.",
"labelI18n": {
"zh-CN": "字号"
},
"descriptionI18n": {
"zh-CN": "调整字幕基础字号。"
},
"kind": "number",
"placement": "subtitleSettings",
"defaultValue": 42,
"min": 12,
"max": 96,
"step": 1,
"mpvProperty": "sub-font-size"
},
{
"id": "font-family",
"label": "Font Family",
"kind": "text",
"placement": "subtitleSettings",
"defaultValue": "sans-serif",
"mpvProperty": "sub-font"
}
],
"actions": [
{
"id": "toggle-tracks",
"label": "Tracks",
"description": "Open the track and subtitle panel.",
"placement": "controls.right",
"command": "player.toggleTracks",
"icon": "tracks",
"requiresMedia": true
},
{
"id": "open-settings",
"label": "Settings",
"placement": "contextMenu",
"command": "app.openSettings",
"icon": "settings"
},
{
"id": "screenshot",
"label": "Screenshot",
"placement": "controls.right",
"command": "player.captureScreenshot",
"icon": "camera",
"requiresMedia": true,
"args": {
"openFolder": true
}
},
{
"id": "open-stream",
"label": "Open Stream",
"placement": "playlist.actions",
"command": "player.openStream",
"icon": "stream",
"args": {
"url": "https://example.com/live.m3u8",
"name": "Live Stream"
}
}
]
}
}Theme plugins remain supported:
{
"id": "dev.openplayer.theme.ocean",
"name": "Ocean Theme Pack",
"version": "1.0.0",
"apiVersion": "1",
"minHostVersion": "1.6.0",
"author": "OpenPlayer Team",
"updateUrl": "https://github.com/AreChen/openplayer-plugins/releases",
"description": "Ocean themes for OpenPlayer.",
"entry": "manifest",
"contributes": {
"themes": [
{
"id": "dev.openplayer.theme.ocean.dark",
"name": "Ocean Dark",
"version": "1.0.0",
"tokens": {
"surface": "#050607",
"panel": "rgba(8, 10, 12, 0.72)",
"panelStrong": "rgba(8, 10, 12, 0.88)",
"text": "#ece7dd",
"muted": "#b9b0a3",
"faint": "#8f867a",
"accent": "#62c7b7",
"danger": "#d78372",
"line": "rgba(236, 231, 221, 0.12)",
"control": "rgba(18, 21, 25, 0.72)",
"scrollbarThumb": "rgba(236, 231, 221, 0.22)",
"scrollbarThumbHover": "rgba(98, 199, 183, 0.46)"
}
}
]
}
}Current supported runtime:
manifest- declarative only, no third-party code execution.webviewJs- executes the package entry script in an isolated Web Worker.
Reserved runtime kinds:
wasm- future sandboxed WebAssembly runtime.
webviewJs requires:
"runtime": {
"kind": "webviewJs",
"entry": "dist/plugin.js",
"sandbox": "openplayer-worker",
"events": ["media.loaded", "playback.snapshot"]
}The runtime entry must be a relative path inside the installed plugin package.
The current script size limit is 1 MiB.
events is optional, but runtime plugins only receive non-lifecycle event
payloads that they declare here. Plugins can then enable or disable those
declared subscriptions at runtime with openplayer.events.subscribe() and
openplayer.events.unsubscribe().
Queue-aware views can declare playlist.changed; capture views can declare
recording.changed. These events follow the same manifest declaration and
runtime subscription rules as playback snapshot events.
Custom views use the same event contract through window.openplayer. A
view-only plugin should still declare the events it needs in runtime.events;
the host rejects view subscriptions to undeclared events. This keeps rich plugin
panels reactive without giving every iframe every playback event by default.
Inside the worker, plugins use the injected openplayer bridge:
openplayer.onReady(async () => {
if (openplayer.capabilities.has("mpv.wall")) {
console.log(`OpenPlayer ${openplayer.host.version} supports native wall tiles`);
console.log(`SDK compatibility: ${openplayer.api.compatibility.compatibility}`);
}
const launchCount = (await openplayer.storage.get("runtime.launchCount")) ?? 0;
await openplayer.storage.set("runtime.launchCount", Number(launchCount) + 1);
await openplayer.events.subscribe("playback.snapshot");
});
openplayer.onEvent((event, payload) => {
if (event === "playback.snapshot") {
console.log(payload.position, payload.duration);
}
});
openplayer.media.onBeforeOpen((media) => {
if (/^https?:\/\//i.test(media.path) && /\.m3u8(?:[?#]|$)/i.test(media.path)) {
return {
loadOptions: {
demuxer: "+lavf",
"demuxer-lavf-format": "hls"
}
};
}
return null;
});
openplayer.commands.register("plugin.open-network-stream", async () => {
await openplayer.media.openStreamDialog();
});SDK 1.6.0 exposes openplayer.host, openplayer.api, and
openplayer.capabilities in both worker runtimes and custom views.
host.version identifies the running OpenPlayer build.
api.compatibility gives plugins a stable compatibility block for feature
gating. capabilities.list() reports host-supported API families, while
capabilities.permissions() reports the permissions granted to the current
plugin from its manifest.
Supported worker bridge requests match the built-in action command allowlist.
Permissioned requests such as player.captureScreenshot and player.openStream
are rejected unless the plugin manifest declares the required permission.
Low-risk requests include plugin.getSettings, plugin.storage.*,
player.currentMedia, player.snapshot, player.play, player.pause,
player.seek, player.frameStep, player.frameBackStep, player.setVolume,
player.setSpeed, player.setLoopMode, player.setVideoFill,
player.setSubtitleDelay, player.selectTrack, ui.*, playlist.current,
playlist.playIndex, and playlist.clear.
network.request requires network.request. filesystem.pickMedia,
filesystem.pickDirectory, playlist.openMediaFiles,
playlist.appendMediaFiles, and subtitle.pickExternal require
filesystem.pick. filesystem.revealPath and filesystem.openDirectory
require filesystem.reveal.
player.wall.open, player.wall.layout, player.wall.snapshot,
player.wall.setVisible, and player.wall.close require mpv.wall and are
intended for native multi-stream views that need protocols the WebView cannot
decode directly, such as RTSP and RTMP. layout accepts viewport-relative tile
rectangles and is safe to call from debounced or throttled resize handlers.
setVisible(false) temporarily hides the native wall so plugin-owned dialogs can
receive pointer input above native video child windows.
Wall snapshots report native buffer and bitrate telemetry, plus
transportLatencyMs/transportLatencySource when the host can bind the
displayed frame to transport timing.
player.wall.open accepts optional per-tile playback tuning for native RTSP
tiles. Runtime plugins can choose latencyMode (off, stable, balanced,
or aggressive), rtspTransport (tcp or udp), and a bounded bufferMs
target; the host validates these values before applying mpv options.
WebRTC/WHEP plugins should render browser video tiles inside their custom view
and use network.request for WHEP HTTP signaling when they need host-mediated
requests.
openplayer.mpv exposes a broader but still allowlisted mpv core surface.
getProperty, setProperty, and generic safe commands require mpv.core.
showText requires mpv.osd; scriptMessage requires mpv.scriptMessage;
filters.add, filters.remove, audioFilters.add, and
audioFilters.remove require mpv.filters. setAbLoop and clearAbLoop
require mpv.core. The backend validates property names, value types, numeric
ranges, allowed commands, and plugin-owned filter labels before sending
anything to libmpv. This API intentionally does not expose raw loadfile,
shell-like commands, arbitrary filter graphs, or unsafe process/configuration
properties.
openplayer.plugin.getSettings() returns the current values declared in
contributes.settings. plugin.storage.get, plugin.storage.set,
plugin.storage.update, plugin.storage.remove, and plugin.storage.list
provide redb-backed plugin-private JSON storage. Storage keys are namespaced by
plugin ID and removed when the plugin is uninstalled. Plugins that persist state
can declare contributes.storage with a positive version and JSON defaults;
the host initializes missing defaults on install, preserves existing values on
upgrade, and exposes schema metadata through openplayer.storage.info() so
plugins can run their own migrations and call
openplayer.storage.markMigrated() without adding core feature toggles. Use
plugin.storage.update for atomic { set, remove } batches during migrations,
queue updates, and cache index refreshes. plugin.storage.list accepts
optional prefix and limit arguments for bounded cache scans, and
plugin.storage.info reports byte usage and per-value limits.
player.openStreamDialog is available to declarative actions and opens the
host-owned network stream dialog.
Capture requests such as player.startRecording, player.stopRecording,
player.toggleRecording, and player.recordingState require mpv.capture.
Changing path from onBeforeOpenMedia requires media.openStream.
Returning loadOptions from onBeforeOpenMedia requires mpv.loadOptions.
Only demuxer and demuxer-lavf-format are accepted at this stage.
Declarative actions may also use plugin-owned commands with the plugin.*
prefix, such as plugin.open-network-stream. Those actions are routed to the
same plugin's webviewJs runtime through openplayer.registerCommand.
Runtime commands can open plugin-owned custom views with
openplayer.ui.openView("view-id"). Custom views are static HTML files inside
the installed plugin package and receive a smaller openplayer bridge for
settings, plugin storage, host-mediated HTTP(S) requests, toasts, and closing
the view. OpenPlayer injects a restrictive Content Security Policy into custom
views, including connect-src 'none', so HTTP(S) requests should go through
openplayer.network.request instead of direct browser network APIs. View
bridges also expose openplayer.player.wall and openplayer.mpv for plugins
that render a custom control surface above native mpv wall tiles.
For authoring TypeScript plugins, use the @openplayer/plugin-sdk types from
the official openplayer-plugins repository. Runtime plugins do not need to
bundle the SDK; OpenPlayer injects the openplayer global.
contributes.themes adds theme tokens.
contributes.capabilities declares what the plugin wants to enhance. Supported
capability kinds:
subtitleStylecapturestreamSourceaudioToolsubtitleToolmpvControl
Supported permission declarations:
mpv.subtitleStylempv.loadOptionsmpv.capturempv.wallmpv.corempv.filtersmpv.osdmpv.scriptMessagemedia.openStreammedia.exportfilesystem.pickfilesystem.revealnetwork.requestaudio.extractsubtitle.readsubtitle.write
contributes.settings adds validated controls. Supported setting kinds:
booleannumbertextselectcolordirectory
Setting, action, and capability display text may include optional locale maps
next to their default English strings. OpenPlayer currently resolves zh-CN,
language-only fallbacks such as zh, then en-US/en before using the default:
{
"label": "Font Size",
"description": "Base subtitle font size.",
"labelI18n": {
"zh-CN": "字号"
},
"descriptionI18n": {
"zh-CN": "调整字幕基础字号。"
}
}Supported UI placements:
pluginSettingssubtitleSettingscaptureSettingsstreamSettingscontrols.leftcontrols.centercontrols.rightcontextMenuoverlay.statusplaylist.actions
The current UI renders pluginSettings centrally and renders
subtitleSettings inside the track/subtitle panel. Other placements are accepted
as stable manifest slots for future UI rendering.
contributes.actions adds declarative buttons or menu items. Actions can invoke
a small allowlist of built-in OpenPlayer commands, or a plugin-owned
plugin.* command registered by the same webviewJs runtime.
contributes.views adds static HTML plugin views:
{
"id": "wall",
"title": "Multi-Stream Wall",
"titleI18n": {
"zh-CN": "多路流媒体墙"
},
"presentation": "sidePanel",
"frameOpacitySetting": "panel-opacity",
"entry": "view/index.html"
}The entry must be a safe relative package path. OpenPlayer reads the HTML from
the installed plugin package and mounts it in an iframe with an injected bridge.
The default presentation is overlay; use sidePanel for right-side panels.
frameOpacitySetting is optional and must reference a number setting in
contributes.settings; OpenPlayer applies that setting as host-level iframe
opacity for user-tunable translucent side panels.
Custom view documents receive theme tokens and reusable UI classes from the
host. Use .op-view, .op-surface, .op-button, .op-button--primary,
.op-icon-button, .op-input, .op-select, .op-list-item, .op-badge, and
.op-muted for controls that should follow theme plugins and user accent
overrides without plugin-local duplicated CSS. Use .op-toolbar, .op-spacer,
.op-field, .op-label, .op-help, .op-divider, .op-tabs, .op-tab,
.op-progress, and .op-empty for reusable plugin layout, form, navigation,
progress, and empty-state UI.
Supported action placements:
controls.leftcontrols.centercontrols.rightcontextMenuoverlay.statusplaylist.actions
The current UI renders action placements for controls.left, controls.center,
controls.right, contextMenu, and playlist.actions.
Supported action commands:
player.openMediaplayer.openStreamplayer.openStreamDialogplayer.captureScreenshotplayer.startRecordingplayer.stopRecordingplayer.toggleRecordingplayer.togglePlaybackplayer.stopplayer.restartplayer.togglePlaylistplayer.toggleTracksplayer.toggleLoopplayer.toggleSpeedwindow.toggleFullscreenwindow.toggleAlwaysOnTopapp.openSettingsplugin.*
Supported action icons:
folderfolderAddplaypausestoprestartlisttrackssettingsfullscreenpinplugincamerarecordstreamtvinfo
Action arguments:
player.captureScreenshotaccepts optionalargs.format,args.formatSetting,args.directorySetting,args.openFolder, andargs.openFolderSetting. Supported screenshot formats arepng,jpg, andwebp. It requires the plugin to declarempv.capture.player.startRecordingandplayer.toggleRecordingaccept optionalargs.format,args.formatSetting,args.directorySetting,args.openFolder, andargs.openFolderSetting. Supported recording containers aremkv,mp4, andts. Local files and HTTP/HTTPS streams are clipped with mpvdump-cache; live protocols such as RTSP, RTMP, SRT, and UDP use mpvstream-record. Recording uses the requested container but does not transcode media, so codec and muxer compatibility depends on the source. These commands requirempv.capture.player.stopRecordingaccepts optionalargs.openFolderandargs.openFolderSetting. It requiresmpv.capture.player.openStreamrequiresargs.urland accepts optionalargs.name. Supported stream protocols arehttp,https,rtmp,rtmps,rtsp,rtsps,srt, andudp. It requires the plugin to declaremedia.openStream.player.openStreamDialogopens OpenPlayer's network stream dialog with recent RTSP, RTMP, and HTTP(S) streams. It requires the plugin to declaremedia.openStream.plugin.*commands are routed toopenplayer.registerCommandin the same plugin runtime. The actionargsobject is passed through unchanged after a small size check.
- Plugin IDs must be dotted lowercase identifiers.
- Versions must use
major.minor.patch. apiVersiondefaults to1; unknown plugin API versions are rejected.minHostVersion, when present, must usemajor.minor.patchand cannot be newer than the running OpenPlayer version.author, when present, must be non-empty.updateUrl, when present, must be an HTTP(S) URL.- A plugin must contribute at least one theme, capability, setting, action, view, or storage schema.
- Unknown manifest fields are rejected.
- Setting defaults must match their declared type.
- Locale maps such as
labelI18nmust use short ASCII locale keys and non-empty text values. - Numeric settings must respect
min,max, andstep. - Select settings must define non-empty unique options.
mpvPropertyis only accepted forsubtitleSettingsand only for the whitelisted subtitle properties listed above.- Actions are rejected unless their placement, command, and icon are on the documented allowlists.
- Capability actions are rejected unless their required permissions are declared.
- Stream actions are rejected for local file URLs, whitespace, unsupported protocols, or missing URLs.
.oppluginpackages are rejected unless they contain a rootmanifest.jsonand all entries stay inside the package.