Native 格式插件开发指南
Native 插件使用 Rust 语言编写,编译为动态链接库(.dll, .so, .dylib)。它是性能最强、功能最完整的插件类型,专门用于处理复杂的音频格式(如加密格式)。
注意: Native 插件具有完全的系统访问权限,开发和使用时需谨慎。
1. 快速开始
1.1 项目结构
创建一个新的 Rust 库项目:
cargo new --lib my-format-plugin编辑 Cargo.toml:
[package]
name = "my-format-plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # 必须是动态库
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 其他依赖...提供插件配置文件 plugin.json(详情请参考 插件开发指南)。
1.2 核心代码 (src/lib.rs)
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use serde_json::Value;
// 1. 核心入口 plugin_invoke (必须!)
#[no_mangle]
pub unsafe extern "C" fn plugin_invoke(
method: *const u8,
params: *const u8,
result_ptr: *mut *mut u8,
) -> c_int {
let method_str = CStr::from_ptr(method as *const i8).to_str().unwrap();
let params_str = CStr::from_ptr(params as *const i8).to_str().unwrap();
let params_json: Value = serde_json::from_str(params_str).unwrap();
let result = match method_str {
"detect" => detect(params_json),
"extract_metadata" => extract_metadata(params_json),
"write_metadata" => write_metadata(params_json),
"decrypt" => decrypt(params_json),
"get_stream_url" => get_stream_url(params_json),
// ... 其他方法
_ => Err("Unknown method".to_string()),
};
match result {
Ok(val) => {
let json = serde_json::to_string(&val).unwrap();
let c_string = CString::new(json).unwrap();
*result_ptr = c_string.into_raw() as *mut u8;
0 // 成功
}
Err(e) => -1 // 失败
}
}
// 2. 核心方法实现
fn detect(params: Value) -> Result<Value, String> {
let path = params["file_path"].as_str().ok_or("Missing path")?;
// 读取文件头,判断是否支持
let is_supported = check_magic_header(path);
Ok(serde_json::json!({ "is_supported": is_supported }))
}
fn extract_metadata(params: Value) -> Result<Value, String> {
// 读取元数据...
Ok(serde_json::json!({ "title": "...", "artist": "..." }))
}
fn write_metadata(params: Value) -> Result<Value, String> {
let path = params["file_path"].as_str().ok_or("Missing path")?;
// params 包含: title, artist, album, genre, description, cover_path
// 更新元数据...
Ok(serde_json::json!({ "status": "success" }))
}
fn decrypt(params: Value) -> Result<Value, String> {
// 解密文件...
Ok(serde_json::json!({ "status": "success" }))
}
// 3. 内存释放导出 (必须!)
#[no_mangle]
pub unsafe extern "C" fn plugin_free(ptr: *mut u8) {
if !ptr.is_null() {
let _ = CString::from_raw(ptr as *mut i8);
}
}1.3 编译
cargo build --release编译产物位于 target/release/ 目录下(Windows 为 .dll,Linux 为 .so,macOS 为 .dylib)。
2. 部署
将编译好的动态库文件和 plugin.json 放入 plugins/my-format-plugin/ 目录。
注意:Native 插件必须与宿主程序的操作系统和架构匹配。
3. 高级功能:流式解密
为了支持大文件播放,建议实现 get_decryption_plan 和 decrypt_chunk 方法,允许播放器按需解密文件的特定部分,而不是一次性解密整个文件。
3.1 解密计划 (Decryption Plan) 规范
- 对于
type: "encrypted"的段,length必须是该段在物理文件中的真实字节长度。后端会主动读取这部分字节并调用decrypt_chunk解密,再自动测量解密后的逻辑长度。 - 即使解密后数据长度与物理长度不同(例如去掉填充),也不需要在
length中预测解密后长度,直接填物理长度即可。后端会通过预解密机制自动修正逻辑偏移,支持浏览器任意Range请求。
如果插件解密后的数据大小与原始加密段大小不同,建议在 DecryptionPlan 中提供 total_size 字段(整个文件的最终逻辑大小)。如果未提供,后端将根据各段逻辑长度自动计算。
fn get_decryption_plan(params: Value) -> Result<Value, String> {
// 返回文件的加密段和明文段分布
Ok(serde_json::json!({
"segments": [
// offset 和 length 必须是物理文件中的真实偏移和真实长度!
{ "type": "encrypted", "offset": 1024, "length": 5000 },
{ "type": "plain", "offset": 6024, "length": -1 }
],
"total_size": 123456 // 可选:解密后的总大小(字节)
}))
}4. 转码支持 (可选)
如果你的插件不需要 FFmpeg 转码(即可以直接输出 PCM/WAV/AAC 流),或者需要明确告知宿主程序不支持某些转码操作,请实现 get_stream_url 方法。
对于不需要转码支持的插件(例如自身负责解码的 Native 插件),应返回空对象以避免后端日志报错:
fn get_stream_url(_params: Value) -> Result<Value, String> {
// 返回空对象表示不提供特殊的转码命令,后端将回退到默认处理逻辑(如 Standard Stream)
Ok(serde_json::json!({}))
}并在 plugin_invoke 中注册该方法:
match method_str {
// ...
"get_stream_url" => get_stream_url(params_json),
// ...
}