# songloft-plugin-miot **Repository Path**: lutaining/songloft-plugin-miot ## Basic Information - **Project Name**: songloft-plugin-miot - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-09 - **Last Updated**: 2026-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # songloft-jsplugin-miot A Songloft JS plugin generated by `create-songloft-plugin`. 通过 MIoT 协议把歌曲投放到智能音箱设备的示例插件。 > ™️ **商标声明**:本插件中提到的 "MIoT" "MiHome" 等协议 / 产品名称均归各自商标权人所有,相关名称的出现仅出于互操作和指示性合理使用目的。本插件**未获得任何商标持有人的授权或背书**,与上述商标持有人**无任何关联**。 ## Development ```bash npm install npm run dev # watch + auto-upload to local Songloft npm run build # produce dist/miot.jsplugin.zip npm run validate # verify plugin.json hashes ``` ## Description 智能音箱设备控制示例插件。本仓库仅提供与宿主 SDK 对接的脚手架代码,**不附带任何第三方设备协议实现或账号体系**,使用者需自行负责接入合规性。 ## 设备分组(多房间同步) 把多台音箱归入同一「设备分组」后,对组内**任一**音箱的播放控制会自动同步给组内**其他**成员,实现多房间同步播放。 实现方式很简单:**一个分组共用一套播放列表**——同一份队列、播放索引、播放模式、切歌定时器与随机序列,所有指令一次性下发给组内全部音箱。 - **覆盖操作**:播放歌单、暂停、继续、停止、上一首、下一首、切换播放模式、调节音量,以及单曲 URL 推送。 - **命令入口全覆盖**:无论从插件网页的播放控制按钮、对音箱说语音口令(如「下一首」「停止」「调大音量」),还是定时任务触发,都会带动全组。 - **切歌与自动续播都同步**:因为全组只有一份队列/一个定时器/一次随机选择,无论手动切歌还是一首播完自动续播,全组始终是同一首,**随机模式下也不会各放各的**。 - **容错**:组内某台离线/失败只记录告警,其余音箱照常播放;离线的音箱恢复后,下一条指令(含自动切歌)会自然覆盖到它。 - **成员互斥**:一台设备最多属于一个组。把设备加入新组时会自动从其它组移除。删除分组或移出成员后,相关设备恢复独立播放。 配置入口:插件页 → 设置 → 设备 → **设备分组**。点「新建分组」,填名称并勾选至少 2 台已启用管理的设备即可。分组信息持久化在插件本地存储,跨重启保留。 > 说明:智能音箱各自独立接收云端指令,不存在帧级同步协议,成员之间可能有轻微起播时差,属正常现象。 ## 定时任务 - 法定节假日 定时任务的「每周」调度支持中国法定节假日感知,有三种模式: - **忽略节假日**(默认):完全按勾选的星期触发,行为与节假日无关。 - **仅法定节假日触发**:今天必须是法定放假日(春节、国庆等)才触发,且星期也需在勾选范围内。适合「节假日早晨播音乐迎接好心情」这类场景。 - **真·工作日(跳过节假日,含调休补班)**:勾选「周一到周五」后开启此模式,则节假日跳过、调休补班的周末强制触发,符合「真正上班日的闹钟」语义。 节假日数据来自 [NateScarlet/holiday-cn](https://github.com/NateScarlet/holiday-cn)(MIT 协议),由 `npm run build`(或 `dev`)的 `prebuild` 钩子从 jsDelivr / GitHub raw 下载,覆盖当前年和下一年,并通过 esbuild 编入插件 bundle。运行时不需要网络访问。 注意事项: - 国务院通常每年 11 月公布次年安排,在此之前下一年的节假日数据为空,此时会按「平常日」处理(不影响普通调度,但「仅法定节假日触发」模式将不会触发)。 - 每次发版会自动滚动到最新数据;长期未更新的插件版本可能缺失最新节假日,建议定期升级插件。 - 数据下载产物已 commit 入库,本地无网络也可构建。 ## 发版节奏 版本号就是发版日期(如 `2026.7.27`,无前导零)。CI 每天北京时间 08:17 自动检查一次: - **自上次发版以来有新提交** → 构建并发布 `v<当天日期>`,即「今天的修改明天出新包」。 - **没有新提交** → 跳过,不产生空版本。 所以版本号会跳日(比如 `2026.7.25` 的下一版是 `2026.7.27`),这是正常的——中间那天没有改动。需要立刻出包时,维护者可在 Actions 页手动触发,手动触发不受上述门禁限制。 同一天重复发版会覆盖当天的 Release,但版本号不变,已安装的客户端**不会**提示升级,需等次日。 ## 作为搜索源接入 miot(供插件开发者) miot 在本地曲库搜不到歌时,会调用用户配置的「外部搜索源」把歌找回来推给音箱。任何插件只要实现了搜索接口,就能把自己**登记为候选**,出现在 miot 配置页的搜索源下拉里供用户一键选用——不必再靠 miot 写死内置列表。 接入分两步: ### 1. 实现搜索接口 `/api/search/topone` `POST`,请求/响应遵循 topone 规范(完整定义见 miot 配置页「外部搜索」区的**「接口规范」**对话框): - 请求体 `{ keyword, hint?: { title, artist, duration }, quality? }` - 成功响应 `{ code: 0, msg, data: { title, artist, album?, duration?, cover_url?, url?, plugin_entry_path?, source_data?, dedup_key?, lyric?, lyric_source? } }` - 未命中 / 失败返回 `code != 0`、`data: null`;**超时 6 秒** 内置的 `ytdlp` / `bili` / `subsonic` 即此规范的参考实现。 ### 2. 经插件间通信(`songloft.comm`)注册为候选 在你的 `plugin.json` 声明 `inter-plugin` 权限,并在 `onInit` 里向 miot 注册: ```ts // 延迟 + 重试,规避与 miot 同时启动的竞态; // miot 未安装 / 旧版 host 无 comm 时静默跳过,绝不阻塞自身功能。 function registerToMiot() { let attempts = 0; const tryRegister = async () => { attempts++; try { if (!songloft.comm || typeof songloft.comm.call !== 'function') return; await songloft.comm.call('miot', 'register-search-provider', { name: '我的音源', // 下拉显示名 searchPath: '/api/search/topone', // 你的搜索路由(默认即此,可省) icon: '', // 可选 }); } catch (e) { if (attempts < 5) setTimeout(tryRegister, 3000); } }; setTimeout(tryRegister, 2000); } ``` | action | 说明 | |--------|------| | `register-search-provider` | 注册 / 更新候选(幂等,按 entryPath 覆盖,每次 onInit 重复调用即可) | | `unregister-search-provider` | 注销候选(可选,一般在 onDeinit 调用;payload 传空即可) | 要点: - **不用传 entryPath**:miot 以宿主注入的**可信调用方身份**为准(`from`),插件无法把自己伪造成别的插件。 - **payload 字段**:`name`(显示名,缺省用 entryPath)、`searchPath`(默认 `/api/search/topone`)、`icon`(可选)。 - **纯增强、无副作用**:注册只是让你出现在候选下拉;是否启用由用户在配置页决定。miot 会通过宿主插件列表校验你的 `installed/active` 状态,你被卸载后会自动从列表消失。 - **向后兼容**:内置 `ytdlp/bili/subsonic` 也走这套注册流程,同时保留在 miot 的内置 fallback 列表中,兼容尚未接入的旧版本。 ## Author hanxi ## 免责声明 - 本项目**仅供个人学习研究技术使用**,严禁任何形式的商业用途,不得使用本代码进行任何形式的牟利 / 贩卖 / 传播。 - 本项目不附带任何第三方设备协议实现或账号体系;与第三方设备通信所产生的数据、账号凭据均由使用者自行提供与管理,对于这些数据本项目不拥有所有权。 - 本项目完全免费,仅供个人私下范围研究交流学习技术使用,对于使用者在违反当地法律法规情况下使用本项目所造成的任何违法违规行为,由使用者自行承担。 - 若你使用了本项目,即代表你接受以上声明。 ## License Apache-2.0 © 2026 hanxi