基于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开发的便捷。

相关推荐
sxjk19872 小时前
华为IMS系统主要接口备忘
运维·服务器·前端·核心网
T***u3332 小时前
前端Server Components性能分析 Server Components架构原理
前端
Q***f6352 小时前
前端动画性能优化,60fps实现技巧
前端
艾莉丝努力练剑2 小时前
【自动化测试实战篇】Web自动化测试实战:从用例编写到报告生成
前端·人工智能·爬虫·python·pycharm·自动化·测试
Mintopia2 小时前
💥 Trae Solo 编程 vs. Cursor:新机遇与新挑战
前端·人工智能·trae
Mintopia2 小时前
🌌 长上下文 AIGC 的性能瓶颈:Web 端技术的突破与妥协
前端·人工智能·trae
天蓝色的鱼鱼2 小时前
前端小白Express入门:初识Web框架与项目搭建
前端·node.js·express
一只小阿乐2 小时前
react 点击事件注意事项
前端·javascript·react.js·react
Mike_jia3 小时前
EMQX:开源MQTT消息中间件王者,百万级物联网连接的首选引擎
前端