您的位置:奥门新浦京网址 > Wed前段 > 录音的踩坑之旅,的一些经验分享

录音的踩坑之旅,的一些经验分享

发布时间:2019-10-14 18:31编辑:Wed前段浏览(107)

    JavaScript 面试中常见算法问题详解

    2017/02/20 · JavaScript · 1 评论 · 算法

    原文出处: 奥门新浦京网址,王下邀月熊_Chevalier   

    JavaScript 面试中常见算法问题详解 翻译自 Interview Algorithm Questions in Javascript() {…} 从属于笔者的 Web 前端入门与工程实践。下文提到的很多问题从算法角度并不一定要么困难,不过用 JavaScript 内置的 API 来完成还是需要一番考量的。

    关于启用 HTTPS 的一些经验分享(二)

    2015/12/24 · 基础技术 · HTTP, HTTPS

    原文出处: imququ(@屈光宇)   

    文章目录

    • SSL 版本选择
    • 加密套件选择
    • SNI 扩展
    • 证书选择

    几天前,一位朋友问我:都说推荐用 Qualys SSL Labs 这个工具测试 SSL 安全性,为什么有些安全实力很强的大厂家评分也很低?我认为这个问题应该从两方面来看:1)国内用户终端情况复杂,很多时候降低 SSL 安全配置是为了兼容更多用户;2)确实有一些大厂家的 SSL 配置很不专业,尤其是配置了一些明显不该使用的 CipherSuite。

    我之前写的《关于启用 HTTPS 的一些经验分享(一)》,主要介绍 HTTPS 如何与一些新出的安全规范配合使用,面向的是现代浏览器。而今天这篇文章,更多的是介绍启用 HTTPS 过程中在老旧浏览器下可能遇到的问题,以及如何取舍。

    HTML5 录音的踩坑之旅

    2017/12/25 · HTML5 · 录音

    原文出处: 翁旺   

    JavaScript Specification

    SSL 版本选择

    TLS(Transport Layer Security,传输层安全)的前身是 SSL(Secure Sockets Layer,安全套接字层),它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。TLS 1.3 改动会比较大,目前还在草案阶段。

    SSL 1.0 从未公开过,而 SSL 2.0 和 SSL 3.0 都存在安全问题,不推荐使用。Nginx 从 1.9.1 开始默认只支持 TLS 的三个版本,以下是 Nginx 官方文档中对 ssl_protocols 配置的说明:

    Syntax: ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];
    Default: ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    Context: http, server
    Enables the specified protocols. The TLSv1.1 and TLSv1.2 parameters work only when the OpenSSL library of version 1.0.1 or higher is used.

    但不幸的是,IE 6 只支持 SSLv2 和 SSLv3(来源),也就是说 HTTPS 网站要支持 IE 6,就必须启用 SSLv3。仅这一项就会导致 SSL Labs 给出的评分降为 C。

    开篇闲扯

    前一段时间的一个案子是开发一个有声课件,大致就是通过导入文档、图片等资源后,页面变为类似 PPT 的布局,然后选中一张图片,可以插入音频,有单页编辑和全局编辑两种模式。其中音频的导入方式有两种,一种是从资源库中导入,还有一种就是要提到的录音。
    说实话,一开始都没接触过 HTML5 的 Audio API,而且要基于在我们接手前的代码中进行优化。当然其中也踩了不少坑,这次也会围绕这几个坑来说说感受(会省略一些基本对象的初始化和获取,因为这些内容不是这次的重点,有兴趣的同学可以自行查找 MDN 上的文档):

    • 调用 Audio API 的兼容性写法
    • 获取录音声音的大小(应该是频率)
    • 暂停录音的兼容性写法
    • 获取当前录音时间

     

    阐述下 JavaScript 中的变量提升

    所谓提升,顾名思义即是 JavaScript 会将所有的声明提升到当前作用域的顶部。这也就意味着我们可以在某个变量声明前就使用该变量,不过虽然 JavaScript 会将声明提升到顶部,但是并不会执行真的初始化过程。

    加密套件选择

    加密套件(CipherSuite),是在 SSL 握手中需要协商的很重要的一个参数。客户端会在 Client Hello 中带上它所支持的 CipherSuite 列表,服务端会从中选定一个并通过 Server Hello 返回。如果客户端支持的 CipherSuite 列表与服务端配置的 CipherSuite 列表没有交集,会导致无法完成协商,握手失败。

    CipherSuite 包含多种技术,例如认证算法(Authentication)、加密算法(Encryption)、消息认证码算法(Message Authentication Code,简称为 MAC)、密钥交换算法(Key Exchange)和密钥衍生算法(Key Derivation Function)。

    SSL 的 CipherSuite 协商机制具有良好的扩展性,每个 CipherSuite 都需要在 IANA 注册,并被分配两个字节的标志。全部 CipherSuite 可以在 IANA 的 TLS Cipher Suite Registry 页面查看。

    OpenSSL 库支持的全部 CipherSuite 可以通过以下命令查看:

    openssl ciphers -V | column -t 0xCC,0x14 - ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH Au=ECDSA Enc=ChaCha20-Poly1305 Mac=AEAD ... ...

    1
    2
    3
    openssl ciphers -V | column -t
    0xCC,0x14  -  ECDHE-ECDSA-CHACHA20-POLY1305  TLSv1.2  Kx=ECDH        Au=ECDSA   Enc=ChaCha20-Poly1305  Mac=AEAD
    ... ...

    0xCC,0x14 是 CipherSuite 的编号,在 SSL 握手中会用到。ECDHE-ECDSA-CHACHA20-POLY1305 是它的名称,之后几部分分别表示:用于 TLSv1.2,使用 ECDH 做密钥交换,使用 ECDSA 做认证,使用 ChaCha20-Poly1305 做对称加密,由于 ChaCha20-Poly1305 是一种 AEAD 模式,不需要 MAC 算法,所以 MAC 列显示为 AEAD。

    要了解 CipherSuite 的更多内容,可以阅读这篇长文《TLS 协议分析 与 现代加密通信协议设计》。总之,在配置 CipherSuite 时,请务必参考权威文档,如:Mozilla 的推荐配置、CloudFlare 使用的配置。

    以上 Mozilla 文档中的「Old backward compatibility」配置,以及 CloudFlare 的配置,都可以很好的兼容老旧浏览器,包括 Windows XP / IE6。

    之前见到某个大厂家居然支持包含 EXPORT 的 CipherSuite,这些套件在上世纪由于美国出口限制而被弱化过,已被攻破,实在没有理由再使用。

    录音前的准备

    开始录音前,要先获取当前设备是否支持 Audio API。早期的方法 navigator.getUserMedia 已经被 navigator.mediaDevices.getUserMedia 所代替。正常来说现在大部分的现代浏览器都已经支持navigator.mediaDevices.getUserMedia 的用法了,当然MDN上也给出了兼容性的写法

    JavaScript

    const promisifiedOldGUM = function(constraints) { // First get ahold of getUserMedia, if present const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; // Some browsers just don't implement it - return a rejected promise with an error // to keep a consistent interface if (!getUserMedia) { return Promise.reject( new Error('getUserMedia is not implemented in this browser') ); } // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise return new Promise(function(resolve, reject) { getUserMedia.call(navigator, constraints, resolve, reject); }); }; // Older browsers might not implement mediaDevices at all, so we set an empty object first if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {}; } // Some browsers partially implement mediaDevices. We can't just assign an object // with getUserMedia as it would overwrite existing properties. // Here, we will just add the getUserMedia property if it's missing. if (navigator.mediaDevices.getUserMedia === undefined) { navigator.mediaDevices.getUserMedia = promisifiedOldGUM; }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    const promisifiedOldGUM = function(constraints) {
    // First get ahold of getUserMedia, if present
    const getUserMedia =
    navigator.getUserMedia ||
    navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia;
     
    // Some browsers just don't implement it - return a rejected promise with an error
    // to keep a consistent interface
    if (!getUserMedia) {
    return Promise.reject(
    new Error('getUserMedia is not implemented in this browser')
    );
    }
     
    // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
    return new Promise(function(resolve, reject) {
    getUserMedia.call(navigator, constraints, resolve, reject);
    });
    };
     
    // Older browsers might not implement mediaDevices at all, so we set an empty object first
    if (navigator.mediaDevices === undefined) {
    navigator.mediaDevices = {};
    }
     
    // Some browsers partially implement mediaDevices. We can't just assign an object
    // with getUserMedia as it would overwrite existing properties.
    // Here, we will just add the getUserMedia property if it's missing.
    if (navigator.mediaDevices.getUserMedia === undefined) {
    navigator.mediaDevices.getUserMedia = promisifiedOldGUM;
    }

    因为这个方法是异步的,所以我们可以对无法兼容的设备进行友好的提示

    JavaScript

    navigator.mediaDevices.getUserMedia(constraints).then( function(mediaStream) { // 成功 }, function(error) { // 失败 const { name } = error; let errorMessage; switch (name) { // 用户拒绝 case 'NotAllowedError': case 'PermissionDeniedError': errorMessage = '用户已禁止网页调用录音设备'; break; // 没接入录音设备 case 'NotFoundError': case 'DevicesNotFoundError': errorMessage = '录音设备未找到'; break; // 其它错误 case 'NotSupportedError': errorMessage = '不支持录音功能'; break; default: errorMessage = '录音调用错误'; window.console.log(error); } return errorMessage; } );

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    navigator.mediaDevices.getUserMedia(constraints).then(
    function(mediaStream) {
    // 成功
    },
    function(error) {
    // 失败
    const { name } = error;
    let errorMessage;
    switch (name) {
    // 用户拒绝
    case 'NotAllowedError':
    case 'PermissionDeniedError':
    errorMessage = '用户已禁止网页调用录音设备';
    break;
    // 没接入录音设备
    case 'NotFoundError':
    case 'DevicesNotFoundError':
    errorMessage = '录音设备未找到';
    break;
    // 其它错误
    case 'NotSupportedError':
    errorMessage = '不支持录音功能';
    break;
    default:
    errorMessage = '录音调用错误';
    window.console.log(error);
    }
    return errorMessage;
    }
    );

    一切顺利的话,我们就可以进入下一步了。
    (这里有对获取上下文的方法进行了省略,因为这不是这次的重点)

    阐述下 use strict; 的作用

    use strict; 顾名思义也就是 JavaScript 会在所谓严格模式下执行,其一个主要的优势在于能够强制开发者避免使用未声明的变量。对于老版本的浏览器或者执行引擎则会自动忽略该指令。

    JavaScript

    // Example of strict mode "use strict"; catchThemAll(); function catchThemAll() { x = 3.14; // Error will be thrown return x * x; }

    1
    2
    3
    4
    5
    6
    7
    8
    // Example of strict mode
    "use strict";
     
    catchThemAll();
    function catchThemAll() {
      x = 3.14; // Error will be thrown
      return x * x;
    }

    SNI 扩展

    我们知道,在 Nginx 中可以通过指定不同的 server_name 来配置多个站点。HTTP/1.1 协议请求头中的 Host 字段可以标识出当前请求属于哪个站点。但是对于 HTTPS 网站来说,要想发送 HTTP 数据,必须等待 SSL 握手完成,而在握手阶段服务端就必须提供网站证书。对于在同一个 IP 部署不同 HTTPS 站点,并且还使用了不同证书的情况下,服务端怎么知道该发送哪个证书?

    Server Name Indication,简称为 SNI,是 TLS 的一个扩展,为解决这个问题应运而生。有了 SNI,服务端可以通过 Client Hello 中的 SNI 扩展拿到用户要访问网站的 Server Name,进而发送与之匹配的证书,顺利完成 SSL 握手。

    Nginx 在很早之前就支持了 SNI,可以通过 nginx -V 来验证。以下是我的验证结果:

    ./nginx -V nginx version: nginx/1.9.9 built by gcc 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) built with OpenSSL 1.0.2e-dev xx XXX xxxx TLS SNI support enabled configure arguments: --with-openssl=../openssl --with-http_ssl_module --with-http_v2_module

    1
    2
    3
    4
    5
    6
    ./nginx -V
    nginx version: nginx/1.9.9
    built by gcc 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04)
    built with OpenSSL 1.0.2e-dev xx XXX xxxx
    TLS SNI support enabled
    configure arguments: --with-openssl=../openssl --with-http_ssl_module --with-http_v2_module

    然而,并不是所有浏览器都支持 SNI,以下是常见浏览器支持 SNI 的最低版本:

    浏览器 最低版本
    Chrome Vista+ 全支持;XP 需要 Chrome 6+;OSX 10.5.7+ 且 Chrome 5+
    Firefox 2.0+
    Internet Explorer 7+ (需要 Vista+)
    Safari 3+ (需要 OS X 10.5.6+)
    Mobile Safari iOS 4.0+
    Android Webview 3.0+

    如果要避免在不支持 SNI 的浏览器中出现证书错误,只能将使用不同证书的 HTTPS 站点部署在不同 IP 上,最简单的做法是分开部署到不同机器上。

    开始录音、暂停录音

    这里有个比较特别的点,就是需要添加一个中间变量来标识是否当前是否在录音。因为在火狐浏览器上,我们发现一个问题,录音的流程都是正常的,但是点击暂停时却发现怎么也暂停不了,我们当时是使用 disconnect 方法。这种方式是不行的,这种方法是需要断开所有的连接才可以。后来发现,应该增加一个中间变量 this.isRecording 来判断当前是否正在录音,当点击开始时,将其设置为true,暂停时将其设置为false
    当我们开始录音时,会有一个录音监听的事件 onaudioprocess ,如果返回 true 则会将流写入,如果返回 false 则不会将其写入。因此判断this.isRecording,如果为 false 则直接 return

    JavaScript

    // 一些初始化 const audioContext = new AudioContext(); const sourceNode = audioContext.createMediaStreamSource(mediaStream); const scriptNode = audioContext.createScriptProcessor( BUFFER_SIZE, INPUT_CHANNELS_NUM, OUPUT_CHANNELS_NUM ); sourceNode.connect(this.scriptNode); scriptNode.connect(this.audioContext.destination); // 监听录音的过程 scriptNode.onaudioprocess = event => { if (!this.isRecording) return; // 判断是否正则录音 this.buffers.push(event.inputBuffer.getChannelData(0)); // 获取当前频道的数据,并写入数组 };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 一些初始化
    const audioContext = new AudioContext();
    const sourceNode = audioContext.createMediaStreamSource(mediaStream);
    const scriptNode = audioContext.createScriptProcessor(
    BUFFER_SIZE,
    INPUT_CHANNELS_NUM,
    OUPUT_CHANNELS_NUM
    );
    sourceNode.connect(this.scriptNode);
    scriptNode.connect(this.audioContext.destination);
    // 监听录音的过程
    scriptNode.onaudioprocess = event => {
    if (!this.isRecording) return; // 判断是否正则录音
    this.buffers.push(event.inputBuffer.getChannelData(0)); // 获取当前频道的数据,并写入数组
    };

    当然这里也会有个坑,就是无法再使用,自带获取当前录音时长的方法了,因为实际上并不是真正的暂停,而是没有将流写入罢了。于是我们还需要获取一下当前录音的时长,需要通过一个公式进行获取

    JavaScript

    const getDuration = () => { return (4096 * this.buffers.length) / this.audioContext.sampleRate // 4096为一个流的长度,sampleRate 为采样率 }

    1
    2
    3
    const getDuration = () => {
        return (4096 * this.buffers.length) / this.audioContext.sampleRate // 4096为一个流的长度,sampleRate 为采样率
    }

    这样就能够获取正确的录音时长了。

    解释下什么是 Event Bubbling 以及如何避免

    Event Bubbling 即指某个事件不仅会触发当前元素,还会以嵌套顺序传递到父元素中。直观而言就是对于某个子元素的点击事件同样会被父元素的点击事件处理器捕获。避免 Event Bubbling 的方式可以使用event.stopPropagation() 或者 IE 9 以下使用event.cancelBubble

    证书选择

    HTTPS 网站需要通过 CA 取得合法证书,证书通过数字签名技术确保第三方无法伪造。证书的简单原理如下:

    • 根据版本号、序列号、签名算法标识、发行者名称、有效期、证书主体名、证书主体公钥信息、发行商唯一标识、主体唯一标识、扩展生成 TBSCertificate(To Be Signed Certificate, 待签名证书)信息;
    • 签发数字签名:使用 HASH 函数对 TBSCertificate 计算得到消息摘要,用 CA 的私钥对消息摘要进行加密,得到签名;
    • 校验数字签名:使用相同的 HASH 函数对 TBSCertificate 计算得到消息摘要,与使用 CA 公钥解密签名得到内容相比较;

    使用 SHA-1 做为 HASH 函数的证书被称之为 SHA-1 证书,由于目前已经找到 SHA-1 的碰撞条件,将证书换成使用更安全的 SHA-2 做为 HASH 函数的 SHA-2 证书被提上日程。

    实际上,微软已经宣称自 2017 年 1 月 1 日起,将全面停止对 SHA-1 证书的支持。届时在最新版本的 Windows 系统中,SHA-1 证书将不被信任。

    而根据 Chrome 官方博客的文章,使用 SHA-1 证书且证书有效期在 2016 年 1 月 1 号至 2016 年 12 月 31 号之间的站点会被给予「安全的,但存在漏洞」的提示,也就是地址栏的小锁不再是绿色的,并且会有一个黄色小三角。而使用 SHA-1 证书且证书有效期超过 2017 年 1 月 1 号的站点会被给予「不安全」的红色警告,小锁上直接显示一个红色的叉。

    然而,并不是所有的终端都支持 SHA-2 证书,服务端不支持还好办,浏览器只能依赖于用户升级了。下面是常见浏览器支持 SHA-2 证书的最低版本:

    浏览器 支持 SHA-2 证书的最低版本
    Chrome 26+
    Firefox 1.5+
    Internet Explorer 6+ (需要 XP SP3+)
    Safari 3+ (需要 OS X 10.5+)
    Android Webview 2.3+

    可以看到,如果要照顾没有打 XP SP3 补丁的 IE6 用户,只能继续使用 SHA-1 证书。

    在我之前的文章中,还提到过 ECC 证书,这种新型的证书支持度更差,这里略过不提,有兴趣的同学可以点这里查看。

    是否可以针对不同浏览器启用不同证书呢?理论上服务端可以根据客户端 Client Hello 中的 Cipher Suites 特征以及是否支持 SNI 的特征来分配不同证书,不过我没有实际验证过。

    本文先写这么多,很多策略都需要根据自己网站的用户来决定,例如我的博客基本没有 IE8- 用户,理所当然可以禁用 SSLv3。如果你的产品还有很多使用老旧浏览器的用户,那就必须为这些用户做兼容方案了。一种方案是:只把主域安全级别配低,将 XP 上 IE 用户的 HTTPS 请求直接重定向到 HTTP 版本,这样其它域名可以使用高安全级别的配置,运维起来比较方便。

    1 赞 1 收藏 评论

    奥门新浦京网址 1

    结束录音

    结束录音的方式,我采用的是先暂停,之后需要试听或者其它的操作先执行,然后再将存储流的数组长度置为 0。

    == 与 === 的区别是什么

    === 也就是所谓的严格比较,关键的区别在于=== 会同时比较类型与值,而不是仅比较值。

    JavaScript

    // Example of comparators 0 == false; // true 0 === false; // false 2 == '2'; // true 2 === '2'; // false

    1
    2
    3
    4
    5
    6
    // Example of comparators
    0 == false; // true
    0 === false; // false
     
    2 == '2'; // true
    2 === '2'; // false

    获取频率

    JavaScript

    getVoiceSize = analyser => { const dataArray = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(dataArray); const data = dataArray.slice(100, 1000); const sum = data.reduce((a, b) => a + b); return sum; };

    1
    2
    3
    4
    5
    6
    7
    getVoiceSize = analyser => {
    const dataArray = new Uint8Array(analyser.frequencyBinCount);
    analyser.getByteFrequencyData(dataArray);
    const data = dataArray.slice(100, 1000);
    const sum = data.reduce((a, b) => a + b);
    return sum;
    };

    具体可以参考

    解释下 null 与 undefined 的区别

    JavaScript 中,null 是一个可以被分配的值,设置为 null 的变量意味着其无值。而 undefined 则代表着某个变量虽然声明了但是尚未进行过任何赋值。

    其它

    • HTTPS:在 chrome 下需要全站有 HTTPS 才允许使用
    • 微信:在微信内置的浏览器需要调用 JSSDK 才能使用
    • 音频格式转换:音频格式的方式也有很多了,能查到的大部分资料,大家基本上是互相 copy,当然还有一个音频质量的问题,这里就不赘述了。

    解释下 Prototypal Inheritance 与 Classical Inheritance 的区别

    在类继承中,类是不可变的,不同的语言中对于多继承的支持也不一样,有些语言中还支持接口、final、abstract 的概念。而原型继承则更为灵活,原型本身是可以可变的,并且对象可能继承自多个原型。

    结语

    这次遇到的大部分问题都是兼容性的问题,因此在上面踩了不少坑,尤其是移动端的问题,一开始还有出现因为获取录音时长写法错误的问题,导致直接卡死的情况。这次的经历也弥补了 HTML5 API 上的一些空白,当然最重要的还是要提醒一下大家,这种原生的 API 文档还是直接查看 MDN 来的简单粗暴!

    1 赞 3 收藏 评论

    奥门新浦京网址 2

    数组

    本文由奥门新浦京网址发布于Wed前段,转载请注明出处:录音的踩坑之旅,的一些经验分享

    关键词:

上一篇:Twitter的”fave”动画

下一篇:没有了