快速开始
运行你的第一个插件
照片流把可选扩展包称为“组件”,网站中沿用更熟悉的“插件”。插件不能导入照片流 React 渲染层或 Electron 主进程代码,只能通过公开桥接、组件自有 RPC 和清单授权的 Host 能力协作。
清单、页面示例和 examples/hello-component/service.cjs 均使用 sample.media-page.v1;先完整复制示例,再添加自己的 RPC。
- 1复制示例
从
examples/hello-component开始;面板、项目读写和声明式设置另有独立最小示例。 - 2选择需要的接口
从 API 参考中选择功能,例如用
project.media.page读取媒体列表,并查阅 SDK 中的请求参数。 - 3选择宿主入口
有页面的插件声明工具栏或面板入口,并引用包内
component.fullPage。只提供文件预览解码器的插件可使用空的 contributions;只提供普通设置表单的插件可以省略工具入口和服务。 - 4填写权限和方法清单
逐项列出 RPC、Host 能力、权限和事件。声明能力不会自动获得权限,每次调用都会复核。
- 5独立验证示例服务
在照片流源码根目录运行
node scripts/mock-component-service.cjs examples/hello-component/service.cjs。该 mock 专门验证示例 RPC;自定义方法需要配套修改测试。
sample.media-page.v1;服务通过 project.media.page 等 Host API 访问项目资源。插件结构
准备插件文件和清单
hello-component/
component.json
service.cjs
ui/index.html
ui/icon.svg # 可选,只允许 PNG 或被动 SVG清单路径都是包内相对路径。路径穿越、URL、UNC、目录链接、文件符号链接、主动 SVG、缺失文件和未知字段都会失败。宿主不会导入插件 Electron/React 模块,插件也不能选择自己的 preload。
最小组件清单
{
"apiVersion": 1,
"id": "hello-component",
"version": "1.0.0",
"componentHost": {
"contractVersion": 2,
"contributions": [
{
"type": "workspace.toolbarAction",
"id": "open",
"label": "示例插件",
"pageId": "main"
},
{
"type": "component.fullPage",
"id": "main",
"title": "示例插件",
"entry": "ui/index.html"
}
],
"service": {
"protocolVersion": 1,
"runtime": "node",
"entrypoints": { "default": "service.cjs" },
"rpcMethods": ["sample.media-page.v1"],
"capabilities": ["project.media.page"],
"permissions": ["project.media.read"],
"events": []
}
}
}此清单与上述示例服务配套,只请求媒体分页权限。UI 可直接使用 window.photoFlowComponent;若使用 SDK JavaScript 或 CSS,应复制或打包进组件自身目录,不可依赖包外的 ../../component-sdk 路径。
component-sdk/index.d.ts 提供 Host API 请求、结果、错误、上下文和事件映射。
服务使用 JSON Lines 交换请求和结果。stdout 用于协议通信,运行日志写入 stderr。
清单显式声明支持平台、CPU 架构、必需文件、入口和运行时;安装前统一校验完整性。
宿主会拒绝未声明的方法和权限,以及无法识别的入口、字段或路径。
页面入口
添加工具栏、面板和右键菜单入口
workspace.toolbarAction在项目工作区提供全页入口,并接收宿主绑定的安全项目上下文。
component.sidePanel可作为浮动工具面板,或通过 workspace.folderPanel 加入常驻面板。不同文件页使用各自的实例。
media.contextAction从媒体右键菜单打开,使用该文件页提供的访问范围和选择项。
project.contextAction在文件页工具栏或右键菜单提供操作,接收当前选择的文件和文件夹。
project.importProvider提供受控项目导入入口,输入与写入仍通过 Host 能力完成。
project.exportProvider提供项目导出入口,不直接获得任意文件系统访问。
application.command注册无项目应用命令;仅存在真实命令时,宿主才启用全局命令入口。
侧面板、右键动作、导入/导出和应用命令分别声明 rpcMethods,每个最多 128 个,且必须是 service.rpcMethods 中已有的方法。服务方法总数最多 128 个;设置页及表单 customPage 各最多 32 个。解码等 host-only 方法只给宿主调用,不能开放给普通页面。
灵感库文件页也会提供组件入口。根据 context.contentKind 区分 project 和 inspiration;灵感库没有项目版本树与进度能力,应关闭依赖这些能力的操作。
工具分组可选 workspace.videoTools、workspace.imageTools 和 workspace.officeTools,分别对应视频工具、图片工具和 Office 文档。只有 component.sidePanel 和 project.contextAction 可使用这些位置。项目和灵感库均提供对应入口,插件仍需自行检查选择是否适用。
设置与样式
添加插件设置并统一页面样式
application.settingsForm支持开关、选项、文本、数字和滑块(toggle/select/text/number/range),由照片流显示并保存。
customPage需要账号授权、环境安装或诊断时,可把自定义页面放在普通设置表单旁。
application.settingsPage没有声明式字段、且确实需要完整自定义交互时使用;设置 surface 没有项目身份。
component-sdk/ui.css提供统一的颜色、间距、按钮、表单和对话框样式,便于插件与主程序保持一致。
不需要服务的纯设置插件
只提供普通设置时,可以让照片流直接生成页面,不必编写 HTML 或服务。清单中只放 application.settingsForm,且不要添加 customPage,例如:
{
"apiVersion": 1,
"id": "sample-preferences",
"version": "1.0.0",
"componentHost": {
"contractVersion": 2,
"contributions": [
{
"type": "application.settingsForm",
"id": "settings",
"label": "示例插件",
"form": {
"schemaVersion": 1,
"groups": [
{
"id": "general",
"title": "常用设置",
"fields": [
{
"id": "showHints",
"type": "toggle",
"label": "显示操作提示",
"default": true
}
]
}
]
}
}
]
}
}这个示例不需要 service、component.fullPage 或 workspace.toolbarAction。加入自定义页面或其他交互入口后,仍要按对应要求声明页面、服务和 RPC。
声明许可和播放设置
在 form.notices 中填写 title、description、license、sourceUrl、licenseUrl,可显示插件依赖的许可说明。链接只接受不带登录信息的 HTTPS 地址;只展示说明时可以留空 groups,但 notices 至少有一条。
视频播放后端可通过 preferenceScope:"videoPlayback" 绑定 hdrMode、toneMapping、targetPeakNits。照片流会检查字段、取值和后端能力,普通插件不能借此修改任意应用设置。具体限制见 设置接口说明。
在页面中调用服务
在 ui/index.html 中放置 <pre id="result"></pre>,再通过 type="module" 脚本执行:
const host = window.photoFlowComponent;
const context = await host.getContext();
const page = await host.rpc('sample.media-page.v1', {});
document.querySelector('#result').textContent = JSON.stringify({ context, page }, null, 2);主题可通过包内 SDK 的 mountUiTheme() 同步,页面销毁时取消订阅。通知需额外声明 notifications capability 与 permission;订阅自有事件前也必须在 service.events 列出相同事件名。
host.notify 只接受 tone/message/dedupeKey? 纯文本结构。HTML、URL、路径、命令、回调和 durationMs 都会拒绝;长任务使用 tasks,需要用户决定时使用 dialogs。
面板与预览
添加常驻面板并跟随视频播放
常驻面板适合字幕、文件信息等需要一边浏览一边查看的内容。将下面的入口加入 componentHost.contributions,同时声明 subtitle-ui 页面和 subtitle.load.v1 服务方法:
{
"type": "component.sidePanel",
"id": "subtitles",
"label": "字幕",
"pageId": "subtitle-ui",
"placement": "workspace.folderPanel",
"rpcMethods": ["subtitle.load.v1"]
}用户从“面板”菜单打开它,可以固定、拖动排序和调整宽度。切换目录或选择项后,用 onContextChange 更新内容;面板隐藏时释放订阅,重新显示时读取最新状态。
声明 component.panel 接口及同名权限后,可调用 host.setPanelInfo({title, subtitle}) 更新顶部文字。不要在正文中再添加一层标题栏。
显示播放位置,并从字幕跳转
服务清单声明 project.preview,权限声明 project.preview.read;需要跳转时加上 project.preview.control。页面通过 window.photoFlowComponent 调用:
const host = window.photoFlowComponent;
const snapshot = await host.getPreview();
if (snapshot.video?.canSeek) {
await host.seekPreview(snapshot.video.sessionId, 0);
}用 await host.onPreviewChange(callback) 持续接收状态,返回值是取消订阅函数。每次先检查 video 是否为 null;sessionId 改变时重新匹配内容。time 和 duration 以秒为单位,位置通知约每 250 毫秒一次。
只提供文件解码的插件
在 componentHost.service.previewDecoders 中声明支持的后缀和解码方法,可以不创建页面。宿主传入读取令牌、pageIndex 和 maxEdge;插件解析文件,将指定页转成 PNG,再通过 component.transfer 返回输出令牌。
解码器需要 project.input.tokens、component.transfer 接口及 project.input.read 权限。只返回 {inputToken, mimeType:"image/png", pageIndex, pageCount},不返回 HTML 或路径。大小、页数和权限限制见 预览接口说明。
文件读取
读取自定义文件和传输大文件
图片编辑器、字体工具或文档插件可以用 project.files.inputToken 读取当前范围内的普通文件。清单同时声明 project.files.inputToken、project.input.tokens 两项接口,以及 project.files.read、project.input.read 两项权限。
下面的代码在服务中执行;host 来自 SDK 的 createServiceHostClient,parentId 是正在处理的 RPC 请求 ID:
const file = await host.callHost(parentId, 'project.files.inputToken', {
relativePath: '文档/设计稿.psd'
});
const snapshot = await host.callHost(parentId, 'project.input.tokens', {
action: 'materialize',
token: file.input.token
});
// 服务读取 snapshot.privatePath,不把这个路径发送给页面。需要发现文件修改或改名时,用 project.files.watch 订阅并定时 poll。每次保存返回的 cursor,收到 rescanRequired:true 后重新核对内容;监听本身不提供文件读取权限。
页面必须传送大块二进制时,用 component.transfer 分段发送,每块最多 1 MiB。每块通过独立 RPC 转发,发送成功后再继续下一块;完成后核对 SHA-256,并关闭传输会话。详见 文件资源与传输限制。
后端服务
编写与照片流通信的插件服务
- ready初始化完成后输出
{"type":"ready","protocolVersion":1}。 - request接收请求 ID、方法名、JSON 参数和当前页面信息。保留请求 ID,返回结果和调用 Host API 时会用到它。
- capability调用 Host API 时,用
parentId指向当前请求,并等待capability-response返回结果。 - response返回成功或错误结果。stdout 只输出协议消息,运行日志写入 stderr。
运行插件自带的程序
在服务的能力和权限中同时声明 component.runtime.execute,并通过顶层 runtimeCommandCapabilities、组件 capability 和平台入口声明自己的命令。服务用 execute 传入 runtime capability、参数及受限输入,不能从 UI 传入任意可执行文件路径。
后台执行使用 task.background:true 和稳定的 operationKey、idempotencyKey;以相同身份调用 status/cancel/pause/resume。进度事件必须预先声明;取消与暂停需要运行时支持控制参数。可参考 extensions/video-tools/service.cjs。
project.media.process 提供 video.timelineFrames 和 office.extractImages。视频转码、切割和裁剪由组件自己的运行时实现。
UI 运行在沙箱中;组件服务、生命周期脚本和可执行文件以当前用户的系统权限运行。Host capability/permission 是接口契约,不能把受监管服务描述为能够安全执行不受信代码的系统沙箱。
媒体处理流程
读取素材、保存结果并创建版本
- 1分页读取
先用
project.media.page获取媒体引用,再调用project.media.variants并传入variants:[]获取稳定元数据。 - 2按需授权像素
需要时再请求 thumbnail、preview 或 original;原图附带短时一次性输入令牌。
- 3取得文件副本
通过
project.input.tokens把受限输入复制进插件私有存储。 - 4暂存与校验
调用
project.output的 stage、write 和 validate,目标始终是项目相对路径。 - 5保存处理结果
使用固定的 idempotencyKey 提交,同一次操作重试时不换键。新文件重名时可使用 onConflict:"rename",并读取返回的实际保存路径。
- 6创建版本
可把 commit/artifact ID 交给
version.create,并使用另一个稳定幂等键。 - 7恢复或清理
stage 保留 24 小时;重启后继续校验/提交,放弃时显式 rollback。
token 是临时读取凭证,cursor 用于继续翻页,stage 是暂存区,commit/artifact 表示已保存的结果。使用宿主返回的值,不要自行构造或跨项目复用。幂等键用于识别同一次保存操作,重试时保持不变。
本地开发
在本地加载和调试插件
未打包开发环境会从项目 extensions 和 PHOTOFLOW_COMPONENT_DEV_ROOTS 发现组件。每个包通过自己的 package.json 选择清单、准备命令、开发运行时和文件映射;同 ID 已安装包存在时,开发构建使用当前源码。
npm run prepare:components:dev
npm run electron:dev- 开发映射只能指向清单已经声明的包路径,未知字段和未声明路径会拒绝。
- Python/Node 脚本运行时可以声明命令、入口和参数前缀;原生可执行文件省略脚本入口。
- 有效源码注册会标记为“开发组件”,不会伪装成通过完整性验证的正式安装。
- 开发与安装组件都可以停用;停用会关闭 surface、服务、工作进程与未完成网络活动。
测试与打包
测试插件并打包安装
npm run test:component-host-api
npm run test:component-host
npm run test:component-service
npm run test:electron-security
npm run test:architecture- 用当前
component-manifestschema 校验清单,并确认所有 Host 能力名称与 SDK 一致。 - 安装包只包含构建后的 UI、服务与运行资源;生命周期动作必须填写匹配 SHA-256。
- 在干净配置中验证安装、停用、启用、取消、重启、任务恢复和卸载。
- 核对包内清单、组件版本和实际文件,确保用户能按安装说明完成安装。
选择需要的接口,了解请求参数、权限和错误处理方式。