普通视图

发现新文章,点击刷新页面。
昨天 — 2025年10月14日首页

微信小程序同声传译插件深度应用:语音合成与长文本播放优化

作者 _AaronWong
2025年10月14日 07:35

之前的文章 微信小程序同声传译插件接入实战:语音识别功能完整实现指南介绍如何使用同声传译插件进行语音识别,这篇将会讲述同声传译的另一个功能语音合成。

功能概述

微信小程序同声传译插件的语音合成(TTS)功能能将文字内容转换为语音播放,适用于内容朗读、语音提醒、无障碍阅读等场景。

核心实现架构

状态管理

const textToSpeechContent = ref("")
const textToSpeechStatus = ref(0)  // 0 未播放 1 合成中 2 正在播放

核心功能实现

语音合成主函数

function onTextToSpeech(text = "") {
  // 如果正在播放,先停止
  if(textToSpeechStatus.value > 0) {
    uni.$emit("STOP_INNER_AUDIO_CONTEXT")
  }
  
  textToSpeechStatus.value = 1
  uni.showLoading({
    title: "语音合成中...",
    mask: true,
  })
  
  // 处理文本内容
  if(text.length) {
    textToSpeechContent.value = text
  }
  
  // 分段处理长文本(微信限制每次最多200字)
  let content = textToSpeechContent.value.slice(0, 200)
  textToSpeechContent.value = textToSpeechContent.value.slice(200)
  
  if(!content) {
    uni.hideLoading()
    return
  }
  
  // 调用合成接口
  plugin.textToSpeech({
    lang: "zh_CN",
    tts: true,
    content: content,
    success: (res) => {
      handleSpeechSuccess(res)
    },
    fail: (res) => {
      handleSpeechFail(res)
    }
  })
}

合成成功处理

function handleSpeechSuccess(res) {
  uni.hideLoading()
  
  // 创建音频上下文
  innerAudioContext = uni.createInnerAudioContext()
  innerAudioContext.src = res.filename
  innerAudioContext.play()
  textToSpeechStatus.value = 2
  
  // 播放结束自动播下一段
  innerAudioContext.onEnded(() => {
    innerAudioContext = null
    textToSpeechStatus.value = 0
    onTextToSpeech() // 递归播放剩余内容
  })
  
  setupAudioControl()
}

音频控制管理

function setupAudioControl() {
  uni.$off("STOP_INNER_AUDIO_CONTEXT")
  uni.$on("STOP_INNER_AUDIO_CONTEXT", (pause) => {
    textToSpeechStatus.value = 0
    if(pause) {
      innerAudioContext?.pause()
    } else {
      innerAudioContext?.stop()
      innerAudioContext = null
      textToSpeechContent.value = ""
    }
  })
}

错误处理

function handleSpeechFail(res) {
  textToSpeechStatus.value = 0
  uni.hideLoading()
  toast("不支持合成的文字")
  console.log("fail tts", res)
}

关键技术点

1. 长文本分段处理

由于微信接口限制,单次合成最多200字,需要实现自动分段:

let content = textToSpeechContent.value.slice(0, 200)
textToSpeechContent.value = textToSpeechContent.value.slice(200)

2. 播放状态管理

通过状态值精确控制播放流程:

  • 0:未播放,可以开始新的合成
  • 1:合成中,显示loading状态
  • 2:播放中,可以暂停或停止

3. 自动连续播放

利用递归实现长文本的自动连续播放:

innerAudioContext.onEnded(() => {
  onTextToSpeech() // 播放结束继续合成下一段
})

完整代码

export function useTextToSpeech() {
  const plugin = requirePlugin('WechatSI')
  let innerAudioContext = null
  const textToSpeechContent = ref("")
  const textToSpeechStatus = ref(0)
  
  function onTextToSpeech(text = "") {
    if(textToSpeechStatus.value > 0) {
      uni.$emit("STOP_INNER_AUDIO_CONTEXT")
    }
    textToSpeechStatus.value = 1
    uni.showLoading({
      title: "语音合成中...",
      mask: true,
    })
    
    if(text.length) {
      textToSpeechContent.value = text
    }
    
    let content = textToSpeechContent.value.slice(0, 200)
    textToSpeechContent.value = textToSpeechContent.value.slice(200)
    
    if(!content) {
      uni.hideLoading()
      return
    }
    
    plugin.textToSpeech({
      lang: "zh_CN",
      tts: true,
      content: content,
      success: (res) => {
        uni.hideLoading()
        innerAudioContext = uni.createInnerAudioContext()
        innerAudioContext.src = res.filename
        innerAudioContext.play()
        textToSpeechStatus.value = 2
        
        innerAudioContext.onEnded(() => {
          innerAudioContext = null
          textToSpeechStatus.value = 0
          onTextToSpeech()
        })
        
        uni.$off("STOP_INNER_AUDIO_CONTEXT")
        uni.$on("STOP_INNER_AUDIO_CONTEXT", (pause) => {
          textToSpeechStatus.value = 0
          if(pause) {
            innerAudioContext?.pause()
          } else {
            innerAudioContext?.stop()
            innerAudioContext = null
            textToSpeechContent.value = ""
          }
        })
      },
      fail: (res) => {
        textToSpeechStatus.value = 0
        uni.hideLoading()
        toast("不支持合成的文字")
        console.log("fail tts", res)
      }
    })
  }
  
  return {
    onTextToSpeech,
    textToSpeechContent,
    textToSpeechStatus
  }
}
昨天以前首页

Electron IPC 自动化注册方案:模块化与热重载的完美结合

作者 _AaronWong
2025年10月9日 07:21

背景

在 Electron 应用开发中,我们经常需要在主进程和渲染进程之间进行通信。随着应用功能不断增加,IPC处理器的数量也会急剧增长。传统的手动注册方式会导致代码臃肿、难以维护。本文将介绍一种自动化的 IPC 处理器注册机制。

核心实现

1. 自动化扫描与注册

首先,我们来看核心的注册机制实现:

// project/electron/ipc/index.js
const path = require("path");
const { readdirSync } = require("fs");
const importSync = require("import-sync");

const getIcpMainHandler = () => {
    let allHandler = {};
    // 扫描 handlers 目录下的所有文件
    const dirs = readdirSync(path.join(__dirname, "handlers"), "utf8");
    
    for (const file of dirs) {
        const filePath = path.join(__dirname, "handlers", file);
        const handlersTemp = importSync(filePath);
        let handlers = {}
        
        // 分析每个导出的处理器
        for (const key in handlersTemp) {
            const handler = handlersTemp[key];
            let handlerType = Object.prototype.toString.call(handler);
            const match = handlerType.match(/^\[object (\w+)\]$/);
            handlerType = match[1];
            
            handlers[key] = {
                key,
                type: handlerType,
                val: handler,
            };
            
            allHandler = {
                ...allHandler,
                ...handlers,
            };
        }
    }
    return allHandler;    
}

module.exports.registerHandlerForIcpMain = () => {
    const ipcMainHandlers = getIcpMainHandler();
    // 只执行函数类型的处理器
    for (const key in ipcMainHandlers) {
        const handler = ipcMainHandlers[key];
        if (handler.type.indexOf("Function") > -1) {
            handler.val()
        }
    }
};

2. 模块化的处理器定义

将不同功能的 IPC 处理器分类到不同的文件中:

// project/electron/ipc/handlers/file.js
const { ipcMain } = require("electron");

module.exports.fileHander = () => {
    // 处理文件夹清理请求
    ipcMain.handle("clear-folder", async (event, path) => {
        // 具体的文件操作逻辑
        console.log("Clearing folder:", path);
    });
    
    // 可以注册更多的文件相关处理器
    ipcMain.handle("read-file", async (event, filePath) => {
        // 文件读取逻辑
    });
}
// project/electron/ipc/handlers/win.js
const { ipcMain, BrowserWindow } = require("electron");

module.exports.winHander = () => {
    // 处理窗口打开请求
    ipcMain.on('open-window', async (event) => {
        // 创建新窗口的逻辑
        const win = new BrowserWindow({ width: 800, height: 600 });
    });
    
    // 窗口管理相关处理器
    ipcMain.on('close-window', async (event) => {
        // 窗口关闭逻辑
    });
}

3. 主进程集成

在主进程启动时注册所有 IPC 处理器:

// project/electron/main.js
const { registerHandlerForIcpMain } = require("./ipc/index.js")

app.whenReady().then(() => {
    // 自动注册所有 IPC 处理器
    registerHandlerForIcpMain();
    
    // ... 其他初始化逻辑
});

4. 构建配置优化

为了支持开发时的热重载,我们在 Vite 配置中动态扫描 IPC 文件:

// vite.config.js
import fs from "fs"
import path from "path"

// 递归扫描目录(支持子目录)
const scanDeep = (dir) => {
    let results = []
    const list = fs.readdirSync(dir, { withFileTypes: true })

    for (const item of list) {
        const fullPath = path.join(dir, item.name)
        if (item.isDirectory()) {
            results = results.concat(scanDeep(fullPath))
        } else if (item.isFile() && [".js", ".cjs", ".mjs"].includes(path.extname(item.name))) {
            results.push(fullPath)
        }
    }
    return results
}

// 生成 Electron 配置
export const getElectronConfig = () => {
    const electronDir = path.join(__dirname, "../electron")
    const ipcEntries = scanDeep(path.join(electronDir, "ipc")).map((file) => ({
        // 从项目根目录计算相对路径
        entry: path.relative(process.cwd(), file)
    }))
    
    return [
        // 主进程
        {
            entry: path.join(process.cwd(), "electron/main.js")
        },
        // 预加载脚本
        {
            entry: path.join(process.cwd(), "electron/preload/index.js"),
            onstart(args) {
                args.reload()
            }
        },
        // 动态 IPC 入口
        ...ipcEntries
    ]
}

技术优势

1. 模块化与可维护性

  • 将相关功能的 IPC 处理器分组到不同的文件中
  • 新功能的添加不会影响现有代码结构
  • 便于团队协作开发

2. 自动化管理

  • 自动扫描并注册所有处理器,无需手动导入
  • 减少遗漏注册的风险
  • 统一的处理器管理入口

3. 开发体验优化

  • 支持开发时的热重载
  • 清晰的目录结构便于定位问题
  • 类型检查确保处理器格式正确

总结

这种自动化的 IPC 处理器注册机制为 Electron 应用开发带来了显著的好处:

  • 可扩展性:轻松添加新的 IPC 处理器
  • 可维护性:清晰的代码组织结构
  • 开发效率:自动化注册减少手动配置
  • 团队协作:统一的代码规范

这种模式特别适合中大型 Electron 项目,能够有效管理复杂的进程间通信需求。

❌
❌