为了账号安全,请及时绑定邮箱和手机立即绑定

小程序、录音、TP5、转码、silk

标签:
PHP JavaScript

本篇文章主要介绍一下以下功能:

用小程序实现录音功能,在本地播放,提交服务端,服务端播放。

中间遇到了一些坑,找到了一些解决方法,如果有更优的解决方案希望你能在评论留言,一起加油。

1.小程序端

图片描述

前端展示页面,小程序采用数据绑定方式,这里正在录音和正在播放的状态切换,用两张图根据录音状态和播放状态切换,这里就不多说了。

长按开始录音,触发录音事件,松手执行录音结束,将录音结果保存在本地。

    /**
     * 开始录音
     */
    startRecord: function () {
        var that = this
        that.setData({
            isRecording: true,
        });

        whisper.startRecord({
            success(result) {
                that.setData({
                    isRecording: false,
                });
                console.log('录音成功:', result);
                console.log('录音成功:', result.tempFilePath);
                //提交录音
                var src = result.tempFilePath;
                var name = 'record'
                repair.uploadRecord(src, name, (res) => {

                    that.data.recordObj.record = res;
                });
            },
            fail(error) {
                console.log('录音失败:', error);
            },
            process() {
                that.setData({
                    duration: whisper.getRecordDuration(),
                });
            },
            compelete() {

            },
        });
    },

    /**
     * 停止录音
     */
    stopRecord: function () {
        var that = this;
        whisper.stopRecord({
            success(result) {
                that.setData({
                    isRecording: false,
                });

                console.log('停止录音:', result);
            },
            fail(error) {
                console.log('停止录音失败:', error);
            }
        });

    },

    /**
     * 播放录音
     */
    play: function () {
        var that = this
        that.setData({
            isPlaying: true,
        });
        whisper.playRecord({
            src: whisper.getRecordSrc(),
            success(result) {
                that.setData({
                    isPlaying: false,
                });
                console.log('播放/暂停成功:', result);
            },
            fail(error) {
                that.setData({
                    isPlaying: false,
                });
                console.log('播放/暂停失败:', error);
            }
        });
    },

在录音结束后,执行success的回调函数,上传录音文件到服务器,注意微信小程序的录音文件是silk格式,这是一大坑,在服务端需要处理一下,后面再说。

具体的上传在model层。

    //上传录音
    uploadRecord(src, name, callback){
        var params = {
            url:'repair/record',
            filePath: src,
            name: name,
            formData: {
                'name': name
            },
            sCallback: function (data) {
                callback && callback(data);
            }
        };
        this.upload(params);
    }

录音类也贴一下


var noop = function noop() { };
var recorder = {

};

/***
 * @class
 * 表示请求过程中发生的异常
 */
var RecordError = (function () {
    function RecordError(message) {
        Error.call(this, message);
        this.message = message;
    }

    RecordError.prototype = new Error();
    RecordError.prototype.constructor = RecordError;

    return RecordError;
})();

function startRecord(options) {
    if (typeof options !== 'object') {
        var message = '请求传参应为 object 类型,但实际传了 ' + (typeof options) + ' 类型';
        throw new RecordError(message);
    }

    var process = options.process;
    var success = options.success || noop;
    var fail = options.fail || noop;
    var complete = options.complete || noop;

    if (typeof process !== 'function') {
        var message = '刷新Ui函数不存在';
        throw new RecordError(message);
    }

    // 成功回调
    var callSuccess = function () {
        success.apply(null, arguments);
        complete.apply(null, arguments);
    };

    // 失败回调
    var callFail = function (error) {
        fail.call(null, error);
        complete.call(null, error);
    };

    // 初始化录音器
    initRecorder();
    // 实际进行请求的方法
    doRecord();

    // 实际进行请求的方法
    function doRecord() {
        console.log("开始录音")
        recorder.timer = setInterval(function () {
            recorder.duration += 1;
            process();
            if (recorder.duration >= recorder.maxDuration && recorder.timer) {
                clearInterval(recorder.timer);
            }
        }, 1000);
        wx.startRecord({
            success: function (res) {
                if (res.tempFilePath) {
                    recorder.src = res.tempFilePath;
                    callSuccess.apply(null, arguments);
                    return ;
                } else {
                    message = '录音文件保存失败';
                    var error = new RecordError(message);
                    options.fail(error);
                }
                callFail(error);
            },

            fail: callFail,
            complete: complete,
        });

    };

    // 初始化录音器
    function initRecorder() {
        recorder = {
            maxDuration: 60,
            duration: 0,
            src: null,
            timer: null,
        };
    }

};

function stopRecord(options) {
    if (typeof options !== 'object') {
        var message = '请求传参应为 object 类型,但实际传了 ' + (typeof options) + ' 类型';
        throw new RecordError(message);
    }

    var success = options.success || noop;
    var fail = options.fail || noop;
    var complete = options.complete || noop;

    // 成功回调
    var callSuccess = function () {
        success.apply(null, arguments);
        complete.apply(null, arguments);
    };

    // 失败回调
    var callFail = function (error) {
        fail.call(null, error);
        complete.call(null, error);
    };

    doStopRecord();

    // 实际进行请求的方法
    function doStopRecord() {
        wx.stopRecord({
            success: function (res) {
                if (recorder.timer) {
                    clearInterval(recorder.timer);
                }
                callSuccess.apply(null, arguments);
            },
            fail: callFail,
            complete: complete,
        });
    };
};

function getRecordDuration() {
    return recorder.duration || 0;
}

function getRecordSrc() {
    return recorder.src || null;
}

module.exports = {
    RecordError: RecordError,
    startRecord: startRecord,
    stopRecord: stopRecord,
    getRecordDuration: getRecordDuration,
    getRecordSrc: getRecordSrc,
};

播放类


var noop = function noop() { };
var player = {
    src: null,
};

/***
 * @class
 * 表示请求过程中发生的异常
 */
var PlayError = (function () {
    function PlayError(message) {
        Error.call(this, message);
        this.message = message;
    }

    PlayError.prototype = new Error();
    PlayError.prototype.constructor = PlayError;

    return PlayError;
})();

function play(options) {
    if (typeof options !== 'object') {
        var message = '请求传参应为 object 类型,但实际传了 ' + (typeof options) + ' 类型';
        throw new PlayError(message);
    }

    if (!options.src) {
        var message = '无资源';
        var error = new PlayError(message);
        options.fail(error);
        return;
    }

    var process = options.process || noop;
    var success = options.success || noop;
    var fail = options.fail || noop;
    var complete = options.complete || noop;

    // 成功回调
    var callSuccess = function () {
        success.apply(null, arguments);
        complete.apply(null, arguments);
    };

    // 失败回调
    var callFail = function (error) {
        fail.call(null, error);
        complete.call(null, error);
    };

    if (!player.src || player.src != options.src) {
        if (player.src) {
            doStop(false);
        }
        doPlay();
    } else {
        doStop(true);
    }

    // 实际进行请求的方法
    function doPlay() {
        player.src = options.src;
        console.log("开始播放" + player.src);
        wx.playVoice({
            filePath: player.src,
            success: function (res) {
                // success
                console.log("播放结束" + player.src);
                player.src = null,
                    callSuccess.apply(null, arguments);
            },
            fail: callFail,
            complete: complete,
        })
    };

    // 实际进行请求的方法
    function doStop(isCallbackOn) {
        wx.stopVoice({
            success: function () {
                console.log("停止播放" + player.src);
                player.src = null;
                isCallbackOn ? callSuccess.apply(null, arguments) : noop();
            },
            fail: isCallbackOn ? callFail : noop,
            complete: isCallbackOn ? complete : noop,
        });
    };
};

module.exports = {
    PlayError: PlayError,
    playRecord: play,
};

ok,前端完成,转向服务端

2.服务端

后端用的是PHP的TP5框架,接收到的silk文件想要在浏览器上播放,我刚开始是想的转码MP3,在网上找了些资料,基本都指向了kn007大神的博客,写了个demo,Silk v3编码格式转码MP3终于实现了,结果发现小程序的silk不是silk V3,哭晕在厕所,这里可能有我没理解的,有人实现了希望可以留言。

然后,我一同事提醒我webm格式的可以直接播放的,囧。那就没的说了,直接base64解码成webm格式。

   /**
     * 报修录音
     * @url /repair/record
     * @http post
     */
    public function uploadRecord()
    {
        $name = input('post.name');
        if($_FILES[$name]["error"] > 0){
            throw new Exception("Error: " . $_FILES[$name]["error"]);
        }else{
            $file = request()->file($name);
            $info = $file->move(ROOT_PATH . 'public' . DS . 'record/'.$this->mainID);
            $path = $info->getSaveName();

            $base = ROOT_PATH . 'public' . DS;
            $silk = $base . 'record/' . $this->mainID . '/' . $path;
            $webm = str_replace("silk","webm",$silk);
            $returnWebm = str_replace("silk","webm",$path);

            $content = file_get_contents($silk);
            $baseSilk = base64_decode(str_replace("data:audio/webm;base64,", '', $content));
            file_put_contents($webm,$baseSilk);

        }

        return $returnWebm;
    }

也贴一下wins环境下,用silk_v3_decoder.exe转码MP3的测试代码吧,具体的可以看下kn007大神的博客。

    public function test()
    {

        $base = ROOT_PATH . 'public' . DS;

        $file = $base.'record\\song1.silk';
        $type = $base.'record\\song.pcm';

        $res  = $base.'record\\song.mp3';
        $cmd = "{$base}silk\\windows\\silk_v3_decoder.exe $file $type" ;
        exec($cmd, $out);

        $cmd1 = "{$base}silk\\windows\\ffmpeg.exe -y -f s16le -ar 24000 -ac 1 -i  $type $res";
        exec($cmd1,$out1);
    }

就这吧。

点击查看更多内容
7人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Web前端工程师
手记
粉丝
82
获赞与收藏
1103

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消