IINA Plugin and mpv Scripting Mastery
IINA Plugin Architecture
The Swift-JavaScript Bridge
IINA is a native macOS application written in Swift, but at its heart, it uses the powerful media engine for playback. While mpv supports its own scripts, typically written in Lua, IINA needed a way for extensions to interact not just with the playback engine, but with the custom user interface and macOS system features. The solution was a custom plugin architecture built on JavaScript.
This creates a distinction between standard mpv scripts and IINA plugins. A script talks directly to the mpv core and has no awareness of the IINA interface. An IINA plugin, however, operates in a JavaScript environment that communicates with IINA's native Swift code. This communication channel, known as a bridge, allows a plugin to open UI elements, modify menus, and listen for IINA-specific events—actions far beyond the scope of a simple mpv script.
Anatomy of a Plugin
IINA plugins are not single files but are packaged as bundles with a .iinaplugin extension. macOS treats these bundles like applications, allowing them to contain multiple resources. All user-installed plugins are stored in a specific directory within your user Library folder:
~/Library/Application Support/iina/plugins/
Inside each .iinaplugin bundle is a standardized structure. The most critical files are the manifest, Info.plist, and the main script file, which is typically main.js.
ExamplePlugin.iinaplugin/
└── Contents/
├── Info.plist
└── Resources/
└── main.js
The file is an XML-based property list that contains metadata about the plugin. It tells IINA the plugin's name, a unique identifier, its version, and the name of the main script file to execute. This manifest is the first thing IINA reads to understand and load the plugin.
The Global 'iina' Object
Once a plugin is loaded, IINA injects a global object named iina into the JavaScript context. This object is the sole entry point for interacting with the player's API. It exposes all the functions and properties needed to control playback, listen for events, and manipulate the user interface.
Think of the
iinaobject as the plugin's remote control for the entire application. Every action, from pausing the video to displaying a notification, is done through a method on this object.
For example, to observe when a file is loaded, you would use the event listener. The following snippet registers a callback function that logs the new file's path to the IINA console whenever a new media file is opened.
// Listen for the 'file-loaded' event
iina.event.on('file-loaded', (event) => {
// Get the path from the event data
const path = iina.properties.path;
// Log it to the IINA developer console
console.log(`File loaded: ${path}`);
});
This event-driven model is central to the plugin system. Plugins register listeners for specific events and react when IINA broadcasts that an event has occurred. This keeps the plugin dormant until it's needed, making the system efficient.
Security and Permissions
Because plugins are written in JavaScript and can interact with the host system, they run within a security sandbox. This is a restricted environment that limits what a plugin can do, preventing it from accessing arbitrary files on your computer or performing malicious actions. The bridge between Swift and JavaScript carefully exposes only the necessary IINA functions, not the entire filesystem or operating system APIs.
This sandboxing is a critical feature. It allows users to safely install third-party plugins without granting them unrestricted access to their machine. While the iina object provides powerful control over the player, its reach is intentionally limited to the context of the application itself.