基于UniappX开发电销APP,实现通话录音上传、通时通次

原文:nicen.cn/8524.html

之前通过Uniapp+Html5plus实现过实现通话录音上传的功能,一直有一个痛点:录音文件很多的时候通过htmlplus来获取录音文件会很卡。

刚好看到新出的UniappX可以直接把类似ts的uts代码编译成原生代码,而且整个开发流程都可以在Hbuilder内完成,于是乎来了兴趣,试上一试!

开发思路

既然可以通过uts来实现原生开发,但是又想保持Web来进行APP的前端开发,那就直接用老一套的混合开发的方式:通过JS调用写好的原生方法。

相关代码已开源,Github:github.com/friend-nice...

1.通过uts实现读取录音文件

javascript 复制代码
/* 将功能封装为类:在构造函数中设置 audioPath */
export default class Scanner {

    ....

/* 读取目录下最新的录音文件 */
getLatestAudio(option : QueryLatest) : LatestAudioResult {

    /*  定义录音文件路径 */
    if (option.audioPath != null) {
        option.audioPath = this.sdRoot + option.audioPath;
        const initDir = new File(option.audioPath as string);
        if (!initDir.exists() || !!initDir.isDirectory()) {
            option.audioPath = this.getDevicePath();
        }
    } else {
        option.audioPath = this.getDevicePath();
    }

    /* 目录对象 */
    const dir = new File(option.audioPath as string);

    if (!dir.exists() || !dir.isDirectory()) {
        throw new UniError('scanner', 0, '录音文件目录不存在');
    }

    const files = dir.listFiles();
    if (files == null || files.size == 0) {
        throw new UniError('scanner', 0, '本次通话没有产生录音文件');
    }

    let latest : File | null = null;
    const countInt : Int = files.size;
    const count : number = countInt;
    option.latestTime = option.latestTime * 1000;

    for (let i : Int = 0; i < countInt; i = i + 1) {
        const f = files[i];
        if (f != null && f.isFile()) {
            const lm = f.lastModified();
            if (lm >= option.latestTime) {
                latest = f;
                option.latestTime = lm;
            }
        }
    }

    if (latest != null) {
        return { file: latest.getAbsolutePath(), count: count };
    }

    throw new UniError('scanner', 0, '本次通话没有产生录音文件');
}

	....
}

以下省略了若干代码,仅展示核心代码,完整代码请查看上方开源项目

2.通过webview对象向js提供直接调用的方法

通过监听webview组件的message事件,来获取js发送过来的消息,然后进行对应的处理,最后通过webview对象的evalJs方法,将结果回调给前端。相关代码如下:

javascript 复制代码
/* 消息监听 */
const onPostMessage = (e) => {

  /* 数据 */
  const _list = e.detail.data;
  const _action = _list[0].action;
  const _data = _list[0].data;


  /* 初始化 */
  if (!_wv) {

    /* 获取webview对象 */
    const _wvs = plus.webview.getDisplayWebview().filter(wv => {
      return wv.getURL().indexOf(host) > -1;
    });

    /* 弹出异常 */
    if (!_wvs.length) {
      load.toast("服务异常");
      return;
    }

    /* webview对象 */
    _wv = _wvs[0];
    _wv.onCall = (action, success, payload) => {
      _wv.evalJS('window.__onCall(`' + JSON.stringify({
        action,
        success,
        payload
      }) + '`)');
    }
  }

  /* 判断对象是否有这个属性 */
  try {
    if (!!scanner[_action]) {
      _wv.onCall(_action, true, scanner[_action](_data));
    }
  } catch (e) {
    const match = e.message.match(/errMsg='([^']+)'/);
    _wv.onCall(_action, false, match && match[1] ? match[1] : '系统异常');
  }


}

3.前端JS封装和APP通信的方法

通过下方封装的模块,可以直接通过await同步调用APP对应的方法。

javascript 复制代码
/**
 * NativeBridge.es6.js
 * ES Module,action 级回调,默认 15 s 超时
 * import { invoke } from './NativeBridge.es6.js';
 * await invoke({ action:'getDeviceId', timeout:10_000 });
 */
const pool = Object.create(null);   // action -> {resolve, reject, timer}

/**
 * App 回传结果的唯一全局钩子
 * 调用方式(Android):
 * webView.evaluateJavascript('window.__onCall('{"action":"getDeviceId","success":true,"payload":"fake-123"}')', null);
 * payload 可省略。
 */
window.__onCall = function (json) {
    try {
        const {action, success, payload} = JSON.parse(json);
        /* 格式非法,直接丢弃 */
        if (typeof action !== 'string') return;

        const item = pool[action];
        /* 已超时或重复回调 */
        if (!item) return;

        const {resolve, reject, timer} = item;
        clearTimeout(timer);
        delete pool[action];

        success ? resolve(payload) : reject(new Error(payload ?? 'native error'));
    } catch {
        /* 非合法 JSON,直接忽略 */
    }
};

/**
 * 调用 App 内部方法
 * @param {Object} opt
 * @param {string} opt.action   - 必填,方法名
 * @param {any}    [opt.params] - 选填,参数,默认空对象
 * @param {number} [opt.timeout=15000] - 选填,超时毫秒
 * @returns {Promise<any>}
 */
export default function invoke(opt = {}) {

    /* 默认配置 */
    const {action, data = {}, timeout = 15000} = opt;

    /* 参数校验 */
    if (typeof action !== 'string' || !action)
        return Promise.reject(new TypeError('action is required'));

    return new Promise((resolve, reject) => {

        /* 如果已存在同 action 的未完成调用,先清掉 */
        if (pool[action]) {
            clearTimeout(pool[action].timer);
            pool[action].reject(new Error('covered by new call'));
        }

        /* 超时器,防止调用超时 */
        const timer = setTimeout(() => {
            delete pool[action];
            reject(new Error(`invoke ${action} timeout`));
        }, timeout);

        /* 回调 */
        pool[action] = {resolve, reject, timer};

        /* 发送消息 */
        uni.postMessage({
            data: {
                action,
                data: data
            }
        });

    });
}

4.相关扩展

上方展示了一个通过UniappX简单实现的混合开发的框架,在此基础上可以实现更多的原生方法来扩展功能,同时兼顾性能和Web开发的便捷。

相关推荐
小鹏linux36 分钟前
Ubuntu 22.04 部署开源免费具有精美现代web页面的Casdoor账号管理系统
linux·前端·ubuntu·开源·堡垒机
前端若水1 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
Bigger2 小时前
mini-cc:一个轻量级 AI 编程助手的诞生
前端·ai编程·claude
涵涵(互关)2 小时前
Naive-ui树型选择器只显示根节点
前端·ui·vue
BY组态2 小时前
Ricon组态系统最佳实践:从零开始构建物联网监控平台
前端·物联网·iot·web组态·组态
BY组态2 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·物联网·iot·web组态·组态
SoaringHeart2 小时前
Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager
前端·flutter
放下华子我只抽RuiKe52 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架
IT_陈寒4 小时前
Redis缓存击穿把我整不会了,原来还有这手操作
前端·人工智能·后端
idcu4 小时前
深入 Lyt.js 组件系统:L2 渲染引擎层的核心
前端·typescript